Implement Makruk counting rules
[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       /* !!?unconstrained back-rank shuffle */
6352       shuffleOpenings = 1;
6353     case VariantSuicide:
6354       pieces = FIDEArray;
6355       nrCastlingRights = 0;
6356       break;
6357     }
6358
6359     overrule = 0;
6360     if(appData.NrFiles >= 0) {
6361         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6362         gameInfo.boardWidth = appData.NrFiles;
6363     }
6364     if(appData.NrRanks >= 0) {
6365         gameInfo.boardHeight = appData.NrRanks;
6366     }
6367     if(appData.holdingsSize >= 0) {
6368         i = appData.holdingsSize;
6369 //        if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6370         gameInfo.holdingsSize = i;
6371     }
6372     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6373     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6374         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6375
6376     if(!handSize) handSize = BOARD_HEIGHT;
6377     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6378     if(pawnRow < 1) pawnRow = 1;
6379     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6380        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6381     if(gameInfo.variant == VariantChu) pawnRow = 3;
6382
6383     /* User pieceToChar list overrules defaults */
6384     if(appData.pieceToCharTable != NULL)
6385         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6386
6387     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6388
6389         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6390             s = (ChessSquare) 0; /* account holding counts in guard band */
6391         for( i=0; i<BOARD_HEIGHT; i++ )
6392             initialPosition[i][j] = s;
6393
6394         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6395         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6396         initialPosition[pawnRow][j] = WhitePawn;
6397         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6398         if(gameInfo.variant == VariantXiangqi) {
6399             if(j&1) {
6400                 initialPosition[pawnRow][j] =
6401                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6402                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6403                    initialPosition[2][j] = WhiteCannon;
6404                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6405                 }
6406             }
6407         }
6408         if(gameInfo.variant == VariantChu) {
6409              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6410                initialPosition[pawnRow+1][j] = WhiteCobra,
6411                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6412              for(i=1; i<pieceRows; i++) {
6413                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6414                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6415              }
6416         }
6417         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6418             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6419                initialPosition[0][j] = WhiteRook;
6420                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6421             }
6422         }
6423         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6424     }
6425     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6426     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6427
6428             j=BOARD_LEFT+1;
6429             initialPosition[1][j] = WhiteBishop;
6430             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6431             j=BOARD_RGHT-2;
6432             initialPosition[1][j] = WhiteRook;
6433             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6434     }
6435
6436     if( nrCastlingRights == -1) {
6437         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6438         /*       This sets default castling rights from none to normal corners   */
6439         /* Variants with other castling rights must set them themselves above    */
6440         nrCastlingRights = 6;
6441
6442         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6443         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6444         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6445         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6446         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6447         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6448      }
6449
6450      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6451      if(gameInfo.variant == VariantGreat) { // promotion commoners
6452         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6453         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6454         initialPosition[handSize-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6455         initialPosition[handSize-1-PieceToNumber(WhiteMan)][1] = 9;
6456      }
6457      if( gameInfo.variant == VariantSChess ) {
6458       initialPosition[1][0] = BlackMarshall;
6459       initialPosition[2][0] = BlackAngel;
6460       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6461       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6462       initialPosition[1][1] = initialPosition[2][1] =
6463       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6464      }
6465      initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6466   if (appData.debugMode) {
6467     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6468   }
6469     if(shuffleOpenings) {
6470         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6471         startedFromSetupPosition = TRUE;
6472     }
6473     if(startedFromPositionFile) {
6474       /* [HGM] loadPos: use PositionFile for every new game */
6475       CopyBoard(initialPosition, filePosition);
6476       for(i=0; i<nrCastlingRights; i++)
6477           initialRights[i] = filePosition[CASTLING][i];
6478       startedFromSetupPosition = TRUE;
6479     }
6480     if(*appData.men) LoadPieceDesc(appData.men);
6481
6482     CopyBoard(boards[0], initialPosition);
6483
6484     if(oldx != gameInfo.boardWidth ||
6485        oldy != gameInfo.boardHeight ||
6486        oldv != gameInfo.variant ||
6487        oldh != gameInfo.holdingsWidth
6488                                          )
6489             InitDrawingSizes(-2 ,0);
6490
6491     oldv = gameInfo.variant;
6492     if (redraw)
6493       DrawPosition(TRUE, boards[currentMove]);
6494 }
6495
6496 void
6497 SendBoard (ChessProgramState *cps, int moveNum)
6498 {
6499     char message[MSG_SIZ];
6500
6501     if (cps->useSetboard) {
6502       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6503       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6504       SendToProgram(message, cps);
6505       free(fen);
6506
6507     } else {
6508       ChessSquare *bp;
6509       int i, j, left=0, right=BOARD_WIDTH;
6510       /* Kludge to set black to move, avoiding the troublesome and now
6511        * deprecated "black" command.
6512        */
6513       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6514         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6515
6516       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6517
6518       SendToProgram("edit\n", cps);
6519       SendToProgram("#\n", cps);
6520       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6521         bp = &boards[moveNum][i][left];
6522         for (j = left; j < right; j++, bp++) {
6523           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6524           if ((int) *bp < (int) BlackPawn) {
6525             if(j == BOARD_RGHT+1)
6526                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6527             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6528             if(message[0] == '+' || message[0] == '~') {
6529               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6530                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6531                         AAA + j, ONE + i - '0');
6532             }
6533             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6534                 message[1] = BOARD_RGHT   - 1 - j + '1';
6535                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6536             }
6537             SendToProgram(message, cps);
6538           }
6539         }
6540       }
6541
6542       SendToProgram("c\n", cps);
6543       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6544         bp = &boards[moveNum][i][left];
6545         for (j = left; j < right; j++, bp++) {
6546           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6547           if (((int) *bp != (int) EmptySquare)
6548               && ((int) *bp >= (int) BlackPawn)) {
6549             if(j == BOARD_LEFT-2)
6550                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6551             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6552                     AAA + j, ONE + i - '0');
6553             if(message[0] == '+' || message[0] == '~') {
6554               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6555                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6556                         AAA + j, ONE + i - '0');
6557             }
6558             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6559                 message[1] = BOARD_RGHT   - 1 - j + '1';
6560                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6561             }
6562             SendToProgram(message, cps);
6563           }
6564         }
6565       }
6566
6567       SendToProgram(".\n", cps);
6568     }
6569     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6570 }
6571
6572 char exclusionHeader[MSG_SIZ];
6573 int exCnt, excludePtr;
6574 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6575 static Exclusion excluTab[200];
6576 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6577
6578 static void
6579 WriteMap (int s)
6580 {
6581     int j;
6582     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6583     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6584 }
6585
6586 static void
6587 ClearMap ()
6588 {
6589     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6590     excludePtr = 24; exCnt = 0;
6591     WriteMap(0);
6592 }
6593
6594 static void
6595 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6596 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6597     char buf[2*MOVE_LEN], *p;
6598     Exclusion *e = excluTab;
6599     int i;
6600     for(i=0; i<exCnt; i++)
6601         if(e[i].ff == fromX && e[i].fr == fromY &&
6602            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6603     if(i == exCnt) { // was not in exclude list; add it
6604         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6605         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6606             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6607             return; // abort
6608         }
6609         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6610         excludePtr++; e[i].mark = excludePtr++;
6611         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6612         exCnt++;
6613     }
6614     exclusionHeader[e[i].mark] = state;
6615 }
6616
6617 static int
6618 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6619 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6620     char buf[MSG_SIZ];
6621     int j, k;
6622     ChessMove moveType;
6623     if((signed char)promoChar == -1) { // kludge to indicate best move
6624         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6625             return 1; // if unparsable, abort
6626     }
6627     // update exclusion map (resolving toggle by consulting existing state)
6628     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6629     j = k%8; k >>= 3;
6630     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6631     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6632          excludeMap[k] |=   1<<j;
6633     else excludeMap[k] &= ~(1<<j);
6634     // update header
6635     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6636     // inform engine
6637     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6638     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6639     SendToBoth(buf);
6640     return (state == '+');
6641 }
6642
6643 static void
6644 ExcludeClick (int index)
6645 {
6646     int i, j;
6647     Exclusion *e = excluTab;
6648     if(index < 25) { // none, best or tail clicked
6649         if(index < 13) { // none: include all
6650             WriteMap(0); // clear map
6651             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6652             SendToBoth("include all\n"); // and inform engine
6653         } else if(index > 18) { // tail
6654             if(exclusionHeader[19] == '-') { // tail was excluded
6655                 SendToBoth("include all\n");
6656                 WriteMap(0); // clear map completely
6657                 // now re-exclude selected moves
6658                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6659                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6660             } else { // tail was included or in mixed state
6661                 SendToBoth("exclude all\n");
6662                 WriteMap(0xFF); // fill map completely
6663                 // now re-include selected moves
6664                 j = 0; // count them
6665                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6666                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6667                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6668             }
6669         } else { // best
6670             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6671         }
6672     } else {
6673         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6674             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6675             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6676             break;
6677         }
6678     }
6679 }
6680
6681 ChessSquare
6682 DefaultPromoChoice (int white)
6683 {
6684     ChessSquare result;
6685     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6686        gameInfo.variant == VariantMakruk)
6687         result = WhiteFerz; // no choice
6688     else if(gameInfo.variant == VariantASEAN)
6689         result = WhiteRook; // no choice
6690     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6691         result= WhiteKing; // in Suicide Q is the last thing we want
6692     else if(gameInfo.variant == VariantSpartan)
6693         result = white ? WhiteQueen : WhiteAngel;
6694     else result = WhiteQueen;
6695     if(!white) result = WHITE_TO_BLACK result;
6696     return result;
6697 }
6698
6699 static int autoQueen; // [HGM] oneclick
6700
6701 int
6702 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6703 {
6704     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6705     /* [HGM] add Shogi promotions */
6706     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6707     ChessSquare piece, partner;
6708     ChessMove moveType;
6709     Boolean premove;
6710
6711     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6712     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6713
6714     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6715       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6716         return FALSE;
6717
6718     if(legal[toY][toX] == 4) return FALSE;
6719
6720     piece = boards[currentMove][fromY][fromX];
6721     if(gameInfo.variant == VariantChu) {
6722         promotionZoneSize = (BOARD_HEIGHT - deadRanks)/3;
6723         if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6724         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6725     } else if(gameInfo.variant == VariantShogi) {
6726         promotionZoneSize = (BOARD_HEIGHT- deadRanks)/3 +(BOARD_HEIGHT == 8);
6727         highestPromotingPiece = (int)WhiteAlfil;
6728         if(PieceToChar(piece) != '+' || PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = piece;
6729     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6730         promotionZoneSize = 3;
6731     }
6732
6733     // Treat Lance as Pawn when it is not representing Amazon or Lance
6734     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6735         if(piece == WhiteLance) piece = WhitePawn; else
6736         if(piece == BlackLance) piece = BlackPawn;
6737     }
6738
6739     // next weed out all moves that do not touch the promotion zone at all
6740     if((int)piece >= BlackPawn) {
6741         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6742              return FALSE;
6743         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6744         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6745     } else {
6746         if(  toY < BOARD_HEIGHT - deadRanks - promotionZoneSize &&
6747            fromY < BOARD_HEIGHT - deadRanks - promotionZoneSize) return FALSE;
6748         if(fromY >= BOARD_HEIGHT - deadRanks - promotionZoneSize && gameInfo.variant == VariantChuChess)
6749              return FALSE;
6750     }
6751
6752     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6753
6754     // weed out mandatory Shogi promotions
6755     if(gameInfo.variant == VariantShogi) {
6756         if(piece >= BlackPawn) {
6757             if(toY == 0 && piece == BlackPawn ||
6758                toY == 0 && piece == BlackQueen ||
6759                toY <= 1 && piece == BlackKnight) {
6760                 *promoChoice = '+';
6761                 return FALSE;
6762             }
6763         } else {
6764             if(toY == BOARD_HEIGHT-deadRanks-1 && piece == WhitePawn ||
6765                toY == BOARD_HEIGHT-deadRanks-1 && piece == WhiteQueen ||
6766                toY >= BOARD_HEIGHT-deadRanks-2 && piece == WhiteKnight) {
6767                 *promoChoice = '+';
6768                 return FALSE;
6769             }
6770         }
6771     }
6772
6773     // weed out obviously illegal Pawn moves
6774     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6775         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6776         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6777         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6778         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6779         // note we are not allowed to test for valid (non-)capture, due to premove
6780     }
6781
6782     // we either have a choice what to promote to, or (in Shogi) whether to promote
6783     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6784        gameInfo.variant == VariantMakruk) {
6785         ChessSquare p=BlackFerz;  // no choice
6786         while(p < EmptySquare) {  //but make sure we use piece that exists
6787             *promoChoice = PieceToChar(p++);
6788             if(*promoChoice != '.') break;
6789         }
6790         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6791     }
6792     // no sense asking what we must promote to if it is going to explode...
6793     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6794         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6795         return FALSE;
6796     }
6797     // give caller the default choice even if we will not make it
6798     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6799     partner = piece; // pieces can promote if the pieceToCharTable says so
6800     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6801     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6802     if(        sweepSelect && gameInfo.variant != VariantGreat
6803                            && gameInfo.variant != VariantGrand
6804                            && gameInfo.variant != VariantSuper) return FALSE;
6805     if(autoQueen) return FALSE; // predetermined
6806
6807     // suppress promotion popup on illegal moves that are not premoves
6808     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6809               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6810     if(appData.testLegality && !premove) {
6811         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6812                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6813         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6814         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6815             return FALSE;
6816     }
6817
6818     return TRUE;
6819 }
6820
6821 int
6822 InPalace (int row, int column)
6823 {   /* [HGM] for Xiangqi */
6824     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6825          column < (BOARD_WIDTH + 4)/2 &&
6826          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6827     return FALSE;
6828 }
6829
6830 int
6831 PieceForSquare (int x, int y)
6832 {
6833   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) return -1;
6834   if(x == BOARD_RGHT+1 && handOffsets & 1) y += handSize - BOARD_HEIGHT;
6835   if(x == BOARD_LEFT-2 && !(handOffsets & 2)) y += handSize - BOARD_HEIGHT;
6836      return boards[currentMove][y][x];
6837 }
6838
6839 ChessSquare
6840 More (Board board, int col, int start, int end)
6841 {
6842     int k;
6843     for(k=start; k<end; k++) if(board[k][col]) return (col == 1 ? WhiteMonarch : BlackMonarch); // arrow image
6844     return EmptySquare;
6845 }
6846
6847 void
6848 DrawPosition (int repaint, Board board)
6849 {
6850     Board compactedBoard;
6851     if(handSize > BOARD_HEIGHT && board) {
6852         int k;
6853         CopyBoard(compactedBoard, board);
6854         if(handOffsets & 1) {
6855             for(k=0; k<BOARD_HEIGHT; k++) {
6856                 compactedBoard[k][BOARD_WIDTH-1] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-1];
6857                 compactedBoard[k][BOARD_WIDTH-2] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-2];
6858             }
6859             compactedBoard[0][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, 0, handSize-BOARD_HEIGHT+1);
6860         } else compactedBoard[BOARD_HEIGHT-1][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, BOARD_HEIGHT-1, handSize);
6861         if(!(handOffsets & 2)) {
6862             for(k=0; k<BOARD_HEIGHT; k++) {
6863                 compactedBoard[k][0] = board[k+handSize-BOARD_HEIGHT][0];
6864                 compactedBoard[k][1] = board[k+handSize-BOARD_HEIGHT][1];
6865             }
6866             compactedBoard[0][0] = More(board, 1, 0, handSize-BOARD_HEIGHT+1);
6867         } else compactedBoard[BOARD_HEIGHT-1][0] = More(board, 1, BOARD_HEIGHT-1, handSize);
6868         DrawPositionX(TRUE, compactedBoard);
6869     } else DrawPositionX(repaint, board);
6870 }
6871
6872 int
6873 OKToStartUserMove (int x, int y)
6874 {
6875     ChessSquare from_piece;
6876     int white_piece;
6877
6878     if (matchMode) return FALSE;
6879     if (gameMode == EditPosition) return TRUE;
6880
6881     if (x >= 0 && y >= 0)
6882       from_piece = boards[currentMove][y][x];
6883     else
6884       from_piece = EmptySquare;
6885
6886     if (from_piece == EmptySquare) return FALSE;
6887
6888     white_piece = (int)from_piece >= (int)WhitePawn &&
6889       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6890
6891     switch (gameMode) {
6892       case AnalyzeFile:
6893       case TwoMachinesPlay:
6894       case EndOfGame:
6895         return FALSE;
6896
6897       case IcsObserving:
6898       case IcsIdle:
6899         return FALSE;
6900
6901       case MachinePlaysWhite:
6902       case IcsPlayingBlack:
6903         if (appData.zippyPlay) return FALSE;
6904         if (white_piece) {
6905             DisplayMoveError(_("You are playing Black"));
6906             return FALSE;
6907         }
6908         break;
6909
6910       case MachinePlaysBlack:
6911       case IcsPlayingWhite:
6912         if (appData.zippyPlay) return FALSE;
6913         if (!white_piece) {
6914             DisplayMoveError(_("You are playing White"));
6915             return FALSE;
6916         }
6917         break;
6918
6919       case PlayFromGameFile:
6920             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6921       case EditGame:
6922       case AnalyzeMode:
6923         if (!white_piece && WhiteOnMove(currentMove)) {
6924             DisplayMoveError(_("It is White's turn"));
6925             return FALSE;
6926         }
6927         if (white_piece && !WhiteOnMove(currentMove)) {
6928             DisplayMoveError(_("It is Black's turn"));
6929             return FALSE;
6930         }
6931         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6932             /* Editing correspondence game history */
6933             /* Could disallow this or prompt for confirmation */
6934             cmailOldMove = -1;
6935         }
6936         break;
6937
6938       case BeginningOfGame:
6939         if (appData.icsActive) return FALSE;
6940         if (!appData.noChessProgram) {
6941             if (!white_piece) {
6942                 DisplayMoveError(_("You are playing White"));
6943                 return FALSE;
6944             }
6945         }
6946         break;
6947
6948       case Training:
6949         if (!white_piece && WhiteOnMove(currentMove)) {
6950             DisplayMoveError(_("It is White's turn"));
6951             return FALSE;
6952         }
6953         if (white_piece && !WhiteOnMove(currentMove)) {
6954             DisplayMoveError(_("It is Black's turn"));
6955             return FALSE;
6956         }
6957         break;
6958
6959       default:
6960       case IcsExamining:
6961         break;
6962     }
6963     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6964         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6965         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6966         && gameMode != AnalyzeFile && gameMode != Training) {
6967         DisplayMoveError(_("Displayed position is not current"));
6968         return FALSE;
6969     }
6970     return TRUE;
6971 }
6972
6973 Boolean
6974 OnlyMove (int *x, int *y, Boolean captures)
6975 {
6976     DisambiguateClosure cl;
6977     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6978     switch(gameMode) {
6979       case MachinePlaysBlack:
6980       case IcsPlayingWhite:
6981       case BeginningOfGame:
6982         if(!WhiteOnMove(currentMove)) return FALSE;
6983         break;
6984       case MachinePlaysWhite:
6985       case IcsPlayingBlack:
6986         if(WhiteOnMove(currentMove)) return FALSE;
6987         break;
6988       case EditGame:
6989         break;
6990       default:
6991         return FALSE;
6992     }
6993     cl.pieceIn = EmptySquare;
6994     cl.rfIn = *y;
6995     cl.ffIn = *x;
6996     cl.rtIn = -1;
6997     cl.ftIn = -1;
6998     cl.promoCharIn = NULLCHAR;
6999     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7000     if( cl.kind == NormalMove ||
7001         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7002         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7003         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7004       fromX = cl.ff;
7005       fromY = cl.rf;
7006       *x = cl.ft;
7007       *y = cl.rt;
7008       return TRUE;
7009     }
7010     if(cl.kind != ImpossibleMove) return FALSE;
7011     cl.pieceIn = EmptySquare;
7012     cl.rfIn = -1;
7013     cl.ffIn = -1;
7014     cl.rtIn = *y;
7015     cl.ftIn = *x;
7016     cl.promoCharIn = NULLCHAR;
7017     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7018     if( cl.kind == NormalMove ||
7019         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7020         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7021         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7022       fromX = cl.ff;
7023       fromY = cl.rf;
7024       *x = cl.ft;
7025       *y = cl.rt;
7026       autoQueen = TRUE; // act as if autoQueen on when we click to-square
7027       return TRUE;
7028     }
7029     return FALSE;
7030 }
7031
7032 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
7033 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
7034 int lastLoadGameUseList = FALSE;
7035 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
7036 ChessMove lastLoadGameStart = EndOfFile;
7037 int doubleClick;
7038 Boolean addToBookFlag;
7039 static Board rightsBoard, nullBoard;
7040
7041 void
7042 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7043 {
7044     ChessMove moveType;
7045     ChessSquare pup;
7046     int ff=fromX, rf=fromY, ft=toX, rt=toY;
7047
7048     /* Check if the user is playing in turn.  This is complicated because we
7049        let the user "pick up" a piece before it is his turn.  So the piece he
7050        tried to pick up may have been captured by the time he puts it down!
7051        Therefore we use the color the user is supposed to be playing in this
7052        test, not the color of the piece that is currently on the starting
7053        square---except in EditGame mode, where the user is playing both
7054        sides; fortunately there the capture race can't happen.  (It can
7055        now happen in IcsExamining mode, but that's just too bad.  The user
7056        will get a somewhat confusing message in that case.)
7057        */
7058
7059     switch (gameMode) {
7060       case AnalyzeFile:
7061       case TwoMachinesPlay:
7062       case EndOfGame:
7063       case IcsObserving:
7064       case IcsIdle:
7065         /* We switched into a game mode where moves are not accepted,
7066            perhaps while the mouse button was down. */
7067         return;
7068
7069       case MachinePlaysWhite:
7070         /* User is moving for Black */
7071         if (WhiteOnMove(currentMove)) {
7072             DisplayMoveError(_("It is White's turn"));
7073             return;
7074         }
7075         break;
7076
7077       case MachinePlaysBlack:
7078         /* User is moving for White */
7079         if (!WhiteOnMove(currentMove)) {
7080             DisplayMoveError(_("It is Black's turn"));
7081             return;
7082         }
7083         break;
7084
7085       case PlayFromGameFile:
7086             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7087       case EditGame:
7088       case IcsExamining:
7089       case BeginningOfGame:
7090       case AnalyzeMode:
7091       case Training:
7092         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7093         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7094             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7095             /* User is moving for Black */
7096             if (WhiteOnMove(currentMove)) {
7097                 DisplayMoveError(_("It is White's turn"));
7098                 return;
7099             }
7100         } else {
7101             /* User is moving for White */
7102             if (!WhiteOnMove(currentMove)) {
7103                 DisplayMoveError(_("It is Black's turn"));
7104                 return;
7105             }
7106         }
7107         break;
7108
7109       case IcsPlayingBlack:
7110         /* User is moving for Black */
7111         if (WhiteOnMove(currentMove)) {
7112             if (!appData.premove) {
7113                 DisplayMoveError(_("It is White's turn"));
7114             } else if (toX >= 0 && toY >= 0) {
7115                 premoveToX = toX;
7116                 premoveToY = toY;
7117                 premoveFromX = fromX;
7118                 premoveFromY = fromY;
7119                 premovePromoChar = promoChar;
7120                 gotPremove = 1;
7121                 if (appData.debugMode)
7122                     fprintf(debugFP, "Got premove: fromX %d,"
7123                             "fromY %d, toX %d, toY %d\n",
7124                             fromX, fromY, toX, toY);
7125             }
7126             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7127             return;
7128         }
7129         break;
7130
7131       case IcsPlayingWhite:
7132         /* User is moving for White */
7133         if (!WhiteOnMove(currentMove)) {
7134             if (!appData.premove) {
7135                 DisplayMoveError(_("It is Black's turn"));
7136             } else if (toX >= 0 && toY >= 0) {
7137                 premoveToX = toX;
7138                 premoveToY = toY;
7139                 premoveFromX = fromX;
7140                 premoveFromY = fromY;
7141                 premovePromoChar = promoChar;
7142                 gotPremove = 1;
7143                 if (appData.debugMode)
7144                     fprintf(debugFP, "Got premove: fromX %d,"
7145                             "fromY %d, toX %d, toY %d\n",
7146                             fromX, fromY, toX, toY);
7147             }
7148             DrawPosition(TRUE, boards[currentMove]);
7149             return;
7150         }
7151         break;
7152
7153       default:
7154         break;
7155
7156       case EditPosition:
7157         /* EditPosition, empty square, or different color piece;
7158            click-click move is possible */
7159         if (toX == -2 || toY == -2) {
7160             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7161             DrawPosition(FALSE, boards[currentMove]);
7162             return;
7163         } else if (toX >= 0 && toY >= 0) {
7164             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7165                 ChessSquare p = boards[0][rf][ff];
7166                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7167                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7168                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7169                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7170                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7171                     gatingPiece = p;
7172                 }
7173             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7174             boards[0][toY][toX] = boards[0][fromY][fromX];
7175             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7176                 if(boards[0][fromY][0] != EmptySquare) {
7177                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7178                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7179                 }
7180             } else
7181             if(fromX == BOARD_RGHT+1) {
7182                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7183                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7184                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7185                 }
7186             } else
7187             boards[0][fromY][fromX] = gatingPiece;
7188             ClearHighlights();
7189             DrawPosition(FALSE, boards[currentMove]);
7190             return;
7191         }
7192         return;
7193     }
7194
7195     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7196     pup = boards[currentMove][toY][toX];
7197
7198     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7199     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7200          if( pup != EmptySquare ) return;
7201          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7202            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7203                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7204            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7205            if(fromX == 0) fromY = handSize-1 - fromY; // black holdings upside-down
7206            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7207            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7208          fromY = DROP_RANK;
7209     }
7210
7211     /* [HGM] always test for legality, to get promotion info */
7212     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7213                                          fromY, fromX, toY, toX, promoChar);
7214
7215     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7216
7217     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7218
7219     /* [HGM] but possibly ignore an IllegalMove result */
7220     if (appData.testLegality) {
7221         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7222             DisplayMoveError(_("Illegal move"));
7223             return;
7224         }
7225     }
7226
7227     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7228         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7229              ClearPremoveHighlights(); // was included
7230         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7231         DrawPosition(FALSE, NULL);
7232         return;
7233     }
7234
7235     if(addToBookFlag) { // adding moves to book
7236         char buf[MSG_SIZ], move[MSG_SIZ];
7237         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7238         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7239                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7240         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7241         AddBookMove(buf);
7242         addToBookFlag = FALSE;
7243         ClearHighlights();
7244         return;
7245     }
7246
7247     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7248 }
7249
7250 /* Common tail of UserMoveEvent and DropMenuEvent */
7251 int
7252 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7253 {
7254     char *bookHit = 0;
7255
7256     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7257         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7258         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7259         if(WhiteOnMove(currentMove)) {
7260             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7261         } else {
7262             if(!boards[currentMove][handSize-1-k][1]) return 0;
7263         }
7264     }
7265
7266     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7267        move type in caller when we know the move is a legal promotion */
7268     if(moveType == NormalMove && promoChar)
7269         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7270
7271     /* [HGM] <popupFix> The following if has been moved here from
7272        UserMoveEvent(). Because it seemed to belong here (why not allow
7273        piece drops in training games?), and because it can only be
7274        performed after it is known to what we promote. */
7275     if (gameMode == Training) {
7276       /* compare the move played on the board to the next move in the
7277        * game. If they match, display the move and the opponent's response.
7278        * If they don't match, display an error message.
7279        */
7280       int saveAnimate;
7281       Board testBoard;
7282       CopyBoard(testBoard, boards[currentMove]);
7283       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7284
7285       if (CompareBoards(testBoard, boards[currentMove+1])) {
7286         ForwardInner(currentMove+1);
7287
7288         /* Autoplay the opponent's response.
7289          * if appData.animate was TRUE when Training mode was entered,
7290          * the response will be animated.
7291          */
7292         saveAnimate = appData.animate;
7293         appData.animate = animateTraining;
7294         ForwardInner(currentMove+1);
7295         appData.animate = saveAnimate;
7296
7297         /* check for the end of the game */
7298         if (currentMove >= forwardMostMove) {
7299           gameMode = PlayFromGameFile;
7300           ModeHighlight();
7301           SetTrainingModeOff();
7302           DisplayInformation(_("End of game"));
7303         }
7304       } else {
7305         DisplayError(_("Incorrect move"), 0);
7306       }
7307       return 1;
7308     }
7309
7310   /* Ok, now we know that the move is good, so we can kill
7311      the previous line in Analysis Mode */
7312   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7313                                 && currentMove < forwardMostMove) {
7314     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7315     else forwardMostMove = currentMove;
7316   }
7317
7318   ClearMap();
7319
7320   /* If we need the chess program but it's dead, restart it */
7321   ResurrectChessProgram();
7322
7323   /* A user move restarts a paused game*/
7324   if (pausing)
7325     PauseEvent();
7326
7327   thinkOutput[0] = NULLCHAR;
7328
7329   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7330
7331   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7332     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7333     return 1;
7334   }
7335
7336   if (gameMode == BeginningOfGame) {
7337     if (appData.noChessProgram) {
7338       gameMode = EditGame;
7339       SetGameInfo();
7340     } else {
7341       char buf[MSG_SIZ];
7342       gameMode = MachinePlaysBlack;
7343       StartClocks();
7344       SetGameInfo();
7345       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7346       DisplayTitle(buf);
7347       if (first.sendName) {
7348         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7349         SendToProgram(buf, &first);
7350       }
7351       StartClocks();
7352     }
7353     ModeHighlight();
7354   }
7355
7356   /* Relay move to ICS or chess engine */
7357   if (appData.icsActive) {
7358     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7359         gameMode == IcsExamining) {
7360       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7361         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7362         SendToICS("draw ");
7363         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7364       }
7365       // also send plain move, in case ICS does not understand atomic claims
7366       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7367       ics_user_moved = 1;
7368     }
7369   } else {
7370     if (first.sendTime && (gameMode == BeginningOfGame ||
7371                            gameMode == MachinePlaysWhite ||
7372                            gameMode == MachinePlaysBlack)) {
7373       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7374     }
7375     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7376          // [HGM] book: if program might be playing, let it use book
7377         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7378         first.maybeThinking = TRUE;
7379     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7380         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7381         SendBoard(&first, currentMove+1);
7382         if(second.analyzing) {
7383             if(!second.useSetboard) SendToProgram("undo\n", &second);
7384             SendBoard(&second, currentMove+1);
7385         }
7386     } else {
7387         SendMoveToProgram(forwardMostMove-1, &first);
7388         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7389     }
7390     if (currentMove == cmailOldMove + 1) {
7391       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7392     }
7393   }
7394
7395   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7396
7397   switch (gameMode) {
7398   case EditGame:
7399     if(appData.testLegality)
7400     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7401     case MT_NONE:
7402     case MT_CHECK:
7403       break;
7404     case MT_CHECKMATE:
7405     case MT_STAINMATE:
7406       if (WhiteOnMove(currentMove)) {
7407         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7408       } else {
7409         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7410       }
7411       break;
7412     case MT_STALEMATE:
7413       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7414       break;
7415     }
7416     break;
7417
7418   case MachinePlaysBlack:
7419   case MachinePlaysWhite:
7420     /* disable certain menu options while machine is thinking */
7421     SetMachineThinkingEnables();
7422     break;
7423
7424   default:
7425     break;
7426   }
7427
7428   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7429   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7430
7431   if(bookHit) { // [HGM] book: simulate book reply
7432         static char bookMove[MSG_SIZ]; // a bit generous?
7433
7434         programStats.nodes = programStats.depth = programStats.time =
7435         programStats.score = programStats.got_only_move = 0;
7436         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7437
7438         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7439         strcat(bookMove, bookHit);
7440         HandleMachineMove(bookMove, &first);
7441   }
7442   return 1;
7443 }
7444
7445 void
7446 MarkByFEN(char *fen)
7447 {
7448         int r, f;
7449         if(!appData.markers || !appData.highlightDragging) return;
7450         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7451         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7452         while(*fen) {
7453             int s = 0;
7454             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7455             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7456             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7457             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7458             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7459             if(*fen == 'T') marker[r][f++] = 0; else
7460             if(*fen == 'Y') marker[r][f++] = 1; else
7461             if(*fen == 'G') marker[r][f++] = 3; else
7462             if(*fen == 'B') marker[r][f++] = 4; else
7463             if(*fen == 'C') marker[r][f++] = 5; else
7464             if(*fen == 'M') marker[r][f++] = 6; else
7465             if(*fen == 'W') marker[r][f++] = 7; else
7466             if(*fen == 'D') marker[r][f++] = 8; else
7467             if(*fen == 'R') marker[r][f++] = 2; else {
7468                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7469               f += s; fen -= s>0;
7470             }
7471             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7472             if(r < 0) break;
7473             fen++;
7474         }
7475         DrawPosition(TRUE, NULL);
7476 }
7477
7478 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7479
7480 void
7481 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7482 {
7483     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7484     Markers *m = (Markers *) closure;
7485     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7486                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7487         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7488                          || kind == WhiteCapturesEnPassant
7489                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7490     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7491 }
7492
7493 static int hoverSavedValid;
7494
7495 void
7496 MarkTargetSquares (int clear)
7497 {
7498   int x, y, sum=0;
7499   if(clear) { // no reason to ever suppress clearing
7500     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7501     hoverSavedValid = 0;
7502     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7503   } else {
7504     int capt = 0;
7505     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7506        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7507     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7508     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7509       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7510       if(capt)
7511       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;
7512     }
7513   }
7514   DrawPosition(FALSE, NULL);
7515 }
7516
7517 int
7518 Explode (Board board, int fromX, int fromY, int toX, int toY)
7519 {
7520     if(gameInfo.variant == VariantAtomic &&
7521        (board[toY][toX] != EmptySquare ||                     // capture?
7522         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7523                          board[fromY][fromX] == BlackPawn   )
7524       )) {
7525         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7526         return TRUE;
7527     }
7528     return FALSE;
7529 }
7530
7531 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7532
7533 int
7534 CanPromote (ChessSquare piece, int y)
7535 {
7536         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7537         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7538         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7539         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7540            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7541           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7542            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7543         return (piece == BlackPawn && y <= zone ||
7544                 piece == WhitePawn && y >= BOARD_HEIGHT-1-deadRanks-zone ||
7545                 piece == BlackLance && y <= zone ||
7546                 piece == WhiteLance && y >= BOARD_HEIGHT-1-deadRanks-zone );
7547 }
7548
7549 void
7550 HoverEvent (int xPix, int yPix, int x, int y)
7551 {
7552         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7553         int r, f;
7554         if(!first.highlight) return;
7555         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7556         if(x == oldX && y == oldY) return; // only do something if we enter new square
7557         oldFromX = fromX; oldFromY = fromY;
7558         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7559           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7560             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7561           hoverSavedValid = 1;
7562         } else if(oldX != x || oldY != y) {
7563           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7564           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7565           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7566             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7567           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7568             char buf[MSG_SIZ];
7569             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7570             SendToProgram(buf, &first);
7571           }
7572           oldX = x; oldY = y;
7573 //        SetHighlights(fromX, fromY, x, y);
7574         }
7575 }
7576
7577 void ReportClick(char *action, int x, int y)
7578 {
7579         char buf[MSG_SIZ]; // Inform engine of what user does
7580         int r, f;
7581         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7582           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7583             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7584         if(!first.highlight || gameMode == EditPosition) return;
7585         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7586         SendToProgram(buf, &first);
7587 }
7588
7589 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7590 Boolean deferChoice;
7591 int createX = -1, createY = -1; // square where we last created a piece in EditPosition mode
7592
7593 void
7594 LeftClick (ClickType clickType, int xPix, int yPix)
7595 {
7596     int x, y;
7597     static Boolean saveAnimate;
7598     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7599     char promoChoice = NULLCHAR;
7600     ChessSquare piece;
7601     static TimeMark lastClickTime, prevClickTime;
7602
7603     if(flashing) return;
7604
7605   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7606     x = EventToSquare(xPix, BOARD_WIDTH);
7607     y = EventToSquare(yPix, BOARD_HEIGHT);
7608     if (!flipView && y >= 0) {
7609         y = BOARD_HEIGHT - 1 - y;
7610     }
7611     if (flipView && x >= 0) {
7612         x = BOARD_WIDTH - 1 - x;
7613     }
7614
7615     // map clicks in offsetted holdings back to true coords (or switch the offset)
7616     if(x == BOARD_RGHT+1) {
7617         if(handOffsets & 1) {
7618             if(y == 0) { handOffsets &= ~1; DrawPosition(TRUE, boards[currentMove]); return; }
7619             y += handSize - BOARD_HEIGHT;
7620         } else if(y == BOARD_HEIGHT-1) { handOffsets |= 1; DrawPosition(TRUE, boards[currentMove]); return; }
7621     }
7622     if(x == BOARD_LEFT-2) {
7623         if(!(handOffsets & 2)) {
7624             if(y == 0) { handOffsets |= 2; DrawPosition(TRUE, boards[currentMove]); return; }
7625             y += handSize - BOARD_HEIGHT;
7626         } else if(y == BOARD_HEIGHT-1) { handOffsets &= ~2; DrawPosition(TRUE, boards[currentMove]); return; }
7627     }
7628
7629     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && 
7630         (boards[currentMove][y][x] == EmptySquare || x == createX && y == createY) ) {
7631         static int dummy;
7632         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7633         right = TRUE;
7634         return;
7635     }
7636
7637     createX = createY = -1;
7638
7639     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7640
7641     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7642
7643     if (clickType == Press) ErrorPopDown();
7644     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7645
7646     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7647         defaultPromoChoice = promoSweep;
7648         promoSweep = EmptySquare;   // terminate sweep
7649         promoDefaultAltered = TRUE;
7650         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7651     }
7652
7653     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7654         if(clickType == Release) return; // ignore upclick of click-click destination
7655         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7656         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7657         if(gameInfo.holdingsWidth &&
7658                 (WhiteOnMove(currentMove)
7659                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7660                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7661             // click in right holdings, for determining promotion piece
7662             ChessSquare p = boards[currentMove][y][x];
7663             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7664             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7665             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7666                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7667                 fromX = fromY = -1;
7668                 return;
7669             }
7670         }
7671         DrawPosition(FALSE, boards[currentMove]);
7672         return;
7673     }
7674
7675     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7676     if(clickType == Press
7677             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7678               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7679               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7680         return;
7681
7682     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7683         // could be static click on premove from-square: abort premove
7684         gotPremove = 0;
7685         ClearPremoveHighlights();
7686     }
7687
7688     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7689         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7690
7691     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7692         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7693                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7694         defaultPromoChoice = DefaultPromoChoice(side);
7695     }
7696
7697     autoQueen = appData.alwaysPromoteToQueen;
7698
7699     if (fromX == -1) {
7700       int originalY = y;
7701       gatingPiece = EmptySquare;
7702       if (clickType != Press) {
7703         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7704             DragPieceEnd(xPix, yPix); dragging = 0;
7705             DrawPosition(FALSE, NULL);
7706         }
7707         return;
7708       }
7709       doubleClick = FALSE;
7710       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7711         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7712       }
7713       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7714       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7715          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7716          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7717             /* First square */
7718             if (OKToStartUserMove(fromX, fromY)) {
7719                 second = 0;
7720                 ReportClick("lift", x, y);
7721                 MarkTargetSquares(0);
7722                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7723                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7724                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7725                     promoSweep = defaultPromoChoice;
7726                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7727                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7728                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7729                 }
7730                 if (appData.highlightDragging) {
7731                     SetHighlights(fromX, fromY, -1, -1);
7732                 } else {
7733                     ClearHighlights();
7734                 }
7735             } else fromX = fromY = -1;
7736             return;
7737         }
7738     }
7739
7740     /* fromX != -1 */
7741     if (clickType == Press && gameMode != EditPosition) {
7742         ChessSquare fromP;
7743         ChessSquare toP;
7744         int frc;
7745
7746         // ignore off-board to clicks
7747         if(y < 0 || x < 0) return;
7748
7749         /* Check if clicking again on the same color piece */
7750         fromP = boards[currentMove][fromY][fromX];
7751         toP = boards[currentMove][y][x];
7752         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7753         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7754             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7755            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7756              WhitePawn <= toP && toP <= WhiteKing &&
7757              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7758              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7759             (BlackPawn <= fromP && fromP <= BlackKing &&
7760              BlackPawn <= toP && toP <= BlackKing &&
7761              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7762              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7763             /* Clicked again on same color piece -- changed his mind */
7764             second = (x == fromX && y == fromY);
7765             killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7766             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7767                 second = FALSE; // first double-click rather than scond click
7768                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7769             }
7770             promoDefaultAltered = FALSE;
7771            if(!second) MarkTargetSquares(1);
7772            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7773             if (appData.highlightDragging) {
7774                 SetHighlights(x, y, -1, -1);
7775             } else {
7776                 ClearHighlights();
7777             }
7778             if (OKToStartUserMove(x, y)) {
7779                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7780                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7781                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7782                  gatingPiece = boards[currentMove][fromY][fromX];
7783                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7784                 fromX = x;
7785                 fromY = y; dragging = 1;
7786                 if(!second) ReportClick("lift", x, y);
7787                 MarkTargetSquares(0);
7788                 DragPieceBegin(xPix, yPix, FALSE);
7789                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7790                     promoSweep = defaultPromoChoice;
7791                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7792                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7793                 }
7794             }
7795            }
7796            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7797            second = FALSE;
7798         }
7799         // ignore clicks on holdings
7800         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7801     }
7802
7803     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7804         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7805         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7806         return;
7807     }
7808
7809     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7810         DragPieceEnd(xPix, yPix); dragging = 0;
7811         if(clearFlag) {
7812             // a deferred attempt to click-click move an empty square on top of a piece
7813             boards[currentMove][y][x] = EmptySquare;
7814             ClearHighlights();
7815             DrawPosition(FALSE, boards[currentMove]);
7816             fromX = fromY = -1; clearFlag = 0;
7817             return;
7818         }
7819         if (appData.animateDragging) {
7820             /* Undo animation damage if any */
7821             DrawPosition(FALSE, NULL);
7822         }
7823         if (second) {
7824             /* Second up/down in same square; just abort move */
7825             second = 0;
7826             fromX = fromY = -1;
7827             gatingPiece = EmptySquare;
7828             ClearHighlights();
7829             gotPremove = 0;
7830             ClearPremoveHighlights();
7831             MarkTargetSquares(-1);
7832             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7833         } else {
7834             /* First upclick in same square; start click-click mode */
7835             SetHighlights(x, y, -1, -1);
7836         }
7837         return;
7838     }
7839
7840     clearFlag = 0;
7841
7842     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7843        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7844         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7845         DisplayMessage(_("only marked squares are legal"),"");
7846         DrawPosition(TRUE, NULL);
7847         return; // ignore to-click
7848     }
7849
7850     /* we now have a different from- and (possibly off-board) to-square */
7851     /* Completed move */
7852     if(!sweepSelecting) {
7853         toX = x;
7854         toY = y;
7855     }
7856
7857     piece = boards[currentMove][fromY][fromX];
7858
7859     saveAnimate = appData.animate;
7860     if (clickType == Press) {
7861         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7862         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7863             // must be Edit Position mode with empty-square selected
7864             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7865             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7866             return;
7867         }
7868         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7869             return;
7870         }
7871         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7872             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7873         } else
7874         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7875         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7876           if(appData.sweepSelect) {
7877             promoSweep = defaultPromoChoice;
7878             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7879             selectFlag = 0; lastX = xPix; lastY = yPix;
7880             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7881             saveFlash = appData.flashCount; appData.flashCount = 0;
7882             Sweep(0); // Pawn that is going to promote: preview promotion piece
7883             sweepSelecting = 1;
7884             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7885             MarkTargetSquares(1);
7886           }
7887           return; // promo popup appears on up-click
7888         }
7889         /* Finish clickclick move */
7890         if (appData.animate || appData.highlightLastMove) {
7891             SetHighlights(fromX, fromY, toX, toY);
7892         } else {
7893             ClearHighlights();
7894         }
7895         MarkTargetSquares(1);
7896     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7897         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7898         *promoRestrict = 0; appData.flashCount = saveFlash;
7899         if (appData.animate || appData.highlightLastMove) {
7900             SetHighlights(fromX, fromY, toX, toY);
7901         } else {
7902             ClearHighlights();
7903         }
7904         MarkTargetSquares(1);
7905     } else {
7906 #if 0
7907 // [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
7908         /* Finish drag move */
7909         if (appData.highlightLastMove) {
7910             SetHighlights(fromX, fromY, toX, toY);
7911         } else {
7912             ClearHighlights();
7913         }
7914 #endif
7915         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7916           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7917         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7918         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7919           dragging *= 2;            // flag button-less dragging if we are dragging
7920           MarkTargetSquares(1);
7921           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7922           else {
7923             kill2X = killX; kill2Y = killY;
7924             killX = x; killY = y;     // remember this square as intermediate
7925             ReportClick("put", x, y); // and inform engine
7926             ReportClick("lift", x, y);
7927             MarkTargetSquares(0);
7928             return;
7929           }
7930         }
7931         DragPieceEnd(xPix, yPix); dragging = 0;
7932         /* Don't animate move and drag both */
7933         appData.animate = FALSE;
7934         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7935     }
7936
7937     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7938     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7939         ChessSquare piece = boards[currentMove][fromY][fromX];
7940         if(gameMode == EditPosition && piece != EmptySquare &&
7941            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7942             int n;
7943
7944             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7945                 n = PieceToNumber(piece - (int)BlackPawn);
7946                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7947                 boards[currentMove][handSize-1 - n][0] = piece;
7948                 boards[currentMove][handSize-1 - n][1]++;
7949             } else
7950             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7951                 n = PieceToNumber(piece);
7952                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7953                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7954                 boards[currentMove][n][BOARD_WIDTH-2]++;
7955             }
7956             boards[currentMove][fromY][fromX] = EmptySquare;
7957         }
7958         ClearHighlights();
7959         fromX = fromY = -1;
7960         MarkTargetSquares(1);
7961         DrawPosition(TRUE, boards[currentMove]);
7962         return;
7963     }
7964
7965     // off-board moves should not be highlighted
7966     if(x < 0 || y < 0) {
7967         ClearHighlights();
7968         DrawPosition(FALSE, NULL);
7969     } else ReportClick("put", x, y);
7970
7971     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7972  }
7973
7974     if(legal[toY][toX] == 2) { // highlight-induced promotion
7975         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7976         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7977     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7978       if(!*promoRestrict) {           // but has not done that yet
7979         deferChoice = TRUE;           // set up retry for when it does
7980         return;                       // and wait for that
7981       }
7982       promoChoice = ToLower(*promoRestrict); // force engine's choice
7983       deferChoice = FALSE;
7984     }
7985
7986     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7987         SetHighlights(fromX, fromY, toX, toY);
7988         MarkTargetSquares(1);
7989         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7990             // [HGM] super: promotion to captured piece selected from holdings
7991             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7992             promotionChoice = TRUE;
7993             // kludge follows to temporarily execute move on display, without promoting yet
7994             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7995             boards[currentMove][toY][toX] = p;
7996             DrawPosition(FALSE, boards[currentMove]);
7997             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7998             boards[currentMove][toY][toX] = q;
7999             DisplayMessage("Click in holdings to choose piece", "");
8000             return;
8001         }
8002         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
8003         PromotionPopUp(promoChoice);
8004     } else {
8005         int oldMove = currentMove;
8006         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
8007         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
8008         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
8009         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
8010         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
8011            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
8012             DrawPosition(TRUE, boards[currentMove]);
8013         else DrawPosition(FALSE, NULL);
8014         fromX = fromY = -1;
8015         flashing = 0;
8016     }
8017     appData.animate = saveAnimate;
8018     if (appData.animate || appData.animateDragging) {
8019         /* Undo animation damage if needed */
8020 //      DrawPosition(FALSE, NULL);
8021     }
8022 }
8023
8024 int
8025 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
8026 {   // front-end-free part taken out of PieceMenuPopup
8027     int whichMenu; int xSqr, ySqr;
8028
8029     if(seekGraphUp) { // [HGM] seekgraph
8030         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
8031         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
8032         return -2;
8033     }
8034
8035     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
8036          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
8037         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
8038         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
8039         if(action == Press)   {
8040             originalFlip = flipView;
8041             flipView = !flipView; // temporarily flip board to see game from partners perspective
8042             DrawPosition(TRUE, partnerBoard);
8043             DisplayMessage(partnerStatus, "");
8044             partnerUp = TRUE;
8045         } else if(action == Release) {
8046             flipView = originalFlip;
8047             DrawPosition(TRUE, boards[currentMove]);
8048             partnerUp = FALSE;
8049         }
8050         return -2;
8051     }
8052
8053     xSqr = EventToSquare(x, BOARD_WIDTH);
8054     ySqr = EventToSquare(y, BOARD_HEIGHT);
8055     if (action == Release) {
8056         if(pieceSweep != EmptySquare) {
8057             EditPositionMenuEvent(pieceSweep, toX, toY);
8058             pieceSweep = EmptySquare;
8059         } else UnLoadPV(); // [HGM] pv
8060     }
8061     if (action != Press) return -2; // return code to be ignored
8062     switch (gameMode) {
8063       case IcsExamining:
8064         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8065       case EditPosition:
8066         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8067         if (xSqr < 0 || ySqr < 0) return -1;
8068         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8069         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8070         if(xSqr == createX && ySqr == createY && xSqr != BOARD_LEFT-2 && xSqr != BOARD_RGHT+1) {
8071             ChessSquare p = boards[currentMove][ySqr][xSqr];
8072             do { if(++p == EmptySquare) p = WhitePawn; } while(PieceToChar(p) == '.');
8073             boards[currentMove][ySqr][xSqr] = p; DrawPosition(FALSE, boards[currentMove]);
8074             return -2;
8075         }
8076         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8077         createX = toX = xSqr; createY = toY = ySqr; lastX = x, lastY = y;
8078         NextPiece(0);
8079         return 2; // grab
8080       case IcsObserving:
8081         if(!appData.icsEngineAnalyze) return -1;
8082       case IcsPlayingWhite:
8083       case IcsPlayingBlack:
8084         if(!appData.zippyPlay) goto noZip;
8085       case AnalyzeMode:
8086       case AnalyzeFile:
8087       case MachinePlaysWhite:
8088       case MachinePlaysBlack:
8089       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8090         if (!appData.dropMenu) {
8091           LoadPV(x, y);
8092           return 2; // flag front-end to grab mouse events
8093         }
8094         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8095            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8096       case EditGame:
8097       noZip:
8098         if (xSqr < 0 || ySqr < 0) return -1;
8099         if (!appData.dropMenu || appData.testLegality &&
8100             gameInfo.variant != VariantBughouse &&
8101             gameInfo.variant != VariantCrazyhouse) return -1;
8102         whichMenu = 1; // drop menu
8103         break;
8104       default:
8105         return -1;
8106     }
8107
8108     if (((*fromX = xSqr) < 0) ||
8109         ((*fromY = ySqr) < 0)) {
8110         *fromX = *fromY = -1;
8111         return -1;
8112     }
8113     if (flipView)
8114       *fromX = BOARD_WIDTH - 1 - *fromX;
8115     else
8116       *fromY = BOARD_HEIGHT - 1 - *fromY;
8117
8118     return whichMenu;
8119 }
8120
8121 void
8122 Wheel (int dir, int x, int y)
8123 {
8124     if(gameMode == EditPosition) {
8125         int xSqr = EventToSquare(x, BOARD_WIDTH);
8126         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8127         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8128         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8129         do {
8130             boards[currentMove][ySqr][xSqr] += dir;
8131             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8132             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8133         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8134         DrawPosition(FALSE, boards[currentMove]);
8135     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8136 }
8137
8138 void
8139 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8140 {
8141 //    char * hint = lastHint;
8142     FrontEndProgramStats stats;
8143
8144     stats.which = cps == &first ? 0 : 1;
8145     stats.depth = cpstats->depth;
8146     stats.nodes = cpstats->nodes;
8147     stats.score = cpstats->score;
8148     stats.time = cpstats->time;
8149     stats.pv = cpstats->movelist;
8150     stats.hint = lastHint;
8151     stats.an_move_index = 0;
8152     stats.an_move_count = 0;
8153
8154     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8155         stats.hint = cpstats->move_name;
8156         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8157         stats.an_move_count = cpstats->nr_moves;
8158     }
8159
8160     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
8161
8162     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8163         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8164
8165     SetProgramStats( &stats );
8166 }
8167
8168 void
8169 ClearEngineOutputPane (int which)
8170 {
8171     static FrontEndProgramStats dummyStats;
8172     dummyStats.which = which;
8173     dummyStats.pv = "#";
8174     SetProgramStats( &dummyStats );
8175 }
8176
8177 #define MAXPLAYERS 500
8178
8179 char *
8180 TourneyStandings (int display)
8181 {
8182     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8183     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8184     char result, *p, *names[MAXPLAYERS];
8185
8186     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8187         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8188     names[0] = p = strdup(appData.participants);
8189     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8190
8191     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8192
8193     while(result = appData.results[nr]) {
8194         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8195         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8196         wScore = bScore = 0;
8197         switch(result) {
8198           case '+': wScore = 2; break;
8199           case '-': bScore = 2; break;
8200           case '=': wScore = bScore = 1; break;
8201           case ' ':
8202           case '*': return strdup("busy"); // tourney not finished
8203         }
8204         score[w] += wScore;
8205         score[b] += bScore;
8206         games[w]++;
8207         games[b]++;
8208         nr++;
8209     }
8210     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8211     for(w=0; w<nPlayers; w++) {
8212         bScore = -1;
8213         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8214         ranking[w] = b; points[w] = bScore; score[b] = -2;
8215     }
8216     p = malloc(nPlayers*34+1);
8217     for(w=0; w<nPlayers && w<display; w++)
8218         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8219     free(names[0]);
8220     return p;
8221 }
8222
8223 void
8224 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8225 {       // count all piece types
8226         int p, f, r;
8227         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8228         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8229         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8230                 p = board[r][f];
8231                 pCnt[p]++;
8232                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8233                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8234                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8235                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8236                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8237                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8238         }
8239 }
8240
8241 int
8242 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8243 {
8244         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8245         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8246
8247         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8248         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8249         if(myPawns == 2 && nMine == 3) // KPP
8250             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8251         if(myPawns == 1 && nMine == 2) // KP
8252             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8253         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8254             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8255         if(myPawns) return FALSE;
8256         if(pCnt[WhiteRook+side])
8257             return pCnt[BlackRook-side] ||
8258                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8259                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8260                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8261         if(pCnt[WhiteCannon+side]) {
8262             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8263             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8264         }
8265         if(pCnt[WhiteKnight+side])
8266             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8267         return FALSE;
8268 }
8269
8270 int
8271 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8272 {
8273         VariantClass v = gameInfo.variant;
8274
8275         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8276         if(v == VariantShatranj) return TRUE; // always winnable through baring
8277         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8278         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8279
8280         if(v == VariantXiangqi) {
8281                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8282
8283                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8284                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8285                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8286                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8287                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8288                 if(stale) // we have at least one last-rank P plus perhaps C
8289                     return majors // KPKX
8290                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8291                 else // KCA*E*
8292                     return pCnt[WhiteFerz+side] // KCAK
8293                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8294                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8295                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8296
8297         } else if(v == VariantKnightmate) {
8298                 if(nMine == 1) return FALSE;
8299                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8300         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8301                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8302
8303                 if(nMine == 1) return FALSE; // bare King
8304                 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
8305                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8306                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8307                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8308                 if(pCnt[WhiteKnight+side])
8309                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8310                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8311                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8312                 if(nBishops)
8313                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8314                 if(pCnt[WhiteAlfil+side])
8315                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8316                 if(pCnt[WhiteWazir+side])
8317                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8318         }
8319
8320         return TRUE;
8321 }
8322
8323 int
8324 CompareWithRights (Board b1, Board b2)
8325 {
8326     int rights = 0;
8327     if(!CompareBoards(b1, b2)) return FALSE;
8328     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8329     /* compare castling rights */
8330     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8331            rights++; /* King lost rights, while rook still had them */
8332     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8333         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8334            rights++; /* but at least one rook lost them */
8335     }
8336     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8337            rights++;
8338     if( b1[CASTLING][5] != NoRights ) {
8339         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8340            rights++;
8341     }
8342     return rights == 0;
8343 }
8344
8345 int
8346 Adjudicate (ChessProgramState *cps)
8347 {       // [HGM] some adjudications useful with buggy engines
8348         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8349         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8350         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8351         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8352         int k, drop, count = 0; static int bare = 1;
8353         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8354         Boolean canAdjudicate = !appData.icsActive;
8355
8356         // most tests only when we understand the game, i.e. legality-checking on
8357             if( appData.testLegality )
8358             {   /* [HGM] Some more adjudications for obstinate engines */
8359                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8360                 static int moveCount = 6;
8361                 ChessMove result;
8362                 char *reason = NULL;
8363
8364                 /* Count what is on board. */
8365                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8366
8367                 /* Some material-based adjudications that have to be made before stalemate test */
8368                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8369                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8370                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8371                      if(canAdjudicate && appData.checkMates) {
8372                          if(engineOpponent)
8373                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8374                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8375                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8376                          return 1;
8377                      }
8378                 }
8379
8380                 /* Bare King in Shatranj (loses) or Losers (wins) */
8381                 if( nrW == 1 || nrB == 1) {
8382                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8383                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8384                      if(canAdjudicate && appData.checkMates) {
8385                          if(engineOpponent)
8386                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8387                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8388                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8389                          return 1;
8390                      }
8391                   } else
8392                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8393                   {    /* bare King */
8394                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8395                         if(canAdjudicate && appData.checkMates) {
8396                             /* but only adjudicate if adjudication enabled */
8397                             if(engineOpponent)
8398                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8399                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8400                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8401                             return 1;
8402                         }
8403                   }
8404                 } else bare = 1;
8405
8406
8407             // don't wait for engine to announce game end if we can judge ourselves
8408             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8409               case MT_CHECK:
8410                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8411                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8412                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8413                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8414                             checkCnt++;
8415                         if(checkCnt >= 2) {
8416                             reason = "Xboard adjudication: 3rd check";
8417                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8418                             break;
8419                         }
8420                     }
8421                 }
8422               case MT_NONE:
8423               default:
8424                 break;
8425               case MT_STEALMATE:
8426               case MT_STALEMATE:
8427               case MT_STAINMATE:
8428                 reason = "Xboard adjudication: Stalemate";
8429                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8430                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8431                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8432                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8433                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8434                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8435                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8436                                                                         EP_CHECKMATE : EP_WINS);
8437                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8438                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8439                 }
8440                 break;
8441               case MT_CHECKMATE:
8442                 reason = "Xboard adjudication: Checkmate";
8443                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8444                 if(gameInfo.variant == VariantShogi) {
8445                     if(forwardMostMove > backwardMostMove
8446                        && moveList[forwardMostMove-1][1] == '@'
8447                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8448                         reason = "XBoard adjudication: pawn-drop mate";
8449                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8450                     }
8451                 }
8452                 break;
8453             }
8454
8455                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8456                     case EP_STALEMATE:
8457                         result = GameIsDrawn; break;
8458                     case EP_CHECKMATE:
8459                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8460                     case EP_WINS:
8461                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8462                     default:
8463                         result = EndOfFile;
8464                 }
8465                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8466                     if(engineOpponent)
8467                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8468                     GameEnds( result, reason, GE_XBOARD );
8469                     return 1;
8470                 }
8471
8472                 /* Next absolutely insufficient mating material. */
8473                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8474                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8475                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8476
8477                      /* always flag draws, for judging claims */
8478                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8479
8480                      if(canAdjudicate && appData.materialDraws) {
8481                          /* but only adjudicate them if adjudication enabled */
8482                          if(engineOpponent) {
8483                            SendToProgram("force\n", engineOpponent); // suppress reply
8484                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8485                          }
8486                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8487                          return 1;
8488                      }
8489                 }
8490
8491                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8492                 if(gameInfo.variant == VariantXiangqi ?
8493                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8494                  : nrW + nrB == 4 &&
8495                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8496                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8497                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8498                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8499                    ) ) {
8500                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8501                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8502                           if(engineOpponent) {
8503                             SendToProgram("force\n", engineOpponent); // suppress reply
8504                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8505                           }
8506                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8507                           return 1;
8508                      }
8509                 } else moveCount = 6;
8510
8511                 if(gameInfo.variant == VariantMakruk && // Makruk counting rules
8512                   (nrW == 1 || nrB == 1 || nr[WhitePawn] + nr[BlackPawn] == 0)) { // which only kick in when pawnless or bare King
8513                     int maxcnt, his, mine, c, wom = WhiteOnMove(forwardMostMove);
8514                     count = forwardMostMove;
8515                     while(count >= backwardMostMove) {
8516                         int np = nr[WhitePawn] + nr[BlackPawn];
8517                         if(wom) mine = nrW, his = nrB, c = BlackPawn;
8518                         else    mine = nrB, his = nrW, c = WhitePawn;
8519                         if(mine > 1 && np) { count++; break; }
8520                         if(mine > 1) maxcnt = 64; else
8521                         maxcnt = (nr[WhiteRook+c] > 1 ? 8 : nr[WhiteRook+c] ? 16 : nr[WhiteMan+c] > 1 ? 22 :
8522                                                             nr[WhiteKnight+c] > 1 ? 32 : nr[WhiteMan+c] ? 44 : 64) - his - 1;
8523                         while(boards[count][EP_STATUS] != EP_CAPTURE && count > backwardMostMove) count--; // seek previous character
8524                         if(count == backwardMostMove) break;
8525                         if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) break;
8526                         Count(boards[--count], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8527                     }
8528                     if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) {
8529                         boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8530                         if(canAdjudicate && appData.ruleMoves >= 0) {
8531                             GameEnds( GameIsDrawn, "Xboard adjudication: counting rule", GE_XBOARD );
8532                             return 1;
8533                         }
8534                     }
8535                 }
8536             }
8537
8538         // Repetition draws and 50-move rule can be applied independently of legality testing
8539
8540                 /* Check for rep-draws */
8541                 count = 0;
8542                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8543                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8544                 for(k = forwardMostMove-2;
8545                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8546                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8547                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8548                     k-=2)
8549                 {   int rights=0;
8550                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8551                         /* compare castling rights */
8552                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8553                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8554                                 rights++; /* King lost rights, while rook still had them */
8555                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8556                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8557                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8558                                    rights++; /* but at least one rook lost them */
8559                         }
8560                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8561                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8562                                 rights++;
8563                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8564                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8565                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8566                                    rights++;
8567                         }
8568                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8569                             && appData.drawRepeats > 1) {
8570                              /* adjudicate after user-specified nr of repeats */
8571                              int result = GameIsDrawn;
8572                              char *details = "XBoard adjudication: repetition draw";
8573                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8574                                 // [HGM] xiangqi: check for forbidden perpetuals
8575                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8576                                 for(m=forwardMostMove; m>k; m-=2) {
8577                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8578                                         ourPerpetual = 0; // the current mover did not always check
8579                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8580                                         hisPerpetual = 0; // the opponent did not always check
8581                                 }
8582                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8583                                                                         ourPerpetual, hisPerpetual);
8584                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8585                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8586                                     details = "Xboard adjudication: perpetual checking";
8587                                 } else
8588                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8589                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8590                                 } else
8591                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8592                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8593                                         result = BlackWins;
8594                                         details = "Xboard adjudication: repetition";
8595                                     }
8596                                 } else // it must be XQ
8597                                 // Now check for perpetual chases
8598                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8599                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8600                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8601                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8602                                         static char resdet[MSG_SIZ];
8603                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8604                                         details = resdet;
8605                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8606                                     } else
8607                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8608                                         break; // Abort repetition-checking loop.
8609                                 }
8610                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8611                              }
8612                              if(engineOpponent) {
8613                                SendToProgram("force\n", engineOpponent); // suppress reply
8614                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8615                              }
8616                              GameEnds( result, details, GE_XBOARD );
8617                              return 1;
8618                         }
8619                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8620                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8621                     }
8622                 }
8623
8624                 /* Now we test for 50-move draws. Determine ply count */
8625                 count = forwardMostMove;
8626                 /* look for last irreversble move */
8627                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8628                     count--;
8629                 /* if we hit starting position, add initial plies */
8630                 if( count == backwardMostMove )
8631                     count -= initialRulePlies;
8632                 count = forwardMostMove - count;
8633                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8634                         // adjust reversible move counter for checks in Xiangqi
8635                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8636                         if(i < backwardMostMove) i = backwardMostMove;
8637                         while(i <= forwardMostMove) {
8638                                 lastCheck = inCheck; // check evasion does not count
8639                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8640                                 if(inCheck || lastCheck) count--; // check does not count
8641                                 i++;
8642                         }
8643                 }
8644                 if( count >= 100)
8645                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8646                          /* this is used to judge if draw claims are legal */
8647                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8648                          if(engineOpponent) {
8649                            SendToProgram("force\n", engineOpponent); // suppress reply
8650                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8651                          }
8652                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8653                          return 1;
8654                 }
8655
8656                 /* if draw offer is pending, treat it as a draw claim
8657                  * when draw condition present, to allow engines a way to
8658                  * claim draws before making their move to avoid a race
8659                  * condition occurring after their move
8660                  */
8661                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8662                          char *p = NULL;
8663                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8664                              p = "Draw claim: 50-move rule";
8665                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8666                              p = "Draw claim: 3-fold repetition";
8667                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8668                              p = "Draw claim: insufficient mating material";
8669                          if( p != NULL && canAdjudicate) {
8670                              if(engineOpponent) {
8671                                SendToProgram("force\n", engineOpponent); // suppress reply
8672                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8673                              }
8674                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8675                              return 1;
8676                          }
8677                 }
8678
8679                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8680                     if(engineOpponent) {
8681                       SendToProgram("force\n", engineOpponent); // suppress reply
8682                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8683                     }
8684                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8685                     return 1;
8686                 }
8687         return 0;
8688 }
8689
8690 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8691 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8692 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8693
8694 static int
8695 BitbaseProbe ()
8696 {
8697     int pieces[10], squares[10], cnt=0, r, f, res;
8698     static int loaded;
8699     static PPROBE_EGBB probeBB;
8700     if(!appData.testLegality) return 10;
8701     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8702     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8703     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8704     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8705         ChessSquare piece = boards[forwardMostMove][r][f];
8706         int black = (piece >= BlackPawn);
8707         int type = piece - black*BlackPawn;
8708         if(piece == EmptySquare) continue;
8709         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8710         if(type == WhiteKing) type = WhiteQueen + 1;
8711         type = egbbCode[type];
8712         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8713         pieces[cnt] = type + black*6;
8714         if(++cnt > 5) return 11;
8715     }
8716     pieces[cnt] = squares[cnt] = 0;
8717     // probe EGBB
8718     if(loaded == 2) return 13; // loading failed before
8719     if(loaded == 0) {
8720         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8721         HMODULE lib;
8722         PLOAD_EGBB loadBB;
8723         loaded = 2; // prepare for failure
8724         if(!path) return 13; // no egbb installed
8725         strncpy(buf, path + 8, MSG_SIZ);
8726         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8727         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8728         lib = LoadLibrary(buf);
8729         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8730         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8731         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8732         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8733         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8734         loaded = 1; // success!
8735     }
8736     res = probeBB(forwardMostMove & 1, pieces, squares);
8737     return res > 0 ? 1 : res < 0 ? -1 : 0;
8738 }
8739
8740 char *
8741 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8742 {   // [HGM] book: this routine intercepts moves to simulate book replies
8743     char *bookHit = NULL;
8744
8745     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8746         char buf[MSG_SIZ];
8747         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8748         SendToProgram(buf, cps);
8749     }
8750     //first determine if the incoming move brings opponent into his book
8751     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8752         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8753     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8754     if(bookHit != NULL && !cps->bookSuspend) {
8755         // make sure opponent is not going to reply after receiving move to book position
8756         SendToProgram("force\n", cps);
8757         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8758     }
8759     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8760     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8761     // now arrange restart after book miss
8762     if(bookHit) {
8763         // after a book hit we never send 'go', and the code after the call to this routine
8764         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8765         char buf[MSG_SIZ], *move = bookHit;
8766         if(cps->useSAN) {
8767             int fromX, fromY, toX, toY;
8768             char promoChar;
8769             ChessMove moveType;
8770             move = buf + 30;
8771             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8772                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8773                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8774                                     PosFlags(forwardMostMove),
8775                                     fromY, fromX, toY, toX, promoChar, move);
8776             } else {
8777                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8778                 bookHit = NULL;
8779             }
8780         }
8781         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8782         SendToProgram(buf, cps);
8783         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8784     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8785         SendToProgram("go\n", cps);
8786         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8787     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8788         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8789             SendToProgram("go\n", cps);
8790         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8791     }
8792     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8793 }
8794
8795 int
8796 LoadError (char *errmess, ChessProgramState *cps)
8797 {   // unloads engine and switches back to -ncp mode if it was first
8798     if(cps->initDone) return FALSE;
8799     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8800     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8801     cps->pr = NoProc;
8802     if(cps == &first) {
8803         appData.noChessProgram = TRUE;
8804         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8805         gameMode = BeginningOfGame; ModeHighlight();
8806         SetNCPMode();
8807     }
8808     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8809     DisplayMessage("", ""); // erase waiting message
8810     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8811     return TRUE;
8812 }
8813
8814 char *savedMessage;
8815 ChessProgramState *savedState;
8816 void
8817 DeferredBookMove (void)
8818 {
8819         if(savedState->lastPing != savedState->lastPong)
8820                     ScheduleDelayedEvent(DeferredBookMove, 10);
8821         else
8822         HandleMachineMove(savedMessage, savedState);
8823 }
8824
8825 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8826 static ChessProgramState *stalledEngine;
8827 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8828
8829 void
8830 HandleMachineMove (char *message, ChessProgramState *cps)
8831 {
8832     static char firstLeg[20], legs;
8833     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8834     char realname[MSG_SIZ];
8835     int fromX, fromY, toX, toY;
8836     ChessMove moveType;
8837     char promoChar, roar;
8838     char *p, *pv=buf1;
8839     int oldError;
8840     char *bookHit;
8841
8842     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8843         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8844         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8845             DisplayError(_("Invalid pairing from pairing engine"), 0);
8846             return;
8847         }
8848         pairingReceived = 1;
8849         NextMatchGame();
8850         return; // Skim the pairing messages here.
8851     }
8852
8853     oldError = cps->userError; cps->userError = 0;
8854
8855 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8856     /*
8857      * Kludge to ignore BEL characters
8858      */
8859     while (*message == '\007') message++;
8860
8861     /*
8862      * [HGM] engine debug message: ignore lines starting with '#' character
8863      */
8864     if(cps->debug && *message == '#') return;
8865
8866     /*
8867      * Look for book output
8868      */
8869     if (cps == &first && bookRequested) {
8870         if (message[0] == '\t' || message[0] == ' ') {
8871             /* Part of the book output is here; append it */
8872             strcat(bookOutput, message);
8873             strcat(bookOutput, "  \n");
8874             return;
8875         } else if (bookOutput[0] != NULLCHAR) {
8876             /* All of book output has arrived; display it */
8877             char *p = bookOutput;
8878             while (*p != NULLCHAR) {
8879                 if (*p == '\t') *p = ' ';
8880                 p++;
8881             }
8882             DisplayInformation(bookOutput);
8883             bookRequested = FALSE;
8884             /* Fall through to parse the current output */
8885         }
8886     }
8887
8888     /*
8889      * Look for machine move.
8890      */
8891     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8892         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8893     {
8894         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8895             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8896             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8897             stalledEngine = cps;
8898             if(appData.ponderNextMove) { // bring opponent out of ponder
8899                 if(gameMode == TwoMachinesPlay) {
8900                     if(cps->other->pause)
8901                         PauseEngine(cps->other);
8902                     else
8903                         SendToProgram("easy\n", cps->other);
8904                 }
8905             }
8906             StopClocks();
8907             return;
8908         }
8909
8910       if(cps->usePing) {
8911
8912         /* This method is only useful on engines that support ping */
8913         if(abortEngineThink) {
8914             if (appData.debugMode) {
8915                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8916             }
8917             SendToProgram("undo\n", cps);
8918             return;
8919         }
8920
8921         if (cps->lastPing != cps->lastPong) {
8922             /* Extra move from before last new; ignore */
8923             if (appData.debugMode) {
8924                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8925             }
8926           return;
8927         }
8928
8929       } else {
8930
8931         int machineWhite = FALSE;
8932
8933         switch (gameMode) {
8934           case BeginningOfGame:
8935             /* Extra move from before last reset; ignore */
8936             if (appData.debugMode) {
8937                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8938             }
8939             return;
8940
8941           case EndOfGame:
8942           case IcsIdle:
8943           default:
8944             /* Extra move after we tried to stop.  The mode test is
8945                not a reliable way of detecting this problem, but it's
8946                the best we can do on engines that don't support ping.
8947             */
8948             if (appData.debugMode) {
8949                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8950                         cps->which, gameMode);
8951             }
8952             SendToProgram("undo\n", cps);
8953             return;
8954
8955           case MachinePlaysWhite:
8956           case IcsPlayingWhite:
8957             machineWhite = TRUE;
8958             break;
8959
8960           case MachinePlaysBlack:
8961           case IcsPlayingBlack:
8962             machineWhite = FALSE;
8963             break;
8964
8965           case TwoMachinesPlay:
8966             machineWhite = (cps->twoMachinesColor[0] == 'w');
8967             break;
8968         }
8969         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8970             if (appData.debugMode) {
8971                 fprintf(debugFP,
8972                         "Ignoring move out of turn by %s, gameMode %d"
8973                         ", forwardMost %d\n",
8974                         cps->which, gameMode, forwardMostMove);
8975             }
8976             return;
8977         }
8978       }
8979
8980         if(cps->alphaRank) AlphaRank(machineMove, 4);
8981
8982         // [HGM] lion: (some very limited) support for Alien protocol
8983         killX = killY = kill2X = kill2Y = -1;
8984         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8985             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8986             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8987             return;
8988         }
8989         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8990             char *q = strchr(p+1, ',');            // second comma?
8991             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8992             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8993             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8994         }
8995         if(firstLeg[0]) { // there was a previous leg;
8996             // only support case where same piece makes two step
8997             char buf[20], *p = machineMove+1, *q = buf+1, f;
8998             safeStrCpy(buf, machineMove, 20);
8999             while(isdigit(*q)) q++; // find start of to-square
9000             safeStrCpy(machineMove, firstLeg, 20);
9001             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
9002             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
9003             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)
9004             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
9005             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
9006             firstLeg[0] = NULLCHAR; legs = 0;
9007         }
9008
9009         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
9010                               &fromX, &fromY, &toX, &toY, &promoChar)) {
9011             /* Machine move could not be parsed; ignore it. */
9012           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
9013                     machineMove, _(cps->which));
9014             DisplayMoveError(buf1);
9015             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
9016                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
9017             if (gameMode == TwoMachinesPlay) {
9018               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9019                        buf1, GE_XBOARD);
9020             }
9021             return;
9022         }
9023
9024         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
9025         /* So we have to redo legality test with true e.p. status here,  */
9026         /* to make sure an illegal e.p. capture does not slip through,   */
9027         /* to cause a forfeit on a justified illegal-move complaint      */
9028         /* of the opponent.                                              */
9029         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
9030            ChessMove moveType;
9031            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
9032                              fromY, fromX, toY, toX, promoChar);
9033             if(moveType == IllegalMove) {
9034               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
9035                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
9036                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9037                            buf1, GE_XBOARD);
9038                 return;
9039            } else if(!appData.fischerCastling)
9040            /* [HGM] Kludge to handle engines that send FRC-style castling
9041               when they shouldn't (like TSCP-Gothic) */
9042            switch(moveType) {
9043              case WhiteASideCastleFR:
9044              case BlackASideCastleFR:
9045                toX+=2;
9046                currentMoveString[2]++;
9047                break;
9048              case WhiteHSideCastleFR:
9049              case BlackHSideCastleFR:
9050                toX--;
9051                currentMoveString[2]--;
9052                break;
9053              default: ; // nothing to do, but suppresses warning of pedantic compilers
9054            }
9055         }
9056         hintRequested = FALSE;
9057         lastHint[0] = NULLCHAR;
9058         bookRequested = FALSE;
9059         /* Program may be pondering now */
9060         cps->maybeThinking = TRUE;
9061         if (cps->sendTime == 2) cps->sendTime = 1;
9062         if (cps->offeredDraw) cps->offeredDraw--;
9063
9064         /* [AS] Save move info*/
9065         pvInfoList[ forwardMostMove ].score = programStats.score;
9066         pvInfoList[ forwardMostMove ].depth = programStats.depth;
9067         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
9068
9069         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
9070
9071         /* Test suites abort the 'game' after one move */
9072         if(*appData.finger) {
9073            static FILE *f;
9074            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
9075            if(!f) f = fopen(appData.finger, "w");
9076            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
9077            else { DisplayFatalError("Bad output file", errno, 0); return; }
9078            free(fen);
9079            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9080         }
9081         if(appData.epd) {
9082            if(solvingTime >= 0) {
9083               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
9084               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
9085            } else {
9086               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
9087               if(solvingTime == -2) second.matchWins++;
9088            }
9089            OutputKibitz(2, buf1);
9090            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9091         }
9092
9093         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9094         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9095             int count = 0;
9096
9097             while( count < adjudicateLossPlies ) {
9098                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9099
9100                 if( count & 1 ) {
9101                     score = -score; /* Flip score for winning side */
9102                 }
9103
9104                 if( score > appData.adjudicateLossThreshold ) {
9105                     break;
9106                 }
9107
9108                 count++;
9109             }
9110
9111             if( count >= adjudicateLossPlies ) {
9112                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9113
9114                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9115                     "Xboard adjudication",
9116                     GE_XBOARD );
9117
9118                 return;
9119             }
9120         }
9121
9122         if(Adjudicate(cps)) {
9123             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9124             return; // [HGM] adjudicate: for all automatic game ends
9125         }
9126
9127 #if ZIPPY
9128         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9129             first.initDone) {
9130           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9131                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9132                 SendToICS("draw ");
9133                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9134           }
9135           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9136           ics_user_moved = 1;
9137           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9138                 char buf[3*MSG_SIZ];
9139
9140                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9141                         programStats.score / 100.,
9142                         programStats.depth,
9143                         programStats.time / 100.,
9144                         (unsigned int)programStats.nodes,
9145                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9146                         programStats.movelist);
9147                 SendToICS(buf);
9148           }
9149         }
9150 #endif
9151
9152         /* [AS] Clear stats for next move */
9153         ClearProgramStats();
9154         thinkOutput[0] = NULLCHAR;
9155         hiddenThinkOutputState = 0;
9156
9157         bookHit = NULL;
9158         if (gameMode == TwoMachinesPlay) {
9159             /* [HGM] relaying draw offers moved to after reception of move */
9160             /* and interpreting offer as claim if it brings draw condition */
9161             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9162                 SendToProgram("draw\n", cps->other);
9163             }
9164             if (cps->other->sendTime) {
9165                 SendTimeRemaining(cps->other,
9166                                   cps->other->twoMachinesColor[0] == 'w');
9167             }
9168             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9169             if (firstMove && !bookHit) {
9170                 firstMove = FALSE;
9171                 if (cps->other->useColors) {
9172                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9173                 }
9174                 SendToProgram("go\n", cps->other);
9175             }
9176             cps->other->maybeThinking = TRUE;
9177         }
9178
9179         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9180
9181         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9182
9183         if (!pausing && appData.ringBellAfterMoves) {
9184             if(!roar) RingBell();
9185         }
9186
9187         /*
9188          * Reenable menu items that were disabled while
9189          * machine was thinking
9190          */
9191         if (gameMode != TwoMachinesPlay)
9192             SetUserThinkingEnables();
9193
9194         // [HGM] book: after book hit opponent has received move and is now in force mode
9195         // force the book reply into it, and then fake that it outputted this move by jumping
9196         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9197         if(bookHit) {
9198                 static char bookMove[MSG_SIZ]; // a bit generous?
9199
9200                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9201                 strcat(bookMove, bookHit);
9202                 message = bookMove;
9203                 cps = cps->other;
9204                 programStats.nodes = programStats.depth = programStats.time =
9205                 programStats.score = programStats.got_only_move = 0;
9206                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9207
9208                 if(cps->lastPing != cps->lastPong) {
9209                     savedMessage = message; // args for deferred call
9210                     savedState = cps;
9211                     ScheduleDelayedEvent(DeferredBookMove, 10);
9212                     return;
9213                 }
9214                 goto FakeBookMove;
9215         }
9216
9217         return;
9218     }
9219
9220     /* Set special modes for chess engines.  Later something general
9221      *  could be added here; for now there is just one kludge feature,
9222      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9223      *  when "xboard" is given as an interactive command.
9224      */
9225     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9226         cps->useSigint = FALSE;
9227         cps->useSigterm = FALSE;
9228     }
9229     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9230       ParseFeatures(message+8, cps);
9231       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9232     }
9233
9234     if (!strncmp(message, "setup ", 6) && 
9235         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9236           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9237                                         ) { // [HGM] allow first engine to define opening position
9238       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9239       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9240       *buf = NULLCHAR;
9241       if(sscanf(message, "setup (%s", buf) == 1) {
9242         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9243         ASSIGN(appData.pieceToCharTable, buf);
9244       }
9245       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9246       if(dummy >= 3) {
9247         while(message[s] && message[s++] != ' ');
9248         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9249            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9250 //          if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9251             if(hand > h) handSize = hand; else handSize = h;
9252             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9253             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9254           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9255           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9256           startedFromSetupPosition = FALSE;
9257         }
9258       }
9259       if(startedFromSetupPosition) return;
9260       ParseFEN(boards[0], &dummy, message+s, FALSE);
9261       DrawPosition(TRUE, boards[0]);
9262       CopyBoard(initialPosition, boards[0]);
9263       startedFromSetupPosition = TRUE;
9264       return;
9265     }
9266     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9267       ChessSquare piece = WhitePawn;
9268       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9269       if(*p == '+') promoted++, ID = *++p;
9270       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9271       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9272       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9273       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9274       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9275       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9276       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9277       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9278                                                && gameInfo.variant != VariantGreat
9279                                                && gameInfo.variant != VariantFairy    ) return;
9280       if(piece < EmptySquare) {
9281         pieceDefs = TRUE;
9282         ASSIGN(pieceDesc[piece], buf1);
9283         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9284       }
9285       return;
9286     }
9287     if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9288       if(deferChoice) {
9289         LeftClick(Press, 0, 0); // finish the click that was interrupted
9290       } else if(promoSweep != EmptySquare) {
9291         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9292         if(strlen(promoRestrict) > 1) Sweep(0);
9293       }
9294       return;
9295     }
9296     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9297      * want this, I was asked to put it in, and obliged.
9298      */
9299     if (!strncmp(message, "setboard ", 9)) {
9300         Board initial_position;
9301
9302         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9303
9304         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9305             DisplayError(_("Bad FEN received from engine"), 0);
9306             return ;
9307         } else {
9308            Reset(TRUE, FALSE);
9309            CopyBoard(boards[0], initial_position);
9310            initialRulePlies = FENrulePlies;
9311            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9312            else gameMode = MachinePlaysBlack;
9313            DrawPosition(FALSE, boards[currentMove]);
9314         }
9315         return;
9316     }
9317
9318     /*
9319      * Look for communication commands
9320      */
9321     if (!strncmp(message, "telluser ", 9)) {
9322         if(message[9] == '\\' && message[10] == '\\')
9323             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9324         PlayTellSound();
9325         DisplayNote(message + 9);
9326         return;
9327     }
9328     if (!strncmp(message, "tellusererror ", 14)) {
9329         cps->userError = 1;
9330         if(message[14] == '\\' && message[15] == '\\')
9331             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9332         PlayTellSound();
9333         DisplayError(message + 14, 0);
9334         return;
9335     }
9336     if (!strncmp(message, "tellopponent ", 13)) {
9337       if (appData.icsActive) {
9338         if (loggedOn) {
9339           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9340           SendToICS(buf1);
9341         }
9342       } else {
9343         DisplayNote(message + 13);
9344       }
9345       return;
9346     }
9347     if (!strncmp(message, "tellothers ", 11)) {
9348       if (appData.icsActive) {
9349         if (loggedOn) {
9350           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9351           SendToICS(buf1);
9352         }
9353       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9354       return;
9355     }
9356     if (!strncmp(message, "tellall ", 8)) {
9357       if (appData.icsActive) {
9358         if (loggedOn) {
9359           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9360           SendToICS(buf1);
9361         }
9362       } else {
9363         DisplayNote(message + 8);
9364       }
9365       return;
9366     }
9367     if (strncmp(message, "warning", 7) == 0) {
9368         /* Undocumented feature, use tellusererror in new code */
9369         DisplayError(message, 0);
9370         return;
9371     }
9372     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9373         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9374         strcat(realname, " query");
9375         AskQuestion(realname, buf2, buf1, cps->pr);
9376         return;
9377     }
9378     /* Commands from the engine directly to ICS.  We don't allow these to be
9379      *  sent until we are logged on. Crafty kibitzes have been known to
9380      *  interfere with the login process.
9381      */
9382     if (loggedOn) {
9383         if (!strncmp(message, "tellics ", 8)) {
9384             SendToICS(message + 8);
9385             SendToICS("\n");
9386             return;
9387         }
9388         if (!strncmp(message, "tellicsnoalias ", 15)) {
9389             SendToICS(ics_prefix);
9390             SendToICS(message + 15);
9391             SendToICS("\n");
9392             return;
9393         }
9394         /* The following are for backward compatibility only */
9395         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9396             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9397             SendToICS(ics_prefix);
9398             SendToICS(message);
9399             SendToICS("\n");
9400             return;
9401         }
9402     }
9403     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9404         if(initPing == cps->lastPong) {
9405             if(gameInfo.variant == VariantUnknown) {
9406                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9407                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9408                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9409             }
9410             initPing = -1;
9411         }
9412         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9413             abortEngineThink = FALSE;
9414             DisplayMessage("", "");
9415             ThawUI();
9416         }
9417         return;
9418     }
9419     if(!strncmp(message, "highlight ", 10)) {
9420         if(appData.testLegality && !*engineVariant && appData.markers) return;
9421         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9422         return;
9423     }
9424     if(!strncmp(message, "click ", 6)) {
9425         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9426         if(appData.testLegality || !appData.oneClick) return;
9427         sscanf(message+6, "%c%d%c", &f, &y, &c);
9428         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9429         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9430         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9431         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9432         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9433         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9434             LeftClick(Release, lastLeftX, lastLeftY);
9435         controlKey  = (c == ',');
9436         LeftClick(Press, x, y);
9437         LeftClick(Release, x, y);
9438         first.highlight = f;
9439         return;
9440     }
9441     /*
9442      * If the move is illegal, cancel it and redraw the board.
9443      * Also deal with other error cases.  Matching is rather loose
9444      * here to accommodate engines written before the spec.
9445      */
9446     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9447         strncmp(message, "Error", 5) == 0) {
9448         if (StrStr(message, "name") ||
9449             StrStr(message, "rating") || StrStr(message, "?") ||
9450             StrStr(message, "result") || StrStr(message, "board") ||
9451             StrStr(message, "bk") || StrStr(message, "computer") ||
9452             StrStr(message, "variant") || StrStr(message, "hint") ||
9453             StrStr(message, "random") || StrStr(message, "depth") ||
9454             StrStr(message, "accepted")) {
9455             return;
9456         }
9457         if (StrStr(message, "protover")) {
9458           /* Program is responding to input, so it's apparently done
9459              initializing, and this error message indicates it is
9460              protocol version 1.  So we don't need to wait any longer
9461              for it to initialize and send feature commands. */
9462           FeatureDone(cps, 1);
9463           cps->protocolVersion = 1;
9464           return;
9465         }
9466         cps->maybeThinking = FALSE;
9467
9468         if (StrStr(message, "draw")) {
9469             /* Program doesn't have "draw" command */
9470             cps->sendDrawOffers = 0;
9471             return;
9472         }
9473         if (cps->sendTime != 1 &&
9474             (StrStr(message, "time") || StrStr(message, "otim"))) {
9475           /* Program apparently doesn't have "time" or "otim" command */
9476           cps->sendTime = 0;
9477           return;
9478         }
9479         if (StrStr(message, "analyze")) {
9480             cps->analysisSupport = FALSE;
9481             cps->analyzing = FALSE;
9482 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9483             EditGameEvent(); // [HGM] try to preserve loaded game
9484             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9485             DisplayError(buf2, 0);
9486             return;
9487         }
9488         if (StrStr(message, "(no matching move)st")) {
9489           /* Special kludge for GNU Chess 4 only */
9490           cps->stKludge = TRUE;
9491           SendTimeControl(cps, movesPerSession, timeControl,
9492                           timeIncrement, appData.searchDepth,
9493                           searchTime);
9494           return;
9495         }
9496         if (StrStr(message, "(no matching move)sd")) {
9497           /* Special kludge for GNU Chess 4 only */
9498           cps->sdKludge = TRUE;
9499           SendTimeControl(cps, movesPerSession, timeControl,
9500                           timeIncrement, appData.searchDepth,
9501                           searchTime);
9502           return;
9503         }
9504         if (!StrStr(message, "llegal")) {
9505             return;
9506         }
9507         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9508             gameMode == IcsIdle) return;
9509         if (forwardMostMove <= backwardMostMove) return;
9510         if (pausing) PauseEvent();
9511       if(appData.forceIllegal) {
9512             // [HGM] illegal: machine refused move; force position after move into it
9513           SendToProgram("force\n", cps);
9514           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9515                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9516                 // when black is to move, while there might be nothing on a2 or black
9517                 // might already have the move. So send the board as if white has the move.
9518                 // But first we must change the stm of the engine, as it refused the last move
9519                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9520                 if(WhiteOnMove(forwardMostMove)) {
9521                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9522                     SendBoard(cps, forwardMostMove); // kludgeless board
9523                 } else {
9524                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9525                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9526                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9527                 }
9528           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9529             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9530                  gameMode == TwoMachinesPlay)
9531               SendToProgram("go\n", cps);
9532             return;
9533       } else
9534         if (gameMode == PlayFromGameFile) {
9535             /* Stop reading this game file */
9536             gameMode = EditGame;
9537             ModeHighlight();
9538         }
9539         /* [HGM] illegal-move claim should forfeit game when Xboard */
9540         /* only passes fully legal moves                            */
9541         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9542             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9543                                 "False illegal-move claim", GE_XBOARD );
9544             return; // do not take back move we tested as valid
9545         }
9546         currentMove = forwardMostMove-1;
9547         DisplayMove(currentMove-1); /* before DisplayMoveError */
9548         SwitchClocks(forwardMostMove-1); // [HGM] race
9549         DisplayBothClocks();
9550         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9551                 parseList[currentMove], _(cps->which));
9552         DisplayMoveError(buf1);
9553         DrawPosition(FALSE, boards[currentMove]);
9554
9555         SetUserThinkingEnables();
9556         return;
9557     }
9558     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9559         /* Program has a broken "time" command that
9560            outputs a string not ending in newline.
9561            Don't use it. */
9562         cps->sendTime = 0;
9563     }
9564     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9565         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9566             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9567     }
9568
9569     /*
9570      * If chess program startup fails, exit with an error message.
9571      * Attempts to recover here are futile. [HGM] Well, we try anyway
9572      */
9573     if ((StrStr(message, "unknown host") != NULL)
9574         || (StrStr(message, "No remote directory") != NULL)
9575         || (StrStr(message, "not found") != NULL)
9576         || (StrStr(message, "No such file") != NULL)
9577         || (StrStr(message, "can't alloc") != NULL)
9578         || (StrStr(message, "Permission denied") != NULL)) {
9579
9580         cps->maybeThinking = FALSE;
9581         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9582                 _(cps->which), cps->program, cps->host, message);
9583         RemoveInputSource(cps->isr);
9584         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9585             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9586             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9587         }
9588         return;
9589     }
9590
9591     /*
9592      * Look for hint output
9593      */
9594     if (sscanf(message, "Hint: %s", buf1) == 1) {
9595         if (cps == &first && hintRequested) {
9596             hintRequested = FALSE;
9597             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9598                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9599                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9600                                     PosFlags(forwardMostMove),
9601                                     fromY, fromX, toY, toX, promoChar, buf1);
9602                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9603                 DisplayInformation(buf2);
9604             } else {
9605                 /* Hint move could not be parsed!? */
9606               snprintf(buf2, sizeof(buf2),
9607                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9608                         buf1, _(cps->which));
9609                 DisplayError(buf2, 0);
9610             }
9611         } else {
9612           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9613         }
9614         return;
9615     }
9616
9617     /*
9618      * Ignore other messages if game is not in progress
9619      */
9620     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9621         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9622
9623     /*
9624      * look for win, lose, draw, or draw offer
9625      */
9626     if (strncmp(message, "1-0", 3) == 0) {
9627         char *p, *q, *r = "";
9628         p = strchr(message, '{');
9629         if (p) {
9630             q = strchr(p, '}');
9631             if (q) {
9632                 *q = NULLCHAR;
9633                 r = p + 1;
9634             }
9635         }
9636         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9637         return;
9638     } else if (strncmp(message, "0-1", 3) == 0) {
9639         char *p, *q, *r = "";
9640         p = strchr(message, '{');
9641         if (p) {
9642             q = strchr(p, '}');
9643             if (q) {
9644                 *q = NULLCHAR;
9645                 r = p + 1;
9646             }
9647         }
9648         /* Kludge for Arasan 4.1 bug */
9649         if (strcmp(r, "Black resigns") == 0) {
9650             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9651             return;
9652         }
9653         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9654         return;
9655     } else if (strncmp(message, "1/2", 3) == 0) {
9656         char *p, *q, *r = "";
9657         p = strchr(message, '{');
9658         if (p) {
9659             q = strchr(p, '}');
9660             if (q) {
9661                 *q = NULLCHAR;
9662                 r = p + 1;
9663             }
9664         }
9665
9666         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9667         return;
9668
9669     } else if (strncmp(message, "White resign", 12) == 0) {
9670         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9671         return;
9672     } else if (strncmp(message, "Black resign", 12) == 0) {
9673         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9674         return;
9675     } else if (strncmp(message, "White matches", 13) == 0 ||
9676                strncmp(message, "Black matches", 13) == 0   ) {
9677         /* [HGM] ignore GNUShogi noises */
9678         return;
9679     } else if (strncmp(message, "White", 5) == 0 &&
9680                message[5] != '(' &&
9681                StrStr(message, "Black") == NULL) {
9682         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9683         return;
9684     } else if (strncmp(message, "Black", 5) == 0 &&
9685                message[5] != '(') {
9686         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9687         return;
9688     } else if (strcmp(message, "resign") == 0 ||
9689                strcmp(message, "computer resigns") == 0) {
9690         switch (gameMode) {
9691           case MachinePlaysBlack:
9692           case IcsPlayingBlack:
9693             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9694             break;
9695           case MachinePlaysWhite:
9696           case IcsPlayingWhite:
9697             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9698             break;
9699           case TwoMachinesPlay:
9700             if (cps->twoMachinesColor[0] == 'w')
9701               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9702             else
9703               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9704             break;
9705           default:
9706             /* can't happen */
9707             break;
9708         }
9709         return;
9710     } else if (strncmp(message, "opponent mates", 14) == 0) {
9711         switch (gameMode) {
9712           case MachinePlaysBlack:
9713           case IcsPlayingBlack:
9714             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9715             break;
9716           case MachinePlaysWhite:
9717           case IcsPlayingWhite:
9718             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9719             break;
9720           case TwoMachinesPlay:
9721             if (cps->twoMachinesColor[0] == 'w')
9722               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9723             else
9724               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9725             break;
9726           default:
9727             /* can't happen */
9728             break;
9729         }
9730         return;
9731     } else if (strncmp(message, "computer mates", 14) == 0) {
9732         switch (gameMode) {
9733           case MachinePlaysBlack:
9734           case IcsPlayingBlack:
9735             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9736             break;
9737           case MachinePlaysWhite:
9738           case IcsPlayingWhite:
9739             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9740             break;
9741           case TwoMachinesPlay:
9742             if (cps->twoMachinesColor[0] == 'w')
9743               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9744             else
9745               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9746             break;
9747           default:
9748             /* can't happen */
9749             break;
9750         }
9751         return;
9752     } else if (strncmp(message, "checkmate", 9) == 0) {
9753         if (WhiteOnMove(forwardMostMove)) {
9754             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9755         } else {
9756             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9757         }
9758         return;
9759     } else if (strstr(message, "Draw") != NULL ||
9760                strstr(message, "game is a draw") != NULL) {
9761         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9762         return;
9763     } else if (strstr(message, "offer") != NULL &&
9764                strstr(message, "draw") != NULL) {
9765 #if ZIPPY
9766         if (appData.zippyPlay && first.initDone) {
9767             /* Relay offer to ICS */
9768             SendToICS(ics_prefix);
9769             SendToICS("draw\n");
9770         }
9771 #endif
9772         cps->offeredDraw = 2; /* valid until this engine moves twice */
9773         if (gameMode == TwoMachinesPlay) {
9774             if (cps->other->offeredDraw) {
9775                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9776             /* [HGM] in two-machine mode we delay relaying draw offer      */
9777             /* until after we also have move, to see if it is really claim */
9778             }
9779         } else if (gameMode == MachinePlaysWhite ||
9780                    gameMode == MachinePlaysBlack) {
9781           if (userOfferedDraw) {
9782             DisplayInformation(_("Machine accepts your draw offer"));
9783             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9784           } else {
9785             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9786           }
9787         }
9788     }
9789
9790
9791     /*
9792      * Look for thinking output
9793      */
9794     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9795           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9796                                 ) {
9797         int plylev, mvleft, mvtot, curscore, time;
9798         char mvname[MOVE_LEN];
9799         u64 nodes; // [DM]
9800         char plyext;
9801         int ignore = FALSE;
9802         int prefixHint = FALSE;
9803         mvname[0] = NULLCHAR;
9804
9805         switch (gameMode) {
9806           case MachinePlaysBlack:
9807           case IcsPlayingBlack:
9808             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9809             break;
9810           case MachinePlaysWhite:
9811           case IcsPlayingWhite:
9812             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9813             break;
9814           case AnalyzeMode:
9815           case AnalyzeFile:
9816             break;
9817           case IcsObserving: /* [DM] icsEngineAnalyze */
9818             if (!appData.icsEngineAnalyze) ignore = TRUE;
9819             break;
9820           case TwoMachinesPlay:
9821             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9822                 ignore = TRUE;
9823             }
9824             break;
9825           default:
9826             ignore = TRUE;
9827             break;
9828         }
9829
9830         if (!ignore) {
9831             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9832             int solved = 0;
9833             buf1[0] = NULLCHAR;
9834             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9835                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9836                 char score_buf[MSG_SIZ];
9837
9838                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9839                     nodes += u64Const(0x100000000);
9840
9841                 if (plyext != ' ' && plyext != '\t') {
9842                     time *= 100;
9843                 }
9844
9845                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9846                 if( cps->scoreIsAbsolute &&
9847                     ( gameMode == MachinePlaysBlack ||
9848                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9849                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9850                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9851                      !WhiteOnMove(currentMove)
9852                     ) )
9853                 {
9854                     curscore = -curscore;
9855                 }
9856
9857                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9858
9859                 if(*bestMove) { // rememer time best EPD move was first found
9860                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9861                     ChessMove mt; char *p = bestMove;
9862                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9863                     solved = 0;
9864                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9865                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9866                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9867                             solved = 1;
9868                             break;
9869                         }
9870                         while(*p && *p != ' ') p++;
9871                         while(*p == ' ') p++;
9872                     }
9873                     if(!solved) solvingTime = -1;
9874                 }
9875                 if(*avoidMove && !solved) {
9876                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9877                     ChessMove mt; char *p = avoidMove, solved = 1;
9878                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9879                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9880                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9881                             solved = 0; solvingTime = -2;
9882                             break;
9883                         }
9884                         while(*p && *p != ' ') p++;
9885                         while(*p == ' ') p++;
9886                     }
9887                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9888                 }
9889
9890                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9891                         char buf[MSG_SIZ];
9892                         FILE *f;
9893                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9894                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9895                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9896                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9897                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9898                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9899                                 fclose(f);
9900                         }
9901                         else
9902                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9903                           DisplayError(_("failed writing PV"), 0);
9904                 }
9905
9906                 tempStats.depth = plylev;
9907                 tempStats.nodes = nodes;
9908                 tempStats.time = time;
9909                 tempStats.score = curscore;
9910                 tempStats.got_only_move = 0;
9911
9912                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9913                         int ticklen;
9914
9915                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9916                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9917                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9918                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9919                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9920                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9921                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9922                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9923                 }
9924
9925                 /* Buffer overflow protection */
9926                 if (pv[0] != NULLCHAR) {
9927                     if (strlen(pv) >= sizeof(tempStats.movelist)
9928                         && appData.debugMode) {
9929                         fprintf(debugFP,
9930                                 "PV is too long; using the first %u bytes.\n",
9931                                 (unsigned) sizeof(tempStats.movelist) - 1);
9932                     }
9933
9934                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9935                 } else {
9936                     sprintf(tempStats.movelist, " no PV\n");
9937                 }
9938
9939                 if (tempStats.seen_stat) {
9940                     tempStats.ok_to_send = 1;
9941                 }
9942
9943                 if (strchr(tempStats.movelist, '(') != NULL) {
9944                     tempStats.line_is_book = 1;
9945                     tempStats.nr_moves = 0;
9946                     tempStats.moves_left = 0;
9947                 } else {
9948                     tempStats.line_is_book = 0;
9949                 }
9950
9951                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9952                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9953
9954                 SendProgramStatsToFrontend( cps, &tempStats );
9955
9956                 /*
9957                     [AS] Protect the thinkOutput buffer from overflow... this
9958                     is only useful if buf1 hasn't overflowed first!
9959                 */
9960                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9961                 if(curscore >= MATE_SCORE) 
9962                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9963                 else if(curscore <= -MATE_SCORE) 
9964                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9965                 else
9966                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9967                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9968                          plylev,
9969                          (gameMode == TwoMachinesPlay ?
9970                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9971                          score_buf,
9972                          prefixHint ? lastHint : "",
9973                          prefixHint ? " " : "" );
9974
9975                 if( buf1[0] != NULLCHAR ) {
9976                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9977
9978                     if( strlen(pv) > max_len ) {
9979                         if( appData.debugMode) {
9980                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9981                         }
9982                         pv[max_len+1] = '\0';
9983                     }
9984
9985                     strcat( thinkOutput, pv);
9986                 }
9987
9988                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9989                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9990                     DisplayMove(currentMove - 1);
9991                 }
9992                 return;
9993
9994             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9995                 /* crafty (9.25+) says "(only move) <move>"
9996                  * if there is only 1 legal move
9997                  */
9998                 sscanf(p, "(only move) %s", buf1);
9999                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
10000                 sprintf(programStats.movelist, "%s (only move)", buf1);
10001                 programStats.depth = 1;
10002                 programStats.nr_moves = 1;
10003                 programStats.moves_left = 1;
10004                 programStats.nodes = 1;
10005                 programStats.time = 1;
10006                 programStats.got_only_move = 1;
10007
10008                 /* Not really, but we also use this member to
10009                    mean "line isn't going to change" (Crafty
10010                    isn't searching, so stats won't change) */
10011                 programStats.line_is_book = 1;
10012
10013                 SendProgramStatsToFrontend( cps, &programStats );
10014
10015                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10016                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10017                     DisplayMove(currentMove - 1);
10018                 }
10019                 return;
10020             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
10021                               &time, &nodes, &plylev, &mvleft,
10022                               &mvtot, mvname) >= 5) {
10023                 /* The stat01: line is from Crafty (9.29+) in response
10024                    to the "." command */
10025                 programStats.seen_stat = 1;
10026                 cps->maybeThinking = TRUE;
10027
10028                 if (programStats.got_only_move || !appData.periodicUpdates)
10029                   return;
10030
10031                 programStats.depth = plylev;
10032                 programStats.time = time;
10033                 programStats.nodes = nodes;
10034                 programStats.moves_left = mvleft;
10035                 programStats.nr_moves = mvtot;
10036                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
10037                 programStats.ok_to_send = 1;
10038                 programStats.movelist[0] = '\0';
10039
10040                 SendProgramStatsToFrontend( cps, &programStats );
10041
10042                 return;
10043
10044             } else if (strncmp(message,"++",2) == 0) {
10045                 /* Crafty 9.29+ outputs this */
10046                 programStats.got_fail = 2;
10047                 return;
10048
10049             } else if (strncmp(message,"--",2) == 0) {
10050                 /* Crafty 9.29+ outputs this */
10051                 programStats.got_fail = 1;
10052                 return;
10053
10054             } else if (thinkOutput[0] != NULLCHAR &&
10055                        strncmp(message, "    ", 4) == 0) {
10056                 unsigned message_len;
10057
10058                 p = message;
10059                 while (*p && *p == ' ') p++;
10060
10061                 message_len = strlen( p );
10062
10063                 /* [AS] Avoid buffer overflow */
10064                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
10065                     strcat(thinkOutput, " ");
10066                     strcat(thinkOutput, p);
10067                 }
10068
10069                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
10070                     strcat(programStats.movelist, " ");
10071                     strcat(programStats.movelist, p);
10072                 }
10073
10074                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10075                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10076                     DisplayMove(currentMove - 1);
10077                 }
10078                 return;
10079             }
10080         }
10081         else {
10082             buf1[0] = NULLCHAR;
10083
10084             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
10085                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
10086             {
10087                 ChessProgramStats cpstats;
10088
10089                 if (plyext != ' ' && plyext != '\t') {
10090                     time *= 100;
10091                 }
10092
10093                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10094                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10095                     curscore = -curscore;
10096                 }
10097
10098                 cpstats.depth = plylev;
10099                 cpstats.nodes = nodes;
10100                 cpstats.time = time;
10101                 cpstats.score = curscore;
10102                 cpstats.got_only_move = 0;
10103                 cpstats.movelist[0] = '\0';
10104
10105                 if (buf1[0] != NULLCHAR) {
10106                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10107                 }
10108
10109                 cpstats.ok_to_send = 0;
10110                 cpstats.line_is_book = 0;
10111                 cpstats.nr_moves = 0;
10112                 cpstats.moves_left = 0;
10113
10114                 SendProgramStatsToFrontend( cps, &cpstats );
10115             }
10116         }
10117     }
10118 }
10119
10120
10121 /* Parse a game score from the character string "game", and
10122    record it as the history of the current game.  The game
10123    score is NOT assumed to start from the standard position.
10124    The display is not updated in any way.
10125    */
10126 void
10127 ParseGameHistory (char *game)
10128 {
10129     ChessMove moveType;
10130     int fromX, fromY, toX, toY, boardIndex, mask;
10131     char promoChar;
10132     char *p, *q;
10133     char buf[MSG_SIZ];
10134
10135     if (appData.debugMode)
10136       fprintf(debugFP, "Parsing game history: %s\n", game);
10137
10138     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10139     gameInfo.site = StrSave(appData.icsHost);
10140     gameInfo.date = PGNDate();
10141     gameInfo.round = StrSave("-");
10142
10143     /* Parse out names of players */
10144     while (*game == ' ') game++;
10145     p = buf;
10146     while (*game != ' ') *p++ = *game++;
10147     *p = NULLCHAR;
10148     gameInfo.white = StrSave(buf);
10149     while (*game == ' ') game++;
10150     p = buf;
10151     while (*game != ' ' && *game != '\n') *p++ = *game++;
10152     *p = NULLCHAR;
10153     gameInfo.black = StrSave(buf);
10154
10155     /* Parse moves */
10156     boardIndex = blackPlaysFirst ? 1 : 0;
10157     yynewstr(game);
10158     for (;;) {
10159         yyboardindex = boardIndex;
10160         moveType = (ChessMove) Myylex();
10161         switch (moveType) {
10162           case IllegalMove:             /* maybe suicide chess, etc. */
10163   if (appData.debugMode) {
10164     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10165     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10166     setbuf(debugFP, NULL);
10167   }
10168           case WhitePromotion:
10169           case BlackPromotion:
10170           case WhiteNonPromotion:
10171           case BlackNonPromotion:
10172           case NormalMove:
10173           case FirstLeg:
10174           case WhiteCapturesEnPassant:
10175           case BlackCapturesEnPassant:
10176           case WhiteKingSideCastle:
10177           case WhiteQueenSideCastle:
10178           case BlackKingSideCastle:
10179           case BlackQueenSideCastle:
10180           case WhiteKingSideCastleWild:
10181           case WhiteQueenSideCastleWild:
10182           case BlackKingSideCastleWild:
10183           case BlackQueenSideCastleWild:
10184           /* PUSH Fabien */
10185           case WhiteHSideCastleFR:
10186           case WhiteASideCastleFR:
10187           case BlackHSideCastleFR:
10188           case BlackASideCastleFR:
10189           /* POP Fabien */
10190             fromX = currentMoveString[0] - AAA;
10191             fromY = currentMoveString[1] - ONE;
10192             toX = currentMoveString[2] - AAA;
10193             toY = currentMoveString[3] - ONE;
10194             promoChar = currentMoveString[4];
10195             break;
10196           case WhiteDrop:
10197           case BlackDrop:
10198             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10199             fromX = moveType == WhiteDrop ?
10200               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10201             (int) CharToPiece(ToLower(currentMoveString[0]));
10202             fromY = DROP_RANK;
10203             toX = currentMoveString[2] - AAA;
10204             toY = currentMoveString[3] - ONE;
10205             promoChar = NULLCHAR;
10206             break;
10207           case AmbiguousMove:
10208             /* bug? */
10209             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10210   if (appData.debugMode) {
10211     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10212     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10213     setbuf(debugFP, NULL);
10214   }
10215             DisplayError(buf, 0);
10216             return;
10217           case ImpossibleMove:
10218             /* bug? */
10219             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10220   if (appData.debugMode) {
10221     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10222     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10223     setbuf(debugFP, NULL);
10224   }
10225             DisplayError(buf, 0);
10226             return;
10227           case EndOfFile:
10228             if (boardIndex < backwardMostMove) {
10229                 /* Oops, gap.  How did that happen? */
10230                 DisplayError(_("Gap in move list"), 0);
10231                 return;
10232             }
10233             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10234             if (boardIndex > forwardMostMove) {
10235                 forwardMostMove = boardIndex;
10236             }
10237             return;
10238           case ElapsedTime:
10239             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10240                 strcat(parseList[boardIndex-1], " ");
10241                 strcat(parseList[boardIndex-1], yy_text);
10242             }
10243             continue;
10244           case Comment:
10245           case PGNTag:
10246           case NAG:
10247           default:
10248             /* ignore */
10249             continue;
10250           case WhiteWins:
10251           case BlackWins:
10252           case GameIsDrawn:
10253           case GameUnfinished:
10254             if (gameMode == IcsExamining) {
10255                 if (boardIndex < backwardMostMove) {
10256                     /* Oops, gap.  How did that happen? */
10257                     return;
10258                 }
10259                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10260                 return;
10261             }
10262             gameInfo.result = moveType;
10263             p = strchr(yy_text, '{');
10264             if (p == NULL) p = strchr(yy_text, '(');
10265             if (p == NULL) {
10266                 p = yy_text;
10267                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10268             } else {
10269                 q = strchr(p, *p == '{' ? '}' : ')');
10270                 if (q != NULL) *q = NULLCHAR;
10271                 p++;
10272             }
10273             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10274             gameInfo.resultDetails = StrSave(p);
10275             continue;
10276         }
10277         if (boardIndex >= forwardMostMove &&
10278             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10279             backwardMostMove = blackPlaysFirst ? 1 : 0;
10280             return;
10281         }
10282         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10283                                  fromY, fromX, toY, toX, promoChar,
10284                                  parseList[boardIndex]);
10285         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10286         /* currentMoveString is set as a side-effect of yylex */
10287         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10288         strcat(moveList[boardIndex], "\n");
10289         boardIndex++;
10290         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10291         mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10292         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10293           case MT_NONE:
10294           case MT_STALEMATE:
10295           default:
10296             break;
10297           case MT_CHECK:
10298             if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10299             if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10300                 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10301                 break;
10302             }
10303           case MT_CHECKMATE:
10304           case MT_STAINMATE:
10305             strcat(parseList[boardIndex - 1], "#");
10306             break;
10307         }
10308     }
10309 }
10310
10311
10312 /* Apply a move to the given board  */
10313 void
10314 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10315 {
10316   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10317   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10318
10319     /* [HGM] compute & store e.p. status and castling rights for new position */
10320     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10321
10322       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10323       oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10324       board[EP_STATUS] = EP_NONE;
10325       board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10326
10327   if (fromY == DROP_RANK) {
10328         /* must be first */
10329         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10330             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10331             return;
10332         }
10333         piece = board[toY][toX] = (ChessSquare) fromX;
10334   } else {
10335 //      ChessSquare victim;
10336       int i;
10337
10338       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10339 //           victim = board[killY][killX],
10340            killed = board[killY][killX],
10341            board[killY][killX] = EmptySquare,
10342            board[EP_STATUS] = EP_CAPTURE;
10343            if( kill2X >= 0 && kill2Y >= 0)
10344              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10345       }
10346
10347       if( board[toY][toX] != EmptySquare ) {
10348            board[EP_STATUS] = EP_CAPTURE;
10349            if( (fromX != toX || fromY != toY) && // not igui!
10350                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10351                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10352                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10353            }
10354       }
10355
10356       pawn = board[fromY][fromX];
10357       if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10358         if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10359             captured = board[lastRank][lastFile]; // remove victim
10360             board[lastRank][lastFile] = EmptySquare;
10361             pawn = EmptySquare; // kludge to suppress old e.p. code
10362         }
10363       }
10364       if( pawn == WhiteLance || pawn == BlackLance ) {
10365            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10366                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10367                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10368            }
10369       }
10370       if( pawn == WhitePawn ) {
10371            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10372                board[EP_STATUS] = EP_PAWN_MOVE;
10373            if( toY-fromY>=2) {
10374                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10375                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10376                         gameInfo.variant != VariantBerolina || toX < fromX)
10377                       board[EP_STATUS] = toX | berolina;
10378                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10379                         gameInfo.variant != VariantBerolina || toX > fromX)
10380                       board[EP_STATUS] = toX;
10381                board[LAST_TO] = toX + 256*toY;
10382            }
10383       } else
10384       if( pawn == BlackPawn ) {
10385            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10386                board[EP_STATUS] = EP_PAWN_MOVE;
10387            if( toY-fromY<= -2) {
10388                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10389                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10390                         gameInfo.variant != VariantBerolina || toX < fromX)
10391                       board[EP_STATUS] = toX | berolina;
10392                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10393                         gameInfo.variant != VariantBerolina || toX > fromX)
10394                       board[EP_STATUS] = toX;
10395                board[LAST_TO] = toX + 256*toY;
10396            }
10397        }
10398
10399        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10400        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10401        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10402        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10403
10404        for(i=0; i<nrCastlingRights; i++) {
10405            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10406               board[CASTLING][i] == toX   && castlingRank[i] == toY
10407              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10408        }
10409
10410        if(gameInfo.variant == VariantSChess) { // update virginity
10411            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10412            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10413            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10414            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10415        }
10416
10417      if (fromX == toX && fromY == toY && killX < 0) return;
10418
10419      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10420      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10421      if(gameInfo.variant == VariantKnightmate)
10422          king += (int) WhiteUnicorn - (int) WhiteKing;
10423
10424     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10425        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10426         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10427         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10428         board[EP_STATUS] = EP_NONE; // capture was fake!
10429     } else
10430     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10431         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10432         board[toY][toX] = piece;
10433         board[EP_STATUS] = EP_NONE; // capture was fake!
10434     } else
10435     /* Code added by Tord: */
10436     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10437     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10438         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10439       board[EP_STATUS] = EP_NONE; // capture was fake!
10440       board[fromY][fromX] = EmptySquare;
10441       board[toY][toX] = EmptySquare;
10442       if((toX > fromX) != (piece == WhiteRook)) {
10443         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10444       } else {
10445         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10446       }
10447     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10448                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10449       board[EP_STATUS] = EP_NONE;
10450       board[fromY][fromX] = EmptySquare;
10451       board[toY][toX] = EmptySquare;
10452       if((toX > fromX) != (piece == BlackRook)) {
10453         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10454       } else {
10455         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10456       }
10457     /* End of code added by Tord */
10458
10459     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10460         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10461         board[toY][toX] = piece;
10462     } else if (board[fromY][fromX] == king
10463         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10464         && toY == fromY && toX > fromX+1) {
10465         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10466                                                                                              ; // castle with nearest piece
10467         board[fromY][toX-1] = board[fromY][rookX];
10468         board[fromY][rookX] = EmptySquare;
10469         board[fromY][fromX] = EmptySquare;
10470         board[toY][toX] = king;
10471     } else if (board[fromY][fromX] == king
10472         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10473                && toY == fromY && toX < fromX-1) {
10474         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10475                                                                                   ; // castle with nearest piece
10476         board[fromY][toX+1] = board[fromY][rookX];
10477         board[fromY][rookX] = EmptySquare;
10478         board[fromY][fromX] = EmptySquare;
10479         board[toY][toX] = king;
10480     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10481                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10482                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10483                ) {
10484         /* white pawn promotion */
10485         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10486         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10487             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10488         board[fromY][fromX] = EmptySquare;
10489     } else if ((fromY >= BOARD_HEIGHT>>1)
10490                && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10491                && (toX != fromX)
10492                && gameInfo.variant != VariantXiangqi
10493                && gameInfo.variant != VariantBerolina
10494                && (pawn == WhitePawn)
10495                && (board[toY][toX] == EmptySquare)) {
10496         if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10497         board[fromY][fromX] = EmptySquare;
10498         board[toY][toX] = piece;
10499         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10500     } else if ((fromY == BOARD_HEIGHT-4)
10501                && (toX == fromX)
10502                && gameInfo.variant == VariantBerolina
10503                && (board[fromY][fromX] == WhitePawn)
10504                && (board[toY][toX] == EmptySquare)) {
10505         board[fromY][fromX] = EmptySquare;
10506         board[toY][toX] = WhitePawn;
10507         if(oldEP & EP_BEROLIN_A) {
10508                 captured = board[fromY][fromX-1];
10509                 board[fromY][fromX-1] = EmptySquare;
10510         }else{  captured = board[fromY][fromX+1];
10511                 board[fromY][fromX+1] = EmptySquare;
10512         }
10513     } else if (board[fromY][fromX] == king
10514         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10515                && toY == fromY && toX > fromX+1) {
10516         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10517                                                                                              ;
10518         board[fromY][toX-1] = board[fromY][rookX];
10519         board[fromY][rookX] = EmptySquare;
10520         board[fromY][fromX] = EmptySquare;
10521         board[toY][toX] = king;
10522     } else if (board[fromY][fromX] == king
10523         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10524                && toY == fromY && toX < fromX-1) {
10525         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10526                                                                                 ;
10527         board[fromY][toX+1] = board[fromY][rookX];
10528         board[fromY][rookX] = EmptySquare;
10529         board[fromY][fromX] = EmptySquare;
10530         board[toY][toX] = king;
10531     } else if (fromY == 7 && fromX == 3
10532                && board[fromY][fromX] == BlackKing
10533                && toY == 7 && toX == 5) {
10534         board[fromY][fromX] = EmptySquare;
10535         board[toY][toX] = BlackKing;
10536         board[fromY][7] = EmptySquare;
10537         board[toY][4] = BlackRook;
10538     } else if (fromY == 7 && fromX == 3
10539                && board[fromY][fromX] == BlackKing
10540                && toY == 7 && toX == 1) {
10541         board[fromY][fromX] = EmptySquare;
10542         board[toY][toX] = BlackKing;
10543         board[fromY][0] = EmptySquare;
10544         board[toY][2] = BlackRook;
10545     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10546                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10547                && toY < promoRank && promoChar
10548                ) {
10549         /* black pawn promotion */
10550         board[toY][toX] = CharToPiece(ToLower(promoChar));
10551         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10552             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10553         board[fromY][fromX] = EmptySquare;
10554     } else if ((fromY < BOARD_HEIGHT>>1)
10555                && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10556                && (toX != fromX)
10557                && gameInfo.variant != VariantXiangqi
10558                && gameInfo.variant != VariantBerolina
10559                && (pawn == BlackPawn)
10560                && (board[toY][toX] == EmptySquare)) {
10561         if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10562         board[fromY][fromX] = EmptySquare;
10563         board[toY][toX] = piece;
10564         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10565     } else if ((fromY == 3)
10566                && (toX == fromX)
10567                && gameInfo.variant == VariantBerolina
10568                && (board[fromY][fromX] == BlackPawn)
10569                && (board[toY][toX] == EmptySquare)) {
10570         board[fromY][fromX] = EmptySquare;
10571         board[toY][toX] = BlackPawn;
10572         if(oldEP & EP_BEROLIN_A) {
10573                 captured = board[fromY][fromX-1];
10574                 board[fromY][fromX-1] = EmptySquare;
10575         }else{  captured = board[fromY][fromX+1];
10576                 board[fromY][fromX+1] = EmptySquare;
10577         }
10578     } else {
10579         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10580         board[fromY][fromX] = EmptySquare;
10581         board[toY][toX] = piece;
10582     }
10583   }
10584
10585     if (gameInfo.holdingsWidth != 0) {
10586
10587       /* !!A lot more code needs to be written to support holdings  */
10588       /* [HGM] OK, so I have written it. Holdings are stored in the */
10589       /* penultimate board files, so they are automaticlly stored   */
10590       /* in the game history.                                       */
10591       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10592                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10593         /* Delete from holdings, by decreasing count */
10594         /* and erasing image if necessary            */
10595         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10596         if(p < (int) BlackPawn) { /* white drop */
10597              p -= (int)WhitePawn;
10598                  p = PieceToNumber((ChessSquare)p);
10599              if(p >= gameInfo.holdingsSize) p = 0;
10600              if(--board[p][BOARD_WIDTH-2] <= 0)
10601                   board[p][BOARD_WIDTH-1] = EmptySquare;
10602              if((int)board[p][BOARD_WIDTH-2] < 0)
10603                         board[p][BOARD_WIDTH-2] = 0;
10604         } else {                  /* black drop */
10605              p -= (int)BlackPawn;
10606                  p = PieceToNumber((ChessSquare)p);
10607              if(p >= gameInfo.holdingsSize) p = 0;
10608              if(--board[handSize-1-p][1] <= 0)
10609                   board[handSize-1-p][0] = EmptySquare;
10610              if((int)board[handSize-1-p][1] < 0)
10611                         board[handSize-1-p][1] = 0;
10612         }
10613       }
10614       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10615           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10616         /* [HGM] holdings: Add to holdings, if holdings exist */
10617         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10618                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10619                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10620         }
10621         p = (int) captured;
10622         if (p >= (int) BlackPawn) {
10623           p -= (int)BlackPawn;
10624           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10625                   /* Restore shogi-promoted piece to its original  first */
10626                   captured = (ChessSquare) (DEMOTED(captured));
10627                   p = DEMOTED(p);
10628           }
10629           p = PieceToNumber((ChessSquare)p);
10630           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10631           board[p][BOARD_WIDTH-2]++;
10632           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10633         } else {
10634           p -= (int)WhitePawn;
10635           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10636                   captured = (ChessSquare) (DEMOTED(captured));
10637                   p = DEMOTED(p);
10638           }
10639           p = PieceToNumber((ChessSquare)p);
10640           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10641           board[handSize-1-p][1]++;
10642           board[handSize-1-p][0] = WHITE_TO_BLACK captured;
10643         }
10644       }
10645     } else if (gameInfo.variant == VariantAtomic) {
10646       if (captured != EmptySquare) {
10647         int y, x;
10648         for (y = toY-1; y <= toY+1; y++) {
10649           for (x = toX-1; x <= toX+1; x++) {
10650             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10651                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10652               board[y][x] = EmptySquare;
10653             }
10654           }
10655         }
10656         board[toY][toX] = EmptySquare;
10657       }
10658     }
10659
10660     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10661         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10662     } else
10663     if(promoChar == '+') {
10664         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10665         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10666         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10667           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10668     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10669         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10670         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10671            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10672         board[toY][toX] = newPiece;
10673     }
10674     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10675                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10676         // [HGM] superchess: take promotion piece out of holdings
10677         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10678         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10679             if(!--board[k][BOARD_WIDTH-2])
10680                 board[k][BOARD_WIDTH-1] = EmptySquare;
10681         } else {
10682             if(!--board[handSize-1-k][1])
10683                 board[handSize-1-k][0] = EmptySquare;
10684         }
10685     }
10686 }
10687
10688 /* Updates forwardMostMove */
10689 void
10690 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10691 {
10692     int x = toX, y = toY, mask;
10693     char *s = parseList[forwardMostMove];
10694     ChessSquare p = boards[forwardMostMove][toY][toX];
10695 //    forwardMostMove++; // [HGM] bare: moved downstream
10696
10697     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10698     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10699     (void) CoordsToAlgebraic(boards[forwardMostMove],
10700                              PosFlags(forwardMostMove),
10701                              fromY, fromX, y, x, (killX < 0)*promoChar,
10702                              s);
10703     if(kill2X >= 0 && kill2Y >= 0)
10704         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10705     if(killX >= 0 && killY >= 0)
10706         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10707                                            toX + AAA, toY + ONE - '0', promoChar);
10708
10709     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10710         int timeLeft; static int lastLoadFlag=0; int king, piece;
10711         piece = boards[forwardMostMove][fromY][fromX];
10712         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10713         if(gameInfo.variant == VariantKnightmate)
10714             king += (int) WhiteUnicorn - (int) WhiteKing;
10715         if(forwardMostMove == 0) {
10716             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10717                 fprintf(serverMoves, "%s;", UserName());
10718             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10719                 fprintf(serverMoves, "%s;", second.tidy);
10720             fprintf(serverMoves, "%s;", first.tidy);
10721             if(gameMode == MachinePlaysWhite)
10722                 fprintf(serverMoves, "%s;", UserName());
10723             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10724                 fprintf(serverMoves, "%s;", second.tidy);
10725         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10726         lastLoadFlag = loadFlag;
10727         // print base move
10728         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10729         // print castling suffix
10730         if( toY == fromY && piece == king ) {
10731             if(toX-fromX > 1)
10732                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10733             if(fromX-toX >1)
10734                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10735         }
10736         // e.p. suffix
10737         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10738              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10739              boards[forwardMostMove][toY][toX] == EmptySquare
10740              && fromX != toX && fromY != toY)
10741                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10742         // promotion suffix
10743         if(promoChar != NULLCHAR) {
10744             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10745                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10746                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10747             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10748         }
10749         if(!loadFlag) {
10750                 char buf[MOVE_LEN*2], *p; int len;
10751             fprintf(serverMoves, "/%d/%d",
10752                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10753             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10754             else                      timeLeft = blackTimeRemaining/1000;
10755             fprintf(serverMoves, "/%d", timeLeft);
10756                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10757                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10758                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10759                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10760             fprintf(serverMoves, "/%s", buf);
10761         }
10762         fflush(serverMoves);
10763     }
10764
10765     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10766         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10767       return;
10768     }
10769     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10770     if (commentList[forwardMostMove+1] != NULL) {
10771         free(commentList[forwardMostMove+1]);
10772         commentList[forwardMostMove+1] = NULL;
10773     }
10774     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10775     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10776     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10777     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10778     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10779     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10780     adjustedClock = FALSE;
10781     gameInfo.result = GameUnfinished;
10782     if (gameInfo.resultDetails != NULL) {
10783         free(gameInfo.resultDetails);
10784         gameInfo.resultDetails = NULL;
10785     }
10786     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10787                               moveList[forwardMostMove - 1]);
10788     mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10789     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10790       case MT_NONE:
10791       case MT_STALEMATE:
10792       default:
10793         break;
10794       case MT_CHECK:
10795         if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10796         if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10797             if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10798             break;
10799         }
10800       case MT_CHECKMATE:
10801       case MT_STAINMATE:
10802         strcat(parseList[forwardMostMove - 1], "#");
10803         break;
10804     }
10805 }
10806
10807 /* Updates currentMove if not pausing */
10808 void
10809 ShowMove (int fromX, int fromY, int toX, int toY)
10810 {
10811     int instant = (gameMode == PlayFromGameFile) ?
10812         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10813     if(appData.noGUI) return;
10814     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10815         if (!instant) {
10816             if (forwardMostMove == currentMove + 1) {
10817                 AnimateMove(boards[forwardMostMove - 1],
10818                             fromX, fromY, toX, toY);
10819             }
10820         }
10821         currentMove = forwardMostMove;
10822     }
10823
10824     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10825
10826     if (instant) return;
10827
10828     DisplayMove(currentMove - 1);
10829     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10830             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10831                 SetHighlights(fromX, fromY, toX, toY);
10832             }
10833     }
10834     DrawPosition(FALSE, boards[currentMove]);
10835     DisplayBothClocks();
10836     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10837 }
10838
10839 void
10840 SendEgtPath (ChessProgramState *cps)
10841 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10842         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10843
10844         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10845
10846         while(*p) {
10847             char c, *q = name+1, *r, *s;
10848
10849             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10850             while(*p && *p != ',') *q++ = *p++;
10851             *q++ = ':'; *q = 0;
10852             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10853                 strcmp(name, ",nalimov:") == 0 ) {
10854                 // take nalimov path from the menu-changeable option first, if it is defined
10855               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10856                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10857             } else
10858             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10859                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10860                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10861                 s = r = StrStr(s, ":") + 1; // beginning of path info
10862                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10863                 c = *r; *r = 0;             // temporarily null-terminate path info
10864                     *--q = 0;               // strip of trailig ':' from name
10865                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10866                 *r = c;
10867                 SendToProgram(buf,cps);     // send egtbpath command for this format
10868             }
10869             if(*p == ',') p++; // read away comma to position for next format name
10870         }
10871 }
10872
10873 static int
10874 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10875 {
10876       int width = 8, height = 8, holdings = 0;             // most common sizes
10877       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10878       // correct the deviations default for each variant
10879       if( v == VariantXiangqi ) width = 9,  height = 10;
10880       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10881       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10882       if( v == VariantCapablanca || v == VariantCapaRandom ||
10883           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10884                                 width = 10;
10885       if( v == VariantCourier ) width = 12;
10886       if( v == VariantSuper )                            holdings = 8;
10887       if( v == VariantGreat )   width = 10,              holdings = 8;
10888       if( v == VariantSChess )                           holdings = 7;
10889       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10890       if( v == VariantChuChess) width = 10, height = 10;
10891       if( v == VariantChu )     width = 12, height = 12;
10892       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10893              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10894              holdingsSize >= 0 && holdingsSize != holdings;
10895 }
10896
10897 char variantError[MSG_SIZ];
10898
10899 char *
10900 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10901 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10902       char *p, *variant = VariantName(v);
10903       static char b[MSG_SIZ];
10904       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10905            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10906                                                holdingsSize, variant); // cook up sized variant name
10907            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10908            if(StrStr(list, b) == NULL) {
10909                // specific sized variant not known, check if general sizing allowed
10910                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10911                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10912                             boardWidth, boardHeight, holdingsSize, engine);
10913                    return NULL;
10914                }
10915                /* [HGM] here we really should compare with the maximum supported board size */
10916            }
10917       } else snprintf(b, MSG_SIZ,"%s", variant);
10918       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10919       p = StrStr(list, b);
10920       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10921       if(p == NULL) {
10922           // occurs not at all in list, or only as sub-string
10923           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10924           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10925               int l = strlen(variantError);
10926               char *q;
10927               while(p != list && p[-1] != ',') p--;
10928               q = strchr(p, ',');
10929               if(q) *q = NULLCHAR;
10930               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10931               if(q) *q= ',';
10932           }
10933           return NULL;
10934       }
10935       return b;
10936 }
10937
10938 void
10939 InitChessProgram (ChessProgramState *cps, int setup)
10940 /* setup needed to setup FRC opening position */
10941 {
10942     char buf[MSG_SIZ], *b;
10943     if (appData.noChessProgram) return;
10944     hintRequested = FALSE;
10945     bookRequested = FALSE;
10946
10947     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10948     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10949     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10950     if(cps->memSize) { /* [HGM] memory */
10951       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10952         SendToProgram(buf, cps);
10953     }
10954     SendEgtPath(cps); /* [HGM] EGT */
10955     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10956       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10957         SendToProgram(buf, cps);
10958     }
10959
10960     setboardSpoiledMachineBlack = FALSE;
10961     SendToProgram(cps->initString, cps);
10962     if (gameInfo.variant != VariantNormal &&
10963         gameInfo.variant != VariantLoadable
10964         /* [HGM] also send variant if board size non-standard */
10965         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10966
10967       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10968                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10969
10970       if (b == NULL) {
10971         VariantClass v;
10972         char c, *q = cps->variants, *p = strchr(q, ',');
10973         if(p) *p = NULLCHAR;
10974         v = StringToVariant(q);
10975         DisplayError(variantError, 0);
10976         if(v != VariantUnknown && cps == &first) {
10977             int w, h, s;
10978             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10979                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10980             ASSIGN(appData.variant, q);
10981             Reset(TRUE, FALSE);
10982         }
10983         if(p) *p = ',';
10984         return;
10985       }
10986
10987       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10988       SendToProgram(buf, cps);
10989     }
10990     currentlyInitializedVariant = gameInfo.variant;
10991
10992     /* [HGM] send opening position in FRC to first engine */
10993     if(setup) {
10994           SendToProgram("force\n", cps);
10995           SendBoard(cps, 0);
10996           /* engine is now in force mode! Set flag to wake it up after first move. */
10997           setboardSpoiledMachineBlack = 1;
10998     }
10999
11000     if (cps->sendICS) {
11001       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
11002       SendToProgram(buf, cps);
11003     }
11004     cps->maybeThinking = FALSE;
11005     cps->offeredDraw = 0;
11006     if (!appData.icsActive) {
11007         SendTimeControl(cps, movesPerSession, timeControl,
11008                         timeIncrement, appData.searchDepth,
11009                         searchTime);
11010     }
11011     if (appData.showThinking
11012         // [HGM] thinking: four options require thinking output to be sent
11013         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
11014                                 ) {
11015         SendToProgram("post\n", cps);
11016     }
11017     SendToProgram("hard\n", cps);
11018     if (!appData.ponderNextMove) {
11019         /* Warning: "easy" is a toggle in GNU Chess, so don't send
11020            it without being sure what state we are in first.  "hard"
11021            is not a toggle, so that one is OK.
11022          */
11023         SendToProgram("easy\n", cps);
11024     }
11025     if (cps->usePing) {
11026       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
11027       SendToProgram(buf, cps);
11028     }
11029     cps->initDone = TRUE;
11030     ClearEngineOutputPane(cps == &second);
11031 }
11032
11033
11034 char *
11035 ResendOptions (ChessProgramState *cps, int toEngine)
11036 { // send the stored value of the options
11037   int i;
11038   static char buf2[MSG_SIZ*10];
11039   char buf[MSG_SIZ], *p = buf2;
11040   Option *opt = cps->option;
11041   *p = NULLCHAR;
11042   for(i=0; i<cps->nrOptions; i++, opt++) {
11043       *buf = NULLCHAR;
11044       switch(opt->type) {
11045         case Spin:
11046         case Slider:
11047         case CheckBox:
11048             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11049             snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
11050           break;
11051         case ComboBox:
11052             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11053             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
11054           break;
11055         default:
11056             if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
11057             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
11058           break;
11059         case Button:
11060         case SaveButton:
11061           continue;
11062       }
11063       if(*buf) {
11064         if(toEngine) {
11065           snprintf(buf2, MSG_SIZ, "option %s\n", buf);
11066           SendToProgram(buf2, cps);
11067         } else {
11068           if(p != buf2) *p++ = ',';
11069           strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
11070           while(*p) p++;
11071         }
11072       }
11073   }
11074   return buf2;
11075 }
11076
11077 void
11078 StartChessProgram (ChessProgramState *cps)
11079 {
11080     char buf[MSG_SIZ];
11081     int err;
11082
11083     if (appData.noChessProgram) return;
11084     cps->initDone = FALSE;
11085
11086     if (strcmp(cps->host, "localhost") == 0) {
11087         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
11088     } else if (*appData.remoteShell == NULLCHAR) {
11089         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
11090     } else {
11091         if (*appData.remoteUser == NULLCHAR) {
11092           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11093                     cps->program);
11094         } else {
11095           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11096                     cps->host, appData.remoteUser, cps->program);
11097         }
11098         err = StartChildProcess(buf, "", &cps->pr);
11099     }
11100
11101     if (err != 0) {
11102       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11103         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11104         if(cps != &first) return;
11105         appData.noChessProgram = TRUE;
11106         ThawUI();
11107         SetNCPMode();
11108 //      DisplayFatalError(buf, err, 1);
11109 //      cps->pr = NoProc;
11110 //      cps->isr = NULL;
11111         return;
11112     }
11113
11114     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11115     if (cps->protocolVersion > 1) {
11116       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11117       if(!cps->reload) { // do not clear options when reloading because of -xreuse
11118         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11119         cps->comboCnt = 0;  //                and values of combo boxes
11120       }
11121       SendToProgram(buf, cps);
11122       if(cps->reload) ResendOptions(cps, TRUE);
11123     } else {
11124       SendToProgram("xboard\n", cps);
11125     }
11126 }
11127
11128 void
11129 TwoMachinesEventIfReady P((void))
11130 {
11131   static int curMess = 0;
11132   if (first.lastPing != first.lastPong) {
11133     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11134     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11135     return;
11136   }
11137   if (second.lastPing != second.lastPong) {
11138     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11139     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11140     return;
11141   }
11142   DisplayMessage("", ""); curMess = 0;
11143   TwoMachinesEvent();
11144 }
11145
11146 char *
11147 MakeName (char *template)
11148 {
11149     time_t clock;
11150     struct tm *tm;
11151     static char buf[MSG_SIZ];
11152     char *p = buf;
11153     int i;
11154
11155     clock = time((time_t *)NULL);
11156     tm = localtime(&clock);
11157
11158     while(*p++ = *template++) if(p[-1] == '%') {
11159         switch(*template++) {
11160           case 0:   *p = 0; return buf;
11161           case 'Y': i = tm->tm_year+1900; break;
11162           case 'y': i = tm->tm_year-100; break;
11163           case 'M': i = tm->tm_mon+1; break;
11164           case 'd': i = tm->tm_mday; break;
11165           case 'h': i = tm->tm_hour; break;
11166           case 'm': i = tm->tm_min; break;
11167           case 's': i = tm->tm_sec; break;
11168           default:  i = 0;
11169         }
11170         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11171     }
11172     return buf;
11173 }
11174
11175 int
11176 CountPlayers (char *p)
11177 {
11178     int n = 0;
11179     while(p = strchr(p, '\n')) p++, n++; // count participants
11180     return n;
11181 }
11182
11183 FILE *
11184 WriteTourneyFile (char *results, FILE *f)
11185 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11186     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11187     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11188         // create a file with tournament description
11189         fprintf(f, "-participants {%s}\n", appData.participants);
11190         fprintf(f, "-seedBase %d\n", appData.seedBase);
11191         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11192         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11193         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11194         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11195         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11196         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11197         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11198         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11199         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11200         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11201         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11202         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11203         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11204         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11205         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11206         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11207         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11208         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11209         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11210         fprintf(f, "-smpCores %d\n", appData.smpCores);
11211         if(searchTime > 0)
11212                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11213         else {
11214                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11215                 fprintf(f, "-tc %s\n", appData.timeControl);
11216                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11217         }
11218         fprintf(f, "-results \"%s\"\n", results);
11219     }
11220     return f;
11221 }
11222
11223 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11224
11225 void
11226 Substitute (char *participants, int expunge)
11227 {
11228     int i, changed, changes=0, nPlayers=0;
11229     char *p, *q, *r, buf[MSG_SIZ];
11230     if(participants == NULL) return;
11231     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11232     r = p = participants; q = appData.participants;
11233     while(*p && *p == *q) {
11234         if(*p == '\n') r = p+1, nPlayers++;
11235         p++; q++;
11236     }
11237     if(*p) { // difference
11238         while(*p && *p++ != '\n')
11239                                  ;
11240         while(*q && *q++ != '\n')
11241                                  ;
11242       changed = nPlayers;
11243         changes = 1 + (strcmp(p, q) != 0);
11244     }
11245     if(changes == 1) { // a single engine mnemonic was changed
11246         q = r; while(*q) nPlayers += (*q++ == '\n');
11247         p = buf; while(*r && (*p = *r++) != '\n') p++;
11248         *p = NULLCHAR;
11249         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11250         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11251         if(mnemonic[i]) { // The substitute is valid
11252             FILE *f;
11253             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11254                 flock(fileno(f), LOCK_EX);
11255                 ParseArgsFromFile(f);
11256                 fseek(f, 0, SEEK_SET);
11257                 FREE(appData.participants); appData.participants = participants;
11258                 if(expunge) { // erase results of replaced engine
11259                     int len = strlen(appData.results), w, b, dummy;
11260                     for(i=0; i<len; i++) {
11261                         Pairing(i, nPlayers, &w, &b, &dummy);
11262                         if((w == changed || b == changed) && appData.results[i] == '*') {
11263                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11264                             fclose(f);
11265                             return;
11266                         }
11267                     }
11268                     for(i=0; i<len; i++) {
11269                         Pairing(i, nPlayers, &w, &b, &dummy);
11270                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11271                     }
11272                 }
11273                 WriteTourneyFile(appData.results, f);
11274                 fclose(f); // release lock
11275                 return;
11276             }
11277         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11278     }
11279     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11280     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11281     free(participants);
11282     return;
11283 }
11284
11285 int
11286 CheckPlayers (char *participants)
11287 {
11288         int i;
11289         char buf[MSG_SIZ], *p;
11290         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11291         while(p = strchr(participants, '\n')) {
11292             *p = NULLCHAR;
11293             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11294             if(!mnemonic[i]) {
11295                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11296                 *p = '\n';
11297                 DisplayError(buf, 0);
11298                 return 1;
11299             }
11300             *p = '\n';
11301             participants = p + 1;
11302         }
11303         return 0;
11304 }
11305
11306 int
11307 CreateTourney (char *name)
11308 {
11309         FILE *f;
11310         if(matchMode && strcmp(name, appData.tourneyFile)) {
11311              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11312         }
11313         if(name[0] == NULLCHAR) {
11314             if(appData.participants[0])
11315                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11316             return 0;
11317         }
11318         f = fopen(name, "r");
11319         if(f) { // file exists
11320             ASSIGN(appData.tourneyFile, name);
11321             ParseArgsFromFile(f); // parse it
11322         } else {
11323             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11324             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11325                 DisplayError(_("Not enough participants"), 0);
11326                 return 0;
11327             }
11328             if(CheckPlayers(appData.participants)) return 0;
11329             ASSIGN(appData.tourneyFile, name);
11330             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11331             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11332         }
11333         fclose(f);
11334         appData.noChessProgram = FALSE;
11335         appData.clockMode = TRUE;
11336         SetGNUMode();
11337         return 1;
11338 }
11339
11340 int
11341 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11342 {
11343     char buf[2*MSG_SIZ], *p, *q;
11344     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11345     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11346     skip = !all && group[0]; // if group requested, we start in skip mode
11347     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11348         p = names; q = buf; header = 0;
11349         while(*p && *p != '\n') *q++ = *p++;
11350         *q = 0;
11351         if(*p == '\n') p++;
11352         if(buf[0] == '#') {
11353             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11354             depth++; // we must be entering a new group
11355             if(all) continue; // suppress printing group headers when complete list requested
11356             header = 1;
11357             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11358         }
11359         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11360         if(engineList[i]) free(engineList[i]);
11361         engineList[i] = strdup(buf);
11362         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11363         if(engineMnemonic[i]) free(engineMnemonic[i]);
11364         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11365             strcat(buf, " (");
11366             sscanf(q + 8, "%s", buf + strlen(buf));
11367             strcat(buf, ")");
11368         }
11369         engineMnemonic[i] = strdup(buf);
11370         i++;
11371     }
11372     engineList[i] = engineMnemonic[i] = NULL;
11373     return i;
11374 }
11375
11376 void
11377 SaveEngineSettings (int n)
11378 {
11379     int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11380     if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11381     p = strstr(firstChessProgramNames, currentEngine[n]);
11382     if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11383     optionSettings = ResendOptions(n ? &second : &first, FALSE);
11384     len = strlen(currentEngine[n]);
11385     q = p + len; *p = 0; // cut list into head and tail piece
11386     s = strstr(currentEngine[n], "firstOptions");
11387     if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11388         char *r = s + 14;
11389         while(*r && *r != s[13]) r++;
11390         s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11391         snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11392     } else if(*optionSettings) {
11393         snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11394     }
11395     ASSIGN(currentEngine[n], buf); // updated engine line
11396     len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11397     s = malloc(len);
11398     snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11399     FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11400 }
11401
11402 // following implemented as macro to avoid type limitations
11403 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11404
11405 void
11406 SwapEngines (int n)
11407 {   // swap settings for first engine and other engine (so far only some selected options)
11408     int h;
11409     char *p;
11410     if(n == 0) return;
11411     SWAP(directory, p)
11412     SWAP(chessProgram, p)
11413     SWAP(isUCI, h)
11414     SWAP(hasOwnBookUCI, h)
11415     SWAP(protocolVersion, h)
11416     SWAP(reuse, h)
11417     SWAP(scoreIsAbsolute, h)
11418     SWAP(timeOdds, h)
11419     SWAP(logo, p)
11420     SWAP(pgnName, p)
11421     SWAP(pvSAN, h)
11422     SWAP(engOptions, p)
11423     SWAP(engInitString, p)
11424     SWAP(computerString, p)
11425     SWAP(features, p)
11426     SWAP(fenOverride, p)
11427     SWAP(NPS, h)
11428     SWAP(accumulateTC, h)
11429     SWAP(drawDepth, h)
11430     SWAP(host, p)
11431     SWAP(pseudo, h)
11432 }
11433
11434 int
11435 GetEngineLine (char *s, int n)
11436 {
11437     int i;
11438     char buf[MSG_SIZ];
11439     extern char *icsNames;
11440     if(!s || !*s) return 0;
11441     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11442     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11443     if(!mnemonic[i]) return 0;
11444     if(n == 11) return 1; // just testing if there was a match
11445     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11446     if(n == 1) SwapEngines(n);
11447     ParseArgsFromString(buf);
11448     if(n == 1) SwapEngines(n);
11449     if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11450     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11451         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11452         ParseArgsFromString(buf);
11453     }
11454     return 1;
11455 }
11456
11457 int
11458 SetPlayer (int player, char *p)
11459 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11460     int i;
11461     char buf[MSG_SIZ], *engineName;
11462     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11463     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11464     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11465     if(mnemonic[i]) {
11466         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11467         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11468         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11469         ParseArgsFromString(buf);
11470     } else { // no engine with this nickname is installed!
11471         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11472         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11473         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11474         ModeHighlight();
11475         DisplayError(buf, 0);
11476         return 0;
11477     }
11478     free(engineName);
11479     return i;
11480 }
11481
11482 char *recentEngines;
11483
11484 void
11485 RecentEngineEvent (int nr)
11486 {
11487     int n;
11488 //    SwapEngines(1); // bump first to second
11489 //    ReplaceEngine(&second, 1); // and load it there
11490     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11491     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11492     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11493         ReplaceEngine(&first, 0);
11494         FloatToFront(&appData.recentEngineList, command[n]);
11495         ASSIGN(currentEngine[0], command[n]);
11496     }
11497 }
11498
11499 int
11500 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11501 {   // determine players from game number
11502     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11503
11504     if(appData.tourneyType == 0) {
11505         roundsPerCycle = (nPlayers - 1) | 1;
11506         pairingsPerRound = nPlayers / 2;
11507     } else if(appData.tourneyType > 0) {
11508         roundsPerCycle = nPlayers - appData.tourneyType;
11509         pairingsPerRound = appData.tourneyType;
11510     }
11511     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11512     gamesPerCycle = gamesPerRound * roundsPerCycle;
11513     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11514     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11515     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11516     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11517     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11518     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11519
11520     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11521     if(appData.roundSync) *syncInterval = gamesPerRound;
11522
11523     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11524
11525     if(appData.tourneyType == 0) {
11526         if(curPairing == (nPlayers-1)/2 ) {
11527             *whitePlayer = curRound;
11528             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11529         } else {
11530             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11531             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11532             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11533             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11534         }
11535     } else if(appData.tourneyType > 1) {
11536         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11537         *whitePlayer = curRound + appData.tourneyType;
11538     } else if(appData.tourneyType > 0) {
11539         *whitePlayer = curPairing;
11540         *blackPlayer = curRound + appData.tourneyType;
11541     }
11542
11543     // take care of white/black alternation per round.
11544     // For cycles and games this is already taken care of by default, derived from matchGame!
11545     return curRound & 1;
11546 }
11547
11548 int
11549 NextTourneyGame (int nr, int *swapColors)
11550 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11551     char *p, *q;
11552     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11553     FILE *tf;
11554     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11555     tf = fopen(appData.tourneyFile, "r");
11556     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11557     ParseArgsFromFile(tf); fclose(tf);
11558     InitTimeControls(); // TC might be altered from tourney file
11559
11560     nPlayers = CountPlayers(appData.participants); // count participants
11561     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11562     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11563
11564     if(syncInterval) {
11565         p = q = appData.results;
11566         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11567         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11568             DisplayMessage(_("Waiting for other game(s)"),"");
11569             waitingForGame = TRUE;
11570             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11571             return 0;
11572         }
11573         waitingForGame = FALSE;
11574     }
11575
11576     if(appData.tourneyType < 0) {
11577         if(nr>=0 && !pairingReceived) {
11578             char buf[1<<16];
11579             if(pairing.pr == NoProc) {
11580                 if(!appData.pairingEngine[0]) {
11581                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11582                     return 0;
11583                 }
11584                 StartChessProgram(&pairing); // starts the pairing engine
11585             }
11586             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11587             SendToProgram(buf, &pairing);
11588             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11589             SendToProgram(buf, &pairing);
11590             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11591         }
11592         pairingReceived = 0;                              // ... so we continue here
11593         *swapColors = 0;
11594         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11595         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11596         matchGame = 1; roundNr = nr / syncInterval + 1;
11597     }
11598
11599     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11600
11601     // redefine engines, engine dir, etc.
11602     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11603     if(first.pr == NoProc) {
11604       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11605       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11606     }
11607     if(second.pr == NoProc) {
11608       SwapEngines(1);
11609       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11610       SwapEngines(1);         // and make that valid for second engine by swapping
11611       InitEngine(&second, 1);
11612     }
11613     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11614     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11615     return OK;
11616 }
11617
11618 void
11619 NextMatchGame ()
11620 {   // performs game initialization that does not invoke engines, and then tries to start the game
11621     int res, firstWhite, swapColors = 0;
11622     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11623     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
11624         char buf[MSG_SIZ];
11625         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11626         if(strcmp(buf, currentDebugFile)) { // name has changed
11627             FILE *f = fopen(buf, "w");
11628             if(f) { // if opening the new file failed, just keep using the old one
11629                 ASSIGN(currentDebugFile, buf);
11630                 fclose(debugFP);
11631                 debugFP = f;
11632             }
11633             if(appData.serverFileName) {
11634                 if(serverFP) fclose(serverFP);
11635                 serverFP = fopen(appData.serverFileName, "w");
11636                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11637                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11638             }
11639         }
11640     }
11641     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11642     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11643     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11644     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11645     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11646     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11647     Reset(FALSE, first.pr != NoProc);
11648     res = LoadGameOrPosition(matchGame); // setup game
11649     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11650     if(!res) return; // abort when bad game/pos file
11651     if(appData.epd) {// in EPD mode we make sure first engine is to move
11652         firstWhite = !(forwardMostMove & 1);
11653         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11654         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11655     }
11656     TwoMachinesEvent();
11657 }
11658
11659 void
11660 UserAdjudicationEvent (int result)
11661 {
11662     ChessMove gameResult = GameIsDrawn;
11663
11664     if( result > 0 ) {
11665         gameResult = WhiteWins;
11666     }
11667     else if( result < 0 ) {
11668         gameResult = BlackWins;
11669     }
11670
11671     if( gameMode == TwoMachinesPlay ) {
11672         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11673     }
11674 }
11675
11676
11677 // [HGM] save: calculate checksum of game to make games easily identifiable
11678 int
11679 StringCheckSum (char *s)
11680 {
11681         int i = 0;
11682         if(s==NULL) return 0;
11683         while(*s) i = i*259 + *s++;
11684         return i;
11685 }
11686
11687 int
11688 GameCheckSum ()
11689 {
11690         int i, sum=0;
11691         for(i=backwardMostMove; i<forwardMostMove; i++) {
11692                 sum += pvInfoList[i].depth;
11693                 sum += StringCheckSum(parseList[i]);
11694                 sum += StringCheckSum(commentList[i]);
11695                 sum *= 261;
11696         }
11697         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11698         return sum + StringCheckSum(commentList[i]);
11699 } // end of save patch
11700
11701 void
11702 GameEnds (ChessMove result, char *resultDetails, int whosays)
11703 {
11704     GameMode nextGameMode;
11705     int isIcsGame;
11706     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11707
11708     if(endingGame) return; /* [HGM] crash: forbid recursion */
11709     endingGame = 1;
11710     if(twoBoards) { // [HGM] dual: switch back to one board
11711         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11712         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11713     }
11714     if (appData.debugMode) {
11715       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11716               result, resultDetails ? resultDetails : "(null)", whosays);
11717     }
11718
11719     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11720
11721     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11722
11723     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11724         /* If we are playing on ICS, the server decides when the
11725            game is over, but the engine can offer to draw, claim
11726            a draw, or resign.
11727          */
11728 #if ZIPPY
11729         if (appData.zippyPlay && first.initDone) {
11730             if (result == GameIsDrawn) {
11731                 /* In case draw still needs to be claimed */
11732                 SendToICS(ics_prefix);
11733                 SendToICS("draw\n");
11734             } else if (StrCaseStr(resultDetails, "resign")) {
11735                 SendToICS(ics_prefix);
11736                 SendToICS("resign\n");
11737             }
11738         }
11739 #endif
11740         endingGame = 0; /* [HGM] crash */
11741         return;
11742     }
11743
11744     /* If we're loading the game from a file, stop */
11745     if (whosays == GE_FILE) {
11746       (void) StopLoadGameTimer();
11747       gameFileFP = NULL;
11748     }
11749
11750     /* Cancel draw offers */
11751     first.offeredDraw = second.offeredDraw = 0;
11752
11753     /* If this is an ICS game, only ICS can really say it's done;
11754        if not, anyone can. */
11755     isIcsGame = (gameMode == IcsPlayingWhite ||
11756                  gameMode == IcsPlayingBlack ||
11757                  gameMode == IcsObserving    ||
11758                  gameMode == IcsExamining);
11759
11760     if (!isIcsGame || whosays == GE_ICS) {
11761         /* OK -- not an ICS game, or ICS said it was done */
11762         StopClocks();
11763         if (!isIcsGame && !appData.noChessProgram)
11764           SetUserThinkingEnables();
11765
11766         /* [HGM] if a machine claims the game end we verify this claim */
11767         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11768             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11769                 char claimer;
11770                 ChessMove trueResult = (ChessMove) -1;
11771
11772                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11773                                             first.twoMachinesColor[0] :
11774                                             second.twoMachinesColor[0] ;
11775
11776                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11777                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11778                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11779                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11780                 } else
11781                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11782                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11783                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11784                 } else
11785                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11786                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11787                 }
11788
11789                 // now verify win claims, but not in drop games, as we don't understand those yet
11790                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11791                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11792                     (result == WhiteWins && claimer == 'w' ||
11793                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11794                       if (appData.debugMode) {
11795                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11796                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11797                       }
11798                       if(result != trueResult) {
11799                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11800                               result = claimer == 'w' ? BlackWins : WhiteWins;
11801                               resultDetails = buf;
11802                       }
11803                 } else
11804                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11805                     && (forwardMostMove <= backwardMostMove ||
11806                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11807                         (claimer=='b')==(forwardMostMove&1))
11808                                                                                   ) {
11809                       /* [HGM] verify: draws that were not flagged are false claims */
11810                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11811                       result = claimer == 'w' ? BlackWins : WhiteWins;
11812                       resultDetails = buf;
11813                 }
11814                 /* (Claiming a loss is accepted no questions asked!) */
11815             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11816                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11817                 result = GameUnfinished;
11818                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11819             }
11820             /* [HGM] bare: don't allow bare King to win */
11821             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11822                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11823                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11824                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11825                && result != GameIsDrawn)
11826             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11827                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11828                         int p = (int)boards[forwardMostMove][i][j] - color;
11829                         if(p >= 0 && p <= (int)WhiteKing) k++;
11830                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11831                 }
11832                 if (appData.debugMode) {
11833                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11834                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11835                 }
11836                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11837                         result = GameIsDrawn;
11838                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11839                         resultDetails = buf;
11840                 }
11841             }
11842         }
11843
11844
11845         if(serverMoves != NULL && !loadFlag) { char c = '=';
11846             if(result==WhiteWins) c = '+';
11847             if(result==BlackWins) c = '-';
11848             if(resultDetails != NULL)
11849                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11850         }
11851         if (resultDetails != NULL) {
11852             gameInfo.result = result;
11853             gameInfo.resultDetails = StrSave(resultDetails);
11854
11855             /* display last move only if game was not loaded from file */
11856             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11857                 DisplayMove(currentMove - 1);
11858
11859             if (forwardMostMove != 0) {
11860                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11861                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11862                                                                 ) {
11863                     if (*appData.saveGameFile != NULLCHAR) {
11864                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11865                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11866                         else
11867                         SaveGameToFile(appData.saveGameFile, TRUE);
11868                     } else if (appData.autoSaveGames) {
11869                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11870                     }
11871                     if (*appData.savePositionFile != NULLCHAR) {
11872                         SavePositionToFile(appData.savePositionFile);
11873                     }
11874                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11875                 }
11876             }
11877
11878             /* Tell program how game ended in case it is learning */
11879             /* [HGM] Moved this to after saving the PGN, just in case */
11880             /* engine died and we got here through time loss. In that */
11881             /* case we will get a fatal error writing the pipe, which */
11882             /* would otherwise lose us the PGN.                       */
11883             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11884             /* output during GameEnds should never be fatal anymore   */
11885             if (gameMode == MachinePlaysWhite ||
11886                 gameMode == MachinePlaysBlack ||
11887                 gameMode == TwoMachinesPlay ||
11888                 gameMode == IcsPlayingWhite ||
11889                 gameMode == IcsPlayingBlack ||
11890                 gameMode == BeginningOfGame) {
11891                 char buf[MSG_SIZ];
11892                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11893                         resultDetails);
11894                 if (first.pr != NoProc) {
11895                     SendToProgram(buf, &first);
11896                 }
11897                 if (second.pr != NoProc &&
11898                     gameMode == TwoMachinesPlay) {
11899                     SendToProgram(buf, &second);
11900                 }
11901             }
11902         }
11903
11904         if (appData.icsActive) {
11905             if (appData.quietPlay &&
11906                 (gameMode == IcsPlayingWhite ||
11907                  gameMode == IcsPlayingBlack)) {
11908                 SendToICS(ics_prefix);
11909                 SendToICS("set shout 1\n");
11910             }
11911             nextGameMode = IcsIdle;
11912             ics_user_moved = FALSE;
11913             /* clean up premove.  It's ugly when the game has ended and the
11914              * premove highlights are still on the board.
11915              */
11916             if (gotPremove) {
11917               gotPremove = FALSE;
11918               ClearPremoveHighlights();
11919               DrawPosition(FALSE, boards[currentMove]);
11920             }
11921             if (whosays == GE_ICS) {
11922                 switch (result) {
11923                 case WhiteWins:
11924                     if (gameMode == IcsPlayingWhite)
11925                         PlayIcsWinSound();
11926                     else if(gameMode == IcsPlayingBlack)
11927                         PlayIcsLossSound();
11928                     break;
11929                 case BlackWins:
11930                     if (gameMode == IcsPlayingBlack)
11931                         PlayIcsWinSound();
11932                     else if(gameMode == IcsPlayingWhite)
11933                         PlayIcsLossSound();
11934                     break;
11935                 case GameIsDrawn:
11936                     PlayIcsDrawSound();
11937                     break;
11938                 default:
11939                     PlayIcsUnfinishedSound();
11940                 }
11941             }
11942             if(appData.quitNext) { ExitEvent(0); return; }
11943         } else if (gameMode == EditGame ||
11944                    gameMode == PlayFromGameFile ||
11945                    gameMode == AnalyzeMode ||
11946                    gameMode == AnalyzeFile) {
11947             nextGameMode = gameMode;
11948         } else {
11949             nextGameMode = EndOfGame;
11950         }
11951         pausing = FALSE;
11952         ModeHighlight();
11953     } else {
11954         nextGameMode = gameMode;
11955     }
11956
11957     if (appData.noChessProgram) {
11958         gameMode = nextGameMode;
11959         ModeHighlight();
11960         endingGame = 0; /* [HGM] crash */
11961         return;
11962     }
11963
11964     if (first.reuse) {
11965         /* Put first chess program into idle state */
11966         if (first.pr != NoProc &&
11967             (gameMode == MachinePlaysWhite ||
11968              gameMode == MachinePlaysBlack ||
11969              gameMode == TwoMachinesPlay ||
11970              gameMode == IcsPlayingWhite ||
11971              gameMode == IcsPlayingBlack ||
11972              gameMode == BeginningOfGame)) {
11973             SendToProgram("force\n", &first);
11974             if (first.usePing) {
11975               char buf[MSG_SIZ];
11976               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11977               SendToProgram(buf, &first);
11978             }
11979         }
11980     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11981         /* Kill off first chess program */
11982         if (first.isr != NULL)
11983           RemoveInputSource(first.isr);
11984         first.isr = NULL;
11985
11986         if (first.pr != NoProc) {
11987             ExitAnalyzeMode();
11988             DoSleep( appData.delayBeforeQuit );
11989             SendToProgram("quit\n", &first);
11990             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11991             first.reload = TRUE;
11992         }
11993         first.pr = NoProc;
11994     }
11995     if (second.reuse) {
11996         /* Put second chess program into idle state */
11997         if (second.pr != NoProc &&
11998             gameMode == TwoMachinesPlay) {
11999             SendToProgram("force\n", &second);
12000             if (second.usePing) {
12001               char buf[MSG_SIZ];
12002               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
12003               SendToProgram(buf, &second);
12004             }
12005         }
12006     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
12007         /* Kill off second chess program */
12008         if (second.isr != NULL)
12009           RemoveInputSource(second.isr);
12010         second.isr = NULL;
12011
12012         if (second.pr != NoProc) {
12013             DoSleep( appData.delayBeforeQuit );
12014             SendToProgram("quit\n", &second);
12015             DestroyChildProcess(second.pr, 4 + second.useSigterm);
12016             second.reload = TRUE;
12017         }
12018         second.pr = NoProc;
12019     }
12020
12021     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
12022         char resChar = '=';
12023         switch (result) {
12024         case WhiteWins:
12025           resChar = '+';
12026           if (first.twoMachinesColor[0] == 'w') {
12027             first.matchWins++;
12028           } else {
12029             second.matchWins++;
12030           }
12031           break;
12032         case BlackWins:
12033           resChar = '-';
12034           if (first.twoMachinesColor[0] == 'b') {
12035             first.matchWins++;
12036           } else {
12037             second.matchWins++;
12038           }
12039           break;
12040         case GameUnfinished:
12041           resChar = ' ';
12042         default:
12043           break;
12044         }
12045
12046         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
12047         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
12048             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
12049             ReserveGame(nextGame, resChar); // sets nextGame
12050             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
12051             else ranking = strdup("busy"); //suppress popup when aborted but not finished
12052         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
12053
12054         if (nextGame <= appData.matchGames && !abortMatch) {
12055             gameMode = nextGameMode;
12056             matchGame = nextGame; // this will be overruled in tourney mode!
12057             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
12058             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
12059             endingGame = 0; /* [HGM] crash */
12060             return;
12061         } else {
12062             gameMode = nextGameMode;
12063             if(appData.epd) {
12064                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
12065                 OutputKibitz(2, buf);
12066                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
12067                 OutputKibitz(2, buf);
12068                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
12069                 if(second.matchWins) OutputKibitz(2, buf);
12070                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
12071                 OutputKibitz(2, buf);
12072             }
12073             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
12074                      first.tidy, second.tidy,
12075                      first.matchWins, second.matchWins,
12076                      appData.matchGames - (first.matchWins + second.matchWins));
12077             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
12078             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
12079             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
12080             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
12081                 first.twoMachinesColor = "black\n";
12082                 second.twoMachinesColor = "white\n";
12083             } else {
12084                 first.twoMachinesColor = "white\n";
12085                 second.twoMachinesColor = "black\n";
12086             }
12087         }
12088     }
12089     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
12090         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
12091       ExitAnalyzeMode();
12092     gameMode = nextGameMode;
12093     ModeHighlight();
12094     endingGame = 0;  /* [HGM] crash */
12095     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12096         if(matchMode == TRUE) { // match through command line: exit with or without popup
12097             if(ranking) {
12098                 ToNrEvent(forwardMostMove);
12099                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12100                 else ExitEvent(0);
12101             } else DisplayFatalError(buf, 0, 0);
12102         } else { // match through menu; just stop, with or without popup
12103             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12104             ModeHighlight();
12105             if(ranking){
12106                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12107             } else DisplayNote(buf);
12108       }
12109       if(ranking) free(ranking);
12110     }
12111 }
12112
12113 /* Assumes program was just initialized (initString sent).
12114    Leaves program in force mode. */
12115 void
12116 FeedMovesToProgram (ChessProgramState *cps, int upto)
12117 {
12118     int i;
12119
12120     if (appData.debugMode)
12121       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12122               startedFromSetupPosition ? "position and " : "",
12123               backwardMostMove, upto, cps->which);
12124     if(currentlyInitializedVariant != gameInfo.variant) {
12125       char buf[MSG_SIZ];
12126         // [HGM] variantswitch: make engine aware of new variant
12127         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12128                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12129                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12130         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12131         SendToProgram(buf, cps);
12132         currentlyInitializedVariant = gameInfo.variant;
12133     }
12134     SendToProgram("force\n", cps);
12135     if (startedFromSetupPosition) {
12136         SendBoard(cps, backwardMostMove);
12137     if (appData.debugMode) {
12138         fprintf(debugFP, "feedMoves\n");
12139     }
12140     }
12141     for (i = backwardMostMove; i < upto; i++) {
12142         SendMoveToProgram(i, cps);
12143     }
12144 }
12145
12146
12147 int
12148 ResurrectChessProgram ()
12149 {
12150      /* The chess program may have exited.
12151         If so, restart it and feed it all the moves made so far. */
12152     static int doInit = 0;
12153
12154     if (appData.noChessProgram) return 1;
12155
12156     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12157         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12158         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12159         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12160     } else {
12161         if (first.pr != NoProc) return 1;
12162         StartChessProgram(&first);
12163     }
12164     InitChessProgram(&first, FALSE);
12165     FeedMovesToProgram(&first, currentMove);
12166
12167     if (!first.sendTime) {
12168         /* can't tell gnuchess what its clock should read,
12169            so we bow to its notion. */
12170         ResetClocks();
12171         timeRemaining[0][currentMove] = whiteTimeRemaining;
12172         timeRemaining[1][currentMove] = blackTimeRemaining;
12173     }
12174
12175     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12176                 appData.icsEngineAnalyze) && first.analysisSupport) {
12177       SendToProgram("analyze\n", &first);
12178       first.analyzing = TRUE;
12179     }
12180     return 1;
12181 }
12182
12183 /*
12184  * Button procedures
12185  */
12186 void
12187 Reset (int redraw, int init)
12188 {
12189     int i;
12190
12191     if (appData.debugMode) {
12192         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12193                 redraw, init, gameMode);
12194     }
12195     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12196     deadRanks = 0; // assume entire board is used
12197     handSize = 0;
12198     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12199     CleanupTail(); // [HGM] vari: delete any stored variations
12200     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12201     pausing = pauseExamInvalid = FALSE;
12202     startedFromSetupPosition = blackPlaysFirst = FALSE;
12203     firstMove = TRUE;
12204     whiteFlag = blackFlag = FALSE;
12205     userOfferedDraw = FALSE;
12206     hintRequested = bookRequested = FALSE;
12207     first.maybeThinking = FALSE;
12208     second.maybeThinking = FALSE;
12209     first.bookSuspend = FALSE; // [HGM] book
12210     second.bookSuspend = FALSE;
12211     thinkOutput[0] = NULLCHAR;
12212     lastHint[0] = NULLCHAR;
12213     ClearGameInfo(&gameInfo);
12214     gameInfo.variant = StringToVariant(appData.variant);
12215     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12216         gameInfo.variant = VariantUnknown;
12217         strncpy(engineVariant, appData.variant, MSG_SIZ);
12218     }
12219     ics_user_moved = ics_clock_paused = FALSE;
12220     ics_getting_history = H_FALSE;
12221     ics_gamenum = -1;
12222     white_holding[0] = black_holding[0] = NULLCHAR;
12223     ClearProgramStats();
12224     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12225
12226     ResetFrontEnd();
12227     ClearHighlights();
12228     flipView = appData.flipView;
12229     ClearPremoveHighlights();
12230     gotPremove = FALSE;
12231     alarmSounded = FALSE;
12232     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12233
12234     GameEnds(EndOfFile, NULL, GE_PLAYER);
12235     if(appData.serverMovesName != NULL) {
12236         /* [HGM] prepare to make moves file for broadcasting */
12237         clock_t t = clock();
12238         if(serverMoves != NULL) fclose(serverMoves);
12239         serverMoves = fopen(appData.serverMovesName, "r");
12240         if(serverMoves != NULL) {
12241             fclose(serverMoves);
12242             /* delay 15 sec before overwriting, so all clients can see end */
12243             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12244         }
12245         serverMoves = fopen(appData.serverMovesName, "w");
12246     }
12247
12248     ExitAnalyzeMode();
12249     gameMode = BeginningOfGame;
12250     ModeHighlight();
12251     if(appData.icsActive) gameInfo.variant = VariantNormal;
12252     currentMove = forwardMostMove = backwardMostMove = 0;
12253     MarkTargetSquares(1);
12254     InitPosition(redraw);
12255     for (i = 0; i < MAX_MOVES; i++) {
12256         if (commentList[i] != NULL) {
12257             free(commentList[i]);
12258             commentList[i] = NULL;
12259         }
12260     }
12261     ResetClocks();
12262     timeRemaining[0][0] = whiteTimeRemaining;
12263     timeRemaining[1][0] = blackTimeRemaining;
12264
12265     if (first.pr == NoProc) {
12266         StartChessProgram(&first);
12267     }
12268     if (init) {
12269             InitChessProgram(&first, startedFromSetupPosition);
12270     }
12271     DisplayTitle("");
12272     DisplayMessage("", "");
12273     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12274     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12275     ClearMap();        // [HGM] exclude: invalidate map
12276 }
12277
12278 void
12279 AutoPlayGameLoop ()
12280 {
12281     for (;;) {
12282         if (!AutoPlayOneMove())
12283           return;
12284         if (matchMode || appData.timeDelay == 0)
12285           continue;
12286         if (appData.timeDelay < 0)
12287           return;
12288         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12289         break;
12290     }
12291 }
12292
12293 void
12294 AnalyzeNextGame()
12295 {
12296     ReloadGame(1); // next game
12297 }
12298
12299 int
12300 AutoPlayOneMove ()
12301 {
12302     int fromX, fromY, toX, toY;
12303
12304     if (appData.debugMode) {
12305       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12306     }
12307
12308     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12309       return FALSE;
12310
12311     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12312       pvInfoList[currentMove].depth = programStats.depth;
12313       pvInfoList[currentMove].score = programStats.score;
12314       pvInfoList[currentMove].time  = 0;
12315       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12316       else { // append analysis of final position as comment
12317         char buf[MSG_SIZ];
12318         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12319         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12320       }
12321       programStats.depth = 0;
12322     }
12323
12324     if (currentMove >= forwardMostMove) {
12325       if(gameMode == AnalyzeFile) {
12326           if(appData.loadGameIndex == -1) {
12327             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12328           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12329           } else {
12330           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12331         }
12332       }
12333 //      gameMode = EndOfGame;
12334 //      ModeHighlight();
12335
12336       /* [AS] Clear current move marker at the end of a game */
12337       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12338
12339       return FALSE;
12340     }
12341
12342     toX = moveList[currentMove][2] - AAA;
12343     toY = moveList[currentMove][3] - ONE;
12344
12345     if (moveList[currentMove][1] == '@') {
12346         if (appData.highlightLastMove) {
12347             SetHighlights(-1, -1, toX, toY);
12348         }
12349     } else {
12350         fromX = moveList[currentMove][0] - AAA;
12351         fromY = moveList[currentMove][1] - ONE;
12352
12353         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12354
12355         if(moveList[currentMove][4] == ';') { // multi-leg
12356             killX = moveList[currentMove][5] - AAA;
12357             killY = moveList[currentMove][6] - ONE;
12358         }
12359         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12360         killX = killY = -1;
12361
12362         if (appData.highlightLastMove) {
12363             SetHighlights(fromX, fromY, toX, toY);
12364         }
12365     }
12366     DisplayMove(currentMove);
12367     SendMoveToProgram(currentMove++, &first);
12368     DisplayBothClocks();
12369     DrawPosition(FALSE, boards[currentMove]);
12370     // [HGM] PV info: always display, routine tests if empty
12371     DisplayComment(currentMove - 1, commentList[currentMove]);
12372     return TRUE;
12373 }
12374
12375
12376 int
12377 LoadGameOneMove (ChessMove readAhead)
12378 {
12379     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12380     char promoChar = NULLCHAR;
12381     ChessMove moveType;
12382     char move[MSG_SIZ];
12383     char *p, *q;
12384
12385     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12386         gameMode != AnalyzeMode && gameMode != Training) {
12387         gameFileFP = NULL;
12388         return FALSE;
12389     }
12390
12391     yyboardindex = forwardMostMove;
12392     if (readAhead != EndOfFile) {
12393       moveType = readAhead;
12394     } else {
12395       if (gameFileFP == NULL)
12396           return FALSE;
12397       moveType = (ChessMove) Myylex();
12398     }
12399
12400     done = FALSE;
12401     switch (moveType) {
12402       case Comment:
12403         if (appData.debugMode)
12404           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12405         p = yy_text;
12406
12407         /* append the comment but don't display it */
12408         AppendComment(currentMove, p, FALSE);
12409         return TRUE;
12410
12411       case WhiteCapturesEnPassant:
12412       case BlackCapturesEnPassant:
12413       case WhitePromotion:
12414       case BlackPromotion:
12415       case WhiteNonPromotion:
12416       case BlackNonPromotion:
12417       case NormalMove:
12418       case FirstLeg:
12419       case WhiteKingSideCastle:
12420       case WhiteQueenSideCastle:
12421       case BlackKingSideCastle:
12422       case BlackQueenSideCastle:
12423       case WhiteKingSideCastleWild:
12424       case WhiteQueenSideCastleWild:
12425       case BlackKingSideCastleWild:
12426       case BlackQueenSideCastleWild:
12427       /* PUSH Fabien */
12428       case WhiteHSideCastleFR:
12429       case WhiteASideCastleFR:
12430       case BlackHSideCastleFR:
12431       case BlackASideCastleFR:
12432       /* POP Fabien */
12433         if (appData.debugMode)
12434           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12435         fromX = currentMoveString[0] - AAA;
12436         fromY = currentMoveString[1] - ONE;
12437         toX = currentMoveString[2] - AAA;
12438         toY = currentMoveString[3] - ONE;
12439         promoChar = currentMoveString[4];
12440         if(promoChar == ';') promoChar = currentMoveString[7];
12441         break;
12442
12443       case WhiteDrop:
12444       case BlackDrop:
12445         if (appData.debugMode)
12446           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12447         fromX = moveType == WhiteDrop ?
12448           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12449         (int) CharToPiece(ToLower(currentMoveString[0]));
12450         fromY = DROP_RANK;
12451         toX = currentMoveString[2] - AAA;
12452         toY = currentMoveString[3] - ONE;
12453         break;
12454
12455       case WhiteWins:
12456       case BlackWins:
12457       case GameIsDrawn:
12458       case GameUnfinished:
12459         if (appData.debugMode)
12460           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12461         p = strchr(yy_text, '{');
12462         if (p == NULL) p = strchr(yy_text, '(');
12463         if (p == NULL) {
12464             p = yy_text;
12465             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12466         } else {
12467             q = strchr(p, *p == '{' ? '}' : ')');
12468             if (q != NULL) *q = NULLCHAR;
12469             p++;
12470         }
12471         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12472         GameEnds(moveType, p, GE_FILE);
12473         done = TRUE;
12474         if (cmailMsgLoaded) {
12475             ClearHighlights();
12476             flipView = WhiteOnMove(currentMove);
12477             if (moveType == GameUnfinished) flipView = !flipView;
12478             if (appData.debugMode)
12479               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12480         }
12481         break;
12482
12483       case EndOfFile:
12484         if (appData.debugMode)
12485           fprintf(debugFP, "Parser hit end of file\n");
12486         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12487           case MT_NONE:
12488           case MT_CHECK:
12489             break;
12490           case MT_CHECKMATE:
12491           case MT_STAINMATE:
12492             if (WhiteOnMove(currentMove)) {
12493                 GameEnds(BlackWins, "Black mates", GE_FILE);
12494             } else {
12495                 GameEnds(WhiteWins, "White mates", GE_FILE);
12496             }
12497             break;
12498           case MT_STALEMATE:
12499             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12500             break;
12501         }
12502         done = TRUE;
12503         break;
12504
12505       case MoveNumberOne:
12506         if (lastLoadGameStart == GNUChessGame) {
12507             /* GNUChessGames have numbers, but they aren't move numbers */
12508             if (appData.debugMode)
12509               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12510                       yy_text, (int) moveType);
12511             return LoadGameOneMove(EndOfFile); /* tail recursion */
12512         }
12513         /* else fall thru */
12514
12515       case XBoardGame:
12516       case GNUChessGame:
12517       case PGNTag:
12518         /* Reached start of next game in file */
12519         if (appData.debugMode)
12520           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12521         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12522           case MT_NONE:
12523           case MT_CHECK:
12524             break;
12525           case MT_CHECKMATE:
12526           case MT_STAINMATE:
12527             if (WhiteOnMove(currentMove)) {
12528                 GameEnds(BlackWins, "Black mates", GE_FILE);
12529             } else {
12530                 GameEnds(WhiteWins, "White mates", GE_FILE);
12531             }
12532             break;
12533           case MT_STALEMATE:
12534             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12535             break;
12536         }
12537         done = TRUE;
12538         break;
12539
12540       case PositionDiagram:     /* should not happen; ignore */
12541       case ElapsedTime:         /* ignore */
12542       case NAG:                 /* ignore */
12543         if (appData.debugMode)
12544           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12545                   yy_text, (int) moveType);
12546         return LoadGameOneMove(EndOfFile); /* tail recursion */
12547
12548       case IllegalMove:
12549         if (appData.testLegality) {
12550             if (appData.debugMode)
12551               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12552             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12553                     (forwardMostMove / 2) + 1,
12554                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12555             DisplayError(move, 0);
12556             done = TRUE;
12557         } else {
12558             if (appData.debugMode)
12559               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12560                       yy_text, currentMoveString);
12561             if(currentMoveString[1] == '@') {
12562                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12563                 fromY = DROP_RANK;
12564             } else {
12565                 fromX = currentMoveString[0] - AAA;
12566                 fromY = currentMoveString[1] - ONE;
12567             }
12568             toX = currentMoveString[2] - AAA;
12569             toY = currentMoveString[3] - ONE;
12570             promoChar = currentMoveString[4];
12571         }
12572         break;
12573
12574       case AmbiguousMove:
12575         if (appData.debugMode)
12576           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12577         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12578                 (forwardMostMove / 2) + 1,
12579                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12580         DisplayError(move, 0);
12581         done = TRUE;
12582         break;
12583
12584       default:
12585       case ImpossibleMove:
12586         if (appData.debugMode)
12587           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12588         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12589                 (forwardMostMove / 2) + 1,
12590                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12591         DisplayError(move, 0);
12592         done = TRUE;
12593         break;
12594     }
12595
12596     if (done) {
12597         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12598             DrawPosition(FALSE, boards[currentMove]);
12599             DisplayBothClocks();
12600             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12601               DisplayComment(currentMove - 1, commentList[currentMove]);
12602         }
12603         (void) StopLoadGameTimer();
12604         gameFileFP = NULL;
12605         cmailOldMove = forwardMostMove;
12606         return FALSE;
12607     } else {
12608         /* currentMoveString is set as a side-effect of yylex */
12609
12610         thinkOutput[0] = NULLCHAR;
12611         MakeMove(fromX, fromY, toX, toY, promoChar);
12612         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12613         currentMove = forwardMostMove;
12614         return TRUE;
12615     }
12616 }
12617
12618 /* Load the nth game from the given file */
12619 int
12620 LoadGameFromFile (char *filename, int n, char *title, int useList)
12621 {
12622     FILE *f;
12623     char buf[MSG_SIZ];
12624
12625     if (strcmp(filename, "-") == 0) {
12626         f = stdin;
12627         title = "stdin";
12628     } else {
12629         f = fopen(filename, "rb");
12630         if (f == NULL) {
12631           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12632             DisplayError(buf, errno);
12633             return FALSE;
12634         }
12635     }
12636     if (fseek(f, 0, 0) == -1) {
12637         /* f is not seekable; probably a pipe */
12638         useList = FALSE;
12639     }
12640     if (useList && n == 0) {
12641         int error = GameListBuild(f);
12642         if (error) {
12643             DisplayError(_("Cannot build game list"), error);
12644         } else if (!ListEmpty(&gameList) &&
12645                    ((ListGame *) gameList.tailPred)->number > 1) {
12646             GameListPopUp(f, title);
12647             return TRUE;
12648         }
12649         GameListDestroy();
12650         n = 1;
12651     }
12652     if (n == 0) n = 1;
12653     return LoadGame(f, n, title, FALSE);
12654 }
12655
12656
12657 void
12658 MakeRegisteredMove ()
12659 {
12660     int fromX, fromY, toX, toY;
12661     char promoChar;
12662     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12663         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12664           case CMAIL_MOVE:
12665           case CMAIL_DRAW:
12666             if (appData.debugMode)
12667               fprintf(debugFP, "Restoring %s for game %d\n",
12668                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12669
12670             thinkOutput[0] = NULLCHAR;
12671             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12672             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12673             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12674             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12675             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12676             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12677             MakeMove(fromX, fromY, toX, toY, promoChar);
12678             ShowMove(fromX, fromY, toX, toY);
12679
12680             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12681               case MT_NONE:
12682               case MT_CHECK:
12683                 break;
12684
12685               case MT_CHECKMATE:
12686               case MT_STAINMATE:
12687                 if (WhiteOnMove(currentMove)) {
12688                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12689                 } else {
12690                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12691                 }
12692                 break;
12693
12694               case MT_STALEMATE:
12695                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12696                 break;
12697             }
12698
12699             break;
12700
12701           case CMAIL_RESIGN:
12702             if (WhiteOnMove(currentMove)) {
12703                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12704             } else {
12705                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12706             }
12707             break;
12708
12709           case CMAIL_ACCEPT:
12710             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12711             break;
12712
12713           default:
12714             break;
12715         }
12716     }
12717
12718     return;
12719 }
12720
12721 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12722 int
12723 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12724 {
12725     int retVal;
12726
12727     if (gameNumber > nCmailGames) {
12728         DisplayError(_("No more games in this message"), 0);
12729         return FALSE;
12730     }
12731     if (f == lastLoadGameFP) {
12732         int offset = gameNumber - lastLoadGameNumber;
12733         if (offset == 0) {
12734             cmailMsg[0] = NULLCHAR;
12735             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12736                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12737                 nCmailMovesRegistered--;
12738             }
12739             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12740             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12741                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12742             }
12743         } else {
12744             if (! RegisterMove()) return FALSE;
12745         }
12746     }
12747
12748     retVal = LoadGame(f, gameNumber, title, useList);
12749
12750     /* Make move registered during previous look at this game, if any */
12751     MakeRegisteredMove();
12752
12753     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12754         commentList[currentMove]
12755           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12756         DisplayComment(currentMove - 1, commentList[currentMove]);
12757     }
12758
12759     return retVal;
12760 }
12761
12762 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12763 int
12764 ReloadGame (int offset)
12765 {
12766     int gameNumber = lastLoadGameNumber + offset;
12767     if (lastLoadGameFP == NULL) {
12768         DisplayError(_("No game has been loaded yet"), 0);
12769         return FALSE;
12770     }
12771     if (gameNumber <= 0) {
12772         DisplayError(_("Can't back up any further"), 0);
12773         return FALSE;
12774     }
12775     if (cmailMsgLoaded) {
12776         return CmailLoadGame(lastLoadGameFP, gameNumber,
12777                              lastLoadGameTitle, lastLoadGameUseList);
12778     } else {
12779         return LoadGame(lastLoadGameFP, gameNumber,
12780                         lastLoadGameTitle, lastLoadGameUseList);
12781     }
12782 }
12783
12784 int keys[EmptySquare+1];
12785
12786 int
12787 PositionMatches (Board b1, Board b2)
12788 {
12789     int r, f, sum=0;
12790     switch(appData.searchMode) {
12791         case 1: return CompareWithRights(b1, b2);
12792         case 2:
12793             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12794                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12795             }
12796             return TRUE;
12797         case 3:
12798             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12799               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12800                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12801             }
12802             return sum==0;
12803         case 4:
12804             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12805                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12806             }
12807             return sum==0;
12808     }
12809     return TRUE;
12810 }
12811
12812 #define Q_PROMO  4
12813 #define Q_EP     3
12814 #define Q_BCASTL 2
12815 #define Q_WCASTL 1
12816
12817 int pieceList[256], quickBoard[256];
12818 ChessSquare pieceType[256] = { EmptySquare };
12819 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12820 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12821 int soughtTotal, turn;
12822 Boolean epOK, flipSearch;
12823
12824 typedef struct {
12825     unsigned char piece, to;
12826 } Move;
12827
12828 #define DSIZE (250000)
12829
12830 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12831 Move *moveDatabase = initialSpace;
12832 unsigned int movePtr, dataSize = DSIZE;
12833
12834 int
12835 MakePieceList (Board board, int *counts)
12836 {
12837     int r, f, n=Q_PROMO, total=0;
12838     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12839     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12840         int sq = f + (r<<4);
12841         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12842             quickBoard[sq] = ++n;
12843             pieceList[n] = sq;
12844             pieceType[n] = board[r][f];
12845             counts[board[r][f]]++;
12846             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12847             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12848             total++;
12849         }
12850     }
12851     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12852     return total;
12853 }
12854
12855 void
12856 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12857 {
12858     int sq = fromX + (fromY<<4);
12859     int piece = quickBoard[sq], rook;
12860     quickBoard[sq] = 0;
12861     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12862     if(piece == pieceList[1] && fromY == toY) {
12863       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12864         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12865         moveDatabase[movePtr++].piece = Q_WCASTL;
12866         quickBoard[sq] = piece;
12867         piece = quickBoard[from]; quickBoard[from] = 0;
12868         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12869       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12870         quickBoard[sq] = 0; // remove Rook
12871         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12872         moveDatabase[movePtr++].piece = Q_WCASTL;
12873         quickBoard[sq] = pieceList[1]; // put King
12874         piece = rook;
12875         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12876       }
12877     } else
12878     if(piece == pieceList[2] && fromY == toY) {
12879       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12880         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12881         moveDatabase[movePtr++].piece = Q_BCASTL;
12882         quickBoard[sq] = piece;
12883         piece = quickBoard[from]; quickBoard[from] = 0;
12884         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12885       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12886         quickBoard[sq] = 0; // remove Rook
12887         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12888         moveDatabase[movePtr++].piece = Q_BCASTL;
12889         quickBoard[sq] = pieceList[2]; // put King
12890         piece = rook;
12891         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12892       }
12893     } else
12894     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12895         quickBoard[(fromY<<4)+toX] = 0;
12896         moveDatabase[movePtr].piece = Q_EP;
12897         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12898         moveDatabase[movePtr].to = sq;
12899     } else
12900     if(promoPiece != pieceType[piece]) {
12901         moveDatabase[movePtr++].piece = Q_PROMO;
12902         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12903     }
12904     moveDatabase[movePtr].piece = piece;
12905     quickBoard[sq] = piece;
12906     movePtr++;
12907 }
12908
12909 int
12910 PackGame (Board board)
12911 {
12912     Move *newSpace = NULL;
12913     moveDatabase[movePtr].piece = 0; // terminate previous game
12914     if(movePtr > dataSize) {
12915         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12916         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12917         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12918         if(newSpace) {
12919             int i;
12920             Move *p = moveDatabase, *q = newSpace;
12921             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12922             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12923             moveDatabase = newSpace;
12924         } else { // calloc failed, we must be out of memory. Too bad...
12925             dataSize = 0; // prevent calloc events for all subsequent games
12926             return 0;     // and signal this one isn't cached
12927         }
12928     }
12929     movePtr++;
12930     MakePieceList(board, counts);
12931     return movePtr;
12932 }
12933
12934 int
12935 QuickCompare (Board board, int *minCounts, int *maxCounts)
12936 {   // compare according to search mode
12937     int r, f;
12938     switch(appData.searchMode)
12939     {
12940       case 1: // exact position match
12941         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12942         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12943             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12944         }
12945         break;
12946       case 2: // can have extra material on empty squares
12947         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12948             if(board[r][f] == EmptySquare) continue;
12949             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12950         }
12951         break;
12952       case 3: // material with exact Pawn structure
12953         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12954             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12955             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12956         } // fall through to material comparison
12957       case 4: // exact material
12958         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12959         break;
12960       case 6: // material range with given imbalance
12961         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12962         // fall through to range comparison
12963       case 5: // material range
12964         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12965     }
12966     return TRUE;
12967 }
12968
12969 int
12970 QuickScan (Board board, Move *move)
12971 {   // reconstruct game,and compare all positions in it
12972     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12973     do {
12974         int piece = move->piece;
12975         int to = move->to, from = pieceList[piece];
12976         if(found < 0) { // if already found just scan to game end for final piece count
12977           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12978            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12979            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12980                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12981             ) {
12982             static int lastCounts[EmptySquare+1];
12983             int i;
12984             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12985             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12986           } else stretch = 0;
12987           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12988           if(found >= 0 && !appData.minPieces) return found;
12989         }
12990         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12991           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12992           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12993             piece = (++move)->piece;
12994             from = pieceList[piece];
12995             counts[pieceType[piece]]--;
12996             pieceType[piece] = (ChessSquare) move->to;
12997             counts[move->to]++;
12998           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12999             counts[pieceType[quickBoard[to]]]--;
13000             quickBoard[to] = 0; total--;
13001             move++;
13002             continue;
13003           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
13004             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
13005             from  = pieceList[piece]; // so this must be King
13006             quickBoard[from] = 0;
13007             pieceList[piece] = to;
13008             from = pieceList[(++move)->piece]; // for FRC this has to be done here
13009             quickBoard[from] = 0; // rook
13010             quickBoard[to] = piece;
13011             to = move->to; piece = move->piece;
13012             goto aftercastle;
13013           }
13014         }
13015         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
13016         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
13017         quickBoard[from] = 0;
13018       aftercastle:
13019         quickBoard[to] = piece;
13020         pieceList[piece] = to;
13021         cnt++; turn ^= 3;
13022         move++;
13023     } while(1);
13024 }
13025
13026 void
13027 InitSearch ()
13028 {
13029     int r, f;
13030     flipSearch = FALSE;
13031     CopyBoard(soughtBoard, boards[currentMove]);
13032     soughtTotal = MakePieceList(soughtBoard, maxSought);
13033     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
13034     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
13035     CopyBoard(reverseBoard, boards[currentMove]);
13036     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13037         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
13038         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
13039         reverseBoard[r][f] = piece;
13040     }
13041     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
13042     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
13043     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
13044                  || (boards[currentMove][CASTLING][2] == NoRights ||
13045                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
13046                  && (boards[currentMove][CASTLING][5] == NoRights ||
13047                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
13048       ) {
13049         flipSearch = TRUE;
13050         CopyBoard(flipBoard, soughtBoard);
13051         CopyBoard(rotateBoard, reverseBoard);
13052         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13053             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
13054             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
13055         }
13056     }
13057     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
13058     if(appData.searchMode >= 5) {
13059         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
13060         MakePieceList(soughtBoard, minSought);
13061         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
13062     }
13063     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
13064         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
13065 }
13066
13067 GameInfo dummyInfo;
13068 static int creatingBook;
13069
13070 int
13071 GameContainsPosition (FILE *f, ListGame *lg)
13072 {
13073     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
13074     int fromX, fromY, toX, toY;
13075     char promoChar;
13076     static int initDone=FALSE;
13077
13078     // weed out games based on numerical tag comparison
13079     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
13080     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
13081     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
13082     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
13083     if(!initDone) {
13084         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
13085         initDone = TRUE;
13086     }
13087     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
13088     else CopyBoard(boards[scratch], initialPosition); // default start position
13089     if(lg->moves) {
13090         turn = btm + 1;
13091         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
13092         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
13093     }
13094     if(btm) plyNr++;
13095     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13096     fseek(f, lg->offset, 0);
13097     yynewfile(f);
13098     while(1) {
13099         yyboardindex = scratch;
13100         quickFlag = plyNr+1;
13101         next = Myylex();
13102         quickFlag = 0;
13103         switch(next) {
13104             case PGNTag:
13105                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13106             default:
13107                 continue;
13108
13109             case XBoardGame:
13110             case GNUChessGame:
13111                 if(plyNr) return -1; // after we have seen moves, this is for new game
13112               continue;
13113
13114             case AmbiguousMove: // we cannot reconstruct the game beyond these two
13115             case ImpossibleMove:
13116             case WhiteWins: // game ends here with these four
13117             case BlackWins:
13118             case GameIsDrawn:
13119             case GameUnfinished:
13120                 return -1;
13121
13122             case IllegalMove:
13123                 if(appData.testLegality) return -1;
13124             case WhiteCapturesEnPassant:
13125             case BlackCapturesEnPassant:
13126             case WhitePromotion:
13127             case BlackPromotion:
13128             case WhiteNonPromotion:
13129             case BlackNonPromotion:
13130             case NormalMove:
13131             case FirstLeg:
13132             case WhiteKingSideCastle:
13133             case WhiteQueenSideCastle:
13134             case BlackKingSideCastle:
13135             case BlackQueenSideCastle:
13136             case WhiteKingSideCastleWild:
13137             case WhiteQueenSideCastleWild:
13138             case BlackKingSideCastleWild:
13139             case BlackQueenSideCastleWild:
13140             case WhiteHSideCastleFR:
13141             case WhiteASideCastleFR:
13142             case BlackHSideCastleFR:
13143             case BlackASideCastleFR:
13144                 fromX = currentMoveString[0] - AAA;
13145                 fromY = currentMoveString[1] - ONE;
13146                 toX = currentMoveString[2] - AAA;
13147                 toY = currentMoveString[3] - ONE;
13148                 promoChar = currentMoveString[4];
13149                 break;
13150             case WhiteDrop:
13151             case BlackDrop:
13152                 fromX = next == WhiteDrop ?
13153                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
13154                   (int) CharToPiece(ToLower(currentMoveString[0]));
13155                 fromY = DROP_RANK;
13156                 toX = currentMoveString[2] - AAA;
13157                 toY = currentMoveString[3] - ONE;
13158                 promoChar = 0;
13159                 break;
13160         }
13161         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13162         plyNr++;
13163         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13164         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13165         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13166         if(appData.findMirror) {
13167             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13168             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13169         }
13170     }
13171 }
13172
13173 /* Load the nth game from open file f */
13174 int
13175 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13176 {
13177     ChessMove cm;
13178     char buf[MSG_SIZ];
13179     int gn = gameNumber;
13180     ListGame *lg = NULL;
13181     int numPGNTags = 0, i;
13182     int err, pos = -1;
13183     GameMode oldGameMode;
13184     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13185     char oldName[MSG_SIZ];
13186
13187     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13188
13189     if (appData.debugMode)
13190         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13191
13192     if (gameMode == Training )
13193         SetTrainingModeOff();
13194
13195     oldGameMode = gameMode;
13196     if (gameMode != BeginningOfGame) {
13197       Reset(FALSE, TRUE);
13198     }
13199     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13200
13201     gameFileFP = f;
13202     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13203         fclose(lastLoadGameFP);
13204     }
13205
13206     if (useList) {
13207         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13208
13209         if (lg) {
13210             fseek(f, lg->offset, 0);
13211             GameListHighlight(gameNumber);
13212             pos = lg->position;
13213             gn = 1;
13214         }
13215         else {
13216             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13217               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13218             else
13219             DisplayError(_("Game number out of range"), 0);
13220             return FALSE;
13221         }
13222     } else {
13223         GameListDestroy();
13224         if (fseek(f, 0, 0) == -1) {
13225             if (f == lastLoadGameFP ?
13226                 gameNumber == lastLoadGameNumber + 1 :
13227                 gameNumber == 1) {
13228                 gn = 1;
13229             } else {
13230                 DisplayError(_("Can't seek on game file"), 0);
13231                 return FALSE;
13232             }
13233         }
13234     }
13235     lastLoadGameFP = f;
13236     lastLoadGameNumber = gameNumber;
13237     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13238     lastLoadGameUseList = useList;
13239
13240     yynewfile(f);
13241
13242     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13243       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13244                 lg->gameInfo.black);
13245             DisplayTitle(buf);
13246     } else if (*title != NULLCHAR) {
13247         if (gameNumber > 1) {
13248           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13249             DisplayTitle(buf);
13250         } else {
13251             DisplayTitle(title);
13252         }
13253     }
13254
13255     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13256         gameMode = PlayFromGameFile;
13257         ModeHighlight();
13258     }
13259
13260     currentMove = forwardMostMove = backwardMostMove = 0;
13261     CopyBoard(boards[0], initialPosition);
13262     StopClocks();
13263
13264     /*
13265      * Skip the first gn-1 games in the file.
13266      * Also skip over anything that precedes an identifiable
13267      * start of game marker, to avoid being confused by
13268      * garbage at the start of the file.  Currently
13269      * recognized start of game markers are the move number "1",
13270      * the pattern "gnuchess .* game", the pattern
13271      * "^[#;%] [^ ]* game file", and a PGN tag block.
13272      * A game that starts with one of the latter two patterns
13273      * will also have a move number 1, possibly
13274      * following a position diagram.
13275      * 5-4-02: Let's try being more lenient and allowing a game to
13276      * start with an unnumbered move.  Does that break anything?
13277      */
13278     cm = lastLoadGameStart = EndOfFile;
13279     while (gn > 0) {
13280         yyboardindex = forwardMostMove;
13281         cm = (ChessMove) Myylex();
13282         switch (cm) {
13283           case EndOfFile:
13284             if (cmailMsgLoaded) {
13285                 nCmailGames = CMAIL_MAX_GAMES - gn;
13286             } else {
13287                 Reset(TRUE, TRUE);
13288                 DisplayError(_("Game not found in file"), 0);
13289             }
13290             return FALSE;
13291
13292           case GNUChessGame:
13293           case XBoardGame:
13294             gn--;
13295             lastLoadGameStart = cm;
13296             break;
13297
13298           case MoveNumberOne:
13299             switch (lastLoadGameStart) {
13300               case GNUChessGame:
13301               case XBoardGame:
13302               case PGNTag:
13303                 break;
13304               case MoveNumberOne:
13305               case EndOfFile:
13306                 gn--;           /* count this game */
13307                 lastLoadGameStart = cm;
13308                 break;
13309               default:
13310                 /* impossible */
13311                 break;
13312             }
13313             break;
13314
13315           case PGNTag:
13316             switch (lastLoadGameStart) {
13317               case GNUChessGame:
13318               case PGNTag:
13319               case MoveNumberOne:
13320               case EndOfFile:
13321                 gn--;           /* count this game */
13322                 lastLoadGameStart = cm;
13323                 break;
13324               case XBoardGame:
13325                 lastLoadGameStart = cm; /* game counted already */
13326                 break;
13327               default:
13328                 /* impossible */
13329                 break;
13330             }
13331             if (gn > 0) {
13332                 do {
13333                     yyboardindex = forwardMostMove;
13334                     cm = (ChessMove) Myylex();
13335                 } while (cm == PGNTag || cm == Comment);
13336             }
13337             break;
13338
13339           case WhiteWins:
13340           case BlackWins:
13341           case GameIsDrawn:
13342             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13343                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13344                     != CMAIL_OLD_RESULT) {
13345                     nCmailResults ++ ;
13346                     cmailResult[  CMAIL_MAX_GAMES
13347                                 - gn - 1] = CMAIL_OLD_RESULT;
13348                 }
13349             }
13350             break;
13351
13352           case NormalMove:
13353           case FirstLeg:
13354             /* Only a NormalMove can be at the start of a game
13355              * without a position diagram. */
13356             if (lastLoadGameStart == EndOfFile ) {
13357               gn--;
13358               lastLoadGameStart = MoveNumberOne;
13359             }
13360             break;
13361
13362           default:
13363             break;
13364         }
13365     }
13366
13367     if (appData.debugMode)
13368       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13369
13370     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13371
13372     if (cm == XBoardGame) {
13373         /* Skip any header junk before position diagram and/or move 1 */
13374         for (;;) {
13375             yyboardindex = forwardMostMove;
13376             cm = (ChessMove) Myylex();
13377
13378             if (cm == EndOfFile ||
13379                 cm == GNUChessGame || cm == XBoardGame) {
13380                 /* Empty game; pretend end-of-file and handle later */
13381                 cm = EndOfFile;
13382                 break;
13383             }
13384
13385             if (cm == MoveNumberOne || cm == PositionDiagram ||
13386                 cm == PGNTag || cm == Comment)
13387               break;
13388         }
13389     } else if (cm == GNUChessGame) {
13390         if (gameInfo.event != NULL) {
13391             free(gameInfo.event);
13392         }
13393         gameInfo.event = StrSave(yy_text);
13394     }
13395
13396     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13397     while (cm == PGNTag) {
13398         if (appData.debugMode)
13399           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13400         err = ParsePGNTag(yy_text, &gameInfo);
13401         if (!err) numPGNTags++;
13402
13403         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13404         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13405             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13406             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13407             InitPosition(TRUE);
13408             oldVariant = gameInfo.variant;
13409             if (appData.debugMode)
13410               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13411         }
13412
13413
13414         if (gameInfo.fen != NULL) {
13415           Board initial_position;
13416           startedFromSetupPosition = TRUE;
13417           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13418             Reset(TRUE, TRUE);
13419             DisplayError(_("Bad FEN position in file"), 0);
13420             return FALSE;
13421           }
13422           CopyBoard(boards[0], initial_position);
13423           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13424             CopyBoard(initialPosition, initial_position);
13425           if (blackPlaysFirst) {
13426             currentMove = forwardMostMove = backwardMostMove = 1;
13427             CopyBoard(boards[1], initial_position);
13428             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13429             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13430             timeRemaining[0][1] = whiteTimeRemaining;
13431             timeRemaining[1][1] = blackTimeRemaining;
13432             if (commentList[0] != NULL) {
13433               commentList[1] = commentList[0];
13434               commentList[0] = NULL;
13435             }
13436           } else {
13437             currentMove = forwardMostMove = backwardMostMove = 0;
13438           }
13439           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13440           {   int i;
13441               initialRulePlies = FENrulePlies;
13442               for( i=0; i< nrCastlingRights; i++ )
13443                   initialRights[i] = initial_position[CASTLING][i];
13444           }
13445           yyboardindex = forwardMostMove;
13446           free(gameInfo.fen);
13447           gameInfo.fen = NULL;
13448         }
13449
13450         yyboardindex = forwardMostMove;
13451         cm = (ChessMove) Myylex();
13452
13453         /* Handle comments interspersed among the tags */
13454         while (cm == Comment) {
13455             char *p;
13456             if (appData.debugMode)
13457               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13458             p = yy_text;
13459             AppendComment(currentMove, p, FALSE);
13460             yyboardindex = forwardMostMove;
13461             cm = (ChessMove) Myylex();
13462         }
13463     }
13464
13465     /* don't rely on existence of Event tag since if game was
13466      * pasted from clipboard the Event tag may not exist
13467      */
13468     if (numPGNTags > 0){
13469         char *tags;
13470         if (gameInfo.variant == VariantNormal) {
13471           VariantClass v = StringToVariant(gameInfo.event);
13472           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13473           if(v < VariantShogi) gameInfo.variant = v;
13474         }
13475         if (!matchMode) {
13476           if( appData.autoDisplayTags ) {
13477             tags = PGNTags(&gameInfo);
13478             TagsPopUp(tags, CmailMsg());
13479             free(tags);
13480           }
13481         }
13482     } else {
13483         /* Make something up, but don't display it now */
13484         SetGameInfo();
13485         TagsPopDown();
13486     }
13487
13488     if (cm == PositionDiagram) {
13489         int i, j;
13490         char *p;
13491         Board initial_position;
13492
13493         if (appData.debugMode)
13494           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13495
13496         if (!startedFromSetupPosition) {
13497             p = yy_text;
13498             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13499               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13500                 switch (*p) {
13501                   case '{':
13502                   case '[':
13503                   case '-':
13504                   case ' ':
13505                   case '\t':
13506                   case '\n':
13507                   case '\r':
13508                     break;
13509                   default:
13510                     initial_position[i][j++] = CharToPiece(*p);
13511                     break;
13512                 }
13513             while (*p == ' ' || *p == '\t' ||
13514                    *p == '\n' || *p == '\r') p++;
13515
13516             if (strncmp(p, "black", strlen("black"))==0)
13517               blackPlaysFirst = TRUE;
13518             else
13519               blackPlaysFirst = FALSE;
13520             startedFromSetupPosition = TRUE;
13521
13522             CopyBoard(boards[0], initial_position);
13523             if (blackPlaysFirst) {
13524                 currentMove = forwardMostMove = backwardMostMove = 1;
13525                 CopyBoard(boards[1], initial_position);
13526                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13527                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13528                 timeRemaining[0][1] = whiteTimeRemaining;
13529                 timeRemaining[1][1] = blackTimeRemaining;
13530                 if (commentList[0] != NULL) {
13531                     commentList[1] = commentList[0];
13532                     commentList[0] = NULL;
13533                 }
13534             } else {
13535                 currentMove = forwardMostMove = backwardMostMove = 0;
13536             }
13537         }
13538         yyboardindex = forwardMostMove;
13539         cm = (ChessMove) Myylex();
13540     }
13541
13542   if(!creatingBook) {
13543     if (first.pr == NoProc) {
13544         StartChessProgram(&first);
13545     }
13546     InitChessProgram(&first, FALSE);
13547     if(gameInfo.variant == VariantUnknown && *oldName) {
13548         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13549         gameInfo.variant = v;
13550     }
13551     SendToProgram("force\n", &first);
13552     if (startedFromSetupPosition) {
13553         SendBoard(&first, forwardMostMove);
13554     if (appData.debugMode) {
13555         fprintf(debugFP, "Load Game\n");
13556     }
13557         DisplayBothClocks();
13558     }
13559   }
13560
13561     /* [HGM] server: flag to write setup moves in broadcast file as one */
13562     loadFlag = appData.suppressLoadMoves;
13563
13564     while (cm == Comment) {
13565         char *p;
13566         if (appData.debugMode)
13567           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13568         p = yy_text;
13569         AppendComment(currentMove, p, FALSE);
13570         yyboardindex = forwardMostMove;
13571         cm = (ChessMove) Myylex();
13572     }
13573
13574     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13575         cm == WhiteWins || cm == BlackWins ||
13576         cm == GameIsDrawn || cm == GameUnfinished) {
13577         DisplayMessage("", _("No moves in game"));
13578         if (cmailMsgLoaded) {
13579             if (appData.debugMode)
13580               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13581             ClearHighlights();
13582             flipView = FALSE;
13583         }
13584         DrawPosition(FALSE, boards[currentMove]);
13585         DisplayBothClocks();
13586         gameMode = EditGame;
13587         ModeHighlight();
13588         gameFileFP = NULL;
13589         cmailOldMove = 0;
13590         return TRUE;
13591     }
13592
13593     // [HGM] PV info: routine tests if comment empty
13594     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13595         DisplayComment(currentMove - 1, commentList[currentMove]);
13596     }
13597     if (!matchMode && appData.timeDelay != 0)
13598       DrawPosition(FALSE, boards[currentMove]);
13599
13600     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13601       programStats.ok_to_send = 1;
13602     }
13603
13604     /* if the first token after the PGN tags is a move
13605      * and not move number 1, retrieve it from the parser
13606      */
13607     if (cm != MoveNumberOne)
13608         LoadGameOneMove(cm);
13609
13610     /* load the remaining moves from the file */
13611     while (LoadGameOneMove(EndOfFile)) {
13612       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13613       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13614     }
13615
13616     /* rewind to the start of the game */
13617     currentMove = backwardMostMove;
13618
13619     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13620
13621     if (oldGameMode == AnalyzeFile) {
13622       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13623       AnalyzeFileEvent();
13624     } else
13625     if (oldGameMode == AnalyzeMode) {
13626       AnalyzeFileEvent();
13627     }
13628
13629     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13630         long int w, b; // [HGM] adjourn: restore saved clock times
13631         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13632         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13633             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13634             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13635         }
13636     }
13637
13638     if(creatingBook) return TRUE;
13639     if (!matchMode && pos > 0) {
13640         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13641     } else
13642     if (matchMode || appData.timeDelay == 0) {
13643       ToEndEvent();
13644     } else if (appData.timeDelay > 0) {
13645       AutoPlayGameLoop();
13646     }
13647
13648     if (appData.debugMode)
13649         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13650
13651     loadFlag = 0; /* [HGM] true game starts */
13652     return TRUE;
13653 }
13654
13655 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13656 int
13657 ReloadPosition (int offset)
13658 {
13659     int positionNumber = lastLoadPositionNumber + offset;
13660     if (lastLoadPositionFP == NULL) {
13661         DisplayError(_("No position has been loaded yet"), 0);
13662         return FALSE;
13663     }
13664     if (positionNumber <= 0) {
13665         DisplayError(_("Can't back up any further"), 0);
13666         return FALSE;
13667     }
13668     return LoadPosition(lastLoadPositionFP, positionNumber,
13669                         lastLoadPositionTitle);
13670 }
13671
13672 /* Load the nth position from the given file */
13673 int
13674 LoadPositionFromFile (char *filename, int n, char *title)
13675 {
13676     FILE *f;
13677     char buf[MSG_SIZ];
13678
13679     if (strcmp(filename, "-") == 0) {
13680         return LoadPosition(stdin, n, "stdin");
13681     } else {
13682         f = fopen(filename, "rb");
13683         if (f == NULL) {
13684             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13685             DisplayError(buf, errno);
13686             return FALSE;
13687         } else {
13688             return LoadPosition(f, n, title);
13689         }
13690     }
13691 }
13692
13693 /* Load the nth position from the given open file, and close it */
13694 int
13695 LoadPosition (FILE *f, int positionNumber, char *title)
13696 {
13697     char *p, line[MSG_SIZ];
13698     Board initial_position;
13699     int i, j, fenMode, pn;
13700
13701     if (gameMode == Training )
13702         SetTrainingModeOff();
13703
13704     if (gameMode != BeginningOfGame) {
13705         Reset(FALSE, TRUE);
13706     }
13707     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13708         fclose(lastLoadPositionFP);
13709     }
13710     if (positionNumber == 0) positionNumber = 1;
13711     lastLoadPositionFP = f;
13712     lastLoadPositionNumber = positionNumber;
13713     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13714     if (first.pr == NoProc && !appData.noChessProgram) {
13715       StartChessProgram(&first);
13716       InitChessProgram(&first, FALSE);
13717     }
13718     pn = positionNumber;
13719     if (positionNumber < 0) {
13720         /* Negative position number means to seek to that byte offset */
13721         if (fseek(f, -positionNumber, 0) == -1) {
13722             DisplayError(_("Can't seek on position file"), 0);
13723             return FALSE;
13724         };
13725         pn = 1;
13726     } else {
13727         if (fseek(f, 0, 0) == -1) {
13728             if (f == lastLoadPositionFP ?
13729                 positionNumber == lastLoadPositionNumber + 1 :
13730                 positionNumber == 1) {
13731                 pn = 1;
13732             } else {
13733                 DisplayError(_("Can't seek on position file"), 0);
13734                 return FALSE;
13735             }
13736         }
13737     }
13738     /* See if this file is FEN or old-style xboard */
13739     if (fgets(line, MSG_SIZ, f) == NULL) {
13740         DisplayError(_("Position not found in file"), 0);
13741         return FALSE;
13742     }
13743     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13744     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13745
13746     if (pn >= 2) {
13747         if (fenMode || line[0] == '#') pn--;
13748         while (pn > 0) {
13749             /* skip positions before number pn */
13750             if (fgets(line, MSG_SIZ, f) == NULL) {
13751                 Reset(TRUE, TRUE);
13752                 DisplayError(_("Position not found in file"), 0);
13753                 return FALSE;
13754             }
13755             if (fenMode || line[0] == '#') pn--;
13756         }
13757     }
13758
13759     if (fenMode) {
13760         char *p;
13761         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13762             DisplayError(_("Bad FEN position in file"), 0);
13763             return FALSE;
13764         }
13765         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13766             sscanf(p+4, "%[^;]", bestMove);
13767         } else *bestMove = NULLCHAR;
13768         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13769             sscanf(p+4, "%[^;]", avoidMove);
13770         } else *avoidMove = NULLCHAR;
13771     } else {
13772         (void) fgets(line, MSG_SIZ, f);
13773         (void) fgets(line, MSG_SIZ, f);
13774
13775         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13776             (void) fgets(line, MSG_SIZ, f);
13777             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13778                 if (*p == ' ')
13779                   continue;
13780                 initial_position[i][j++] = CharToPiece(*p);
13781             }
13782         }
13783
13784         blackPlaysFirst = FALSE;
13785         if (!feof(f)) {
13786             (void) fgets(line, MSG_SIZ, f);
13787             if (strncmp(line, "black", strlen("black"))==0)
13788               blackPlaysFirst = TRUE;
13789         }
13790     }
13791     startedFromSetupPosition = TRUE;
13792
13793     CopyBoard(boards[0], initial_position);
13794     if (blackPlaysFirst) {
13795         currentMove = forwardMostMove = backwardMostMove = 1;
13796         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13797         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13798         CopyBoard(boards[1], initial_position);
13799         DisplayMessage("", _("Black to play"));
13800     } else {
13801         currentMove = forwardMostMove = backwardMostMove = 0;
13802         DisplayMessage("", _("White to play"));
13803     }
13804     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13805     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13806         SendToProgram("force\n", &first);
13807         SendBoard(&first, forwardMostMove);
13808     }
13809     if (appData.debugMode) {
13810 int i, j;
13811   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13812   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13813         fprintf(debugFP, "Load Position\n");
13814     }
13815
13816     if (positionNumber > 1) {
13817       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13818         DisplayTitle(line);
13819     } else {
13820         DisplayTitle(title);
13821     }
13822     gameMode = EditGame;
13823     ModeHighlight();
13824     ResetClocks();
13825     timeRemaining[0][1] = whiteTimeRemaining;
13826     timeRemaining[1][1] = blackTimeRemaining;
13827     DrawPosition(FALSE, boards[currentMove]);
13828
13829     return TRUE;
13830 }
13831
13832
13833 void
13834 CopyPlayerNameIntoFileName (char **dest, char *src)
13835 {
13836     while (*src != NULLCHAR && *src != ',') {
13837         if (*src == ' ') {
13838             *(*dest)++ = '_';
13839             src++;
13840         } else {
13841             *(*dest)++ = *src++;
13842         }
13843     }
13844 }
13845
13846 char *
13847 DefaultFileName (char *ext)
13848 {
13849     static char def[MSG_SIZ];
13850     char *p;
13851
13852     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13853         p = def;
13854         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13855         *p++ = '-';
13856         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13857         *p++ = '.';
13858         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13859     } else {
13860         def[0] = NULLCHAR;
13861     }
13862     return def;
13863 }
13864
13865 /* Save the current game to the given file */
13866 int
13867 SaveGameToFile (char *filename, int append)
13868 {
13869     FILE *f;
13870     char buf[MSG_SIZ];
13871     int result, i, t,tot=0;
13872
13873     if (strcmp(filename, "-") == 0) {
13874         return SaveGame(stdout, 0, NULL);
13875     } else {
13876         for(i=0; i<10; i++) { // upto 10 tries
13877              f = fopen(filename, append ? "a" : "w");
13878              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13879              if(f || errno != 13) break;
13880              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13881              tot += t;
13882         }
13883         if (f == NULL) {
13884             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13885             DisplayError(buf, errno);
13886             return FALSE;
13887         } else {
13888             safeStrCpy(buf, lastMsg, MSG_SIZ);
13889             DisplayMessage(_("Waiting for access to save file"), "");
13890             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13891             DisplayMessage(_("Saving game"), "");
13892             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13893             result = SaveGame(f, 0, NULL);
13894             DisplayMessage(buf, "");
13895             return result;
13896         }
13897     }
13898 }
13899
13900 char *
13901 SavePart (char *str)
13902 {
13903     static char buf[MSG_SIZ];
13904     char *p;
13905
13906     p = strchr(str, ' ');
13907     if (p == NULL) return str;
13908     strncpy(buf, str, p - str);
13909     buf[p - str] = NULLCHAR;
13910     return buf;
13911 }
13912
13913 #define PGN_MAX_LINE 75
13914
13915 #define PGN_SIDE_WHITE  0
13916 #define PGN_SIDE_BLACK  1
13917
13918 static int
13919 FindFirstMoveOutOfBook (int side)
13920 {
13921     int result = -1;
13922
13923     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13924         int index = backwardMostMove;
13925         int has_book_hit = 0;
13926
13927         if( (index % 2) != side ) {
13928             index++;
13929         }
13930
13931         while( index < forwardMostMove ) {
13932             /* Check to see if engine is in book */
13933             int depth = pvInfoList[index].depth;
13934             int score = pvInfoList[index].score;
13935             int in_book = 0;
13936
13937             if( depth <= 2 ) {
13938                 in_book = 1;
13939             }
13940             else if( score == 0 && depth == 63 ) {
13941                 in_book = 1; /* Zappa */
13942             }
13943             else if( score == 2 && depth == 99 ) {
13944                 in_book = 1; /* Abrok */
13945             }
13946
13947             has_book_hit += in_book;
13948
13949             if( ! in_book ) {
13950                 result = index;
13951
13952                 break;
13953             }
13954
13955             index += 2;
13956         }
13957     }
13958
13959     return result;
13960 }
13961
13962 void
13963 GetOutOfBookInfo (char * buf)
13964 {
13965     int oob[2];
13966     int i;
13967     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13968
13969     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13970     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13971
13972     *buf = '\0';
13973
13974     if( oob[0] >= 0 || oob[1] >= 0 ) {
13975         for( i=0; i<2; i++ ) {
13976             int idx = oob[i];
13977
13978             if( idx >= 0 ) {
13979                 if( i > 0 && oob[0] >= 0 ) {
13980                     strcat( buf, "   " );
13981                 }
13982
13983                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13984                 sprintf( buf+strlen(buf), "%s%.2f",
13985                     pvInfoList[idx].score >= 0 ? "+" : "",
13986                     pvInfoList[idx].score / 100.0 );
13987             }
13988         }
13989     }
13990 }
13991
13992 /* Save game in PGN style */
13993 static void
13994 SaveGamePGN2 (FILE *f)
13995 {
13996     int i, offset, linelen, newblock;
13997 //    char *movetext;
13998     char numtext[32];
13999     int movelen, numlen, blank;
14000     char move_buffer[100]; /* [AS] Buffer for move+PV info */
14001
14002     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14003
14004     PrintPGNTags(f, &gameInfo);
14005
14006     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
14007
14008     if (backwardMostMove > 0 || startedFromSetupPosition) {
14009         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
14010         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
14011         fprintf(f, "\n{--------------\n");
14012         PrintPosition(f, backwardMostMove);
14013         fprintf(f, "--------------}\n");
14014         free(fen);
14015     }
14016     else {
14017         /* [AS] Out of book annotation */
14018         if( appData.saveOutOfBookInfo ) {
14019             char buf[64];
14020
14021             GetOutOfBookInfo( buf );
14022
14023             if( buf[0] != '\0' ) {
14024                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
14025             }
14026         }
14027
14028         fprintf(f, "\n");
14029     }
14030
14031     i = backwardMostMove;
14032     linelen = 0;
14033     newblock = TRUE;
14034
14035     while (i < forwardMostMove) {
14036         /* Print comments preceding this move */
14037         if (commentList[i] != NULL) {
14038             if (linelen > 0) fprintf(f, "\n");
14039             fprintf(f, "%s", commentList[i]);
14040             linelen = 0;
14041             newblock = TRUE;
14042         }
14043
14044         /* Format move number */
14045         if ((i % 2) == 0)
14046           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
14047         else
14048           if (newblock)
14049             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
14050           else
14051             numtext[0] = NULLCHAR;
14052
14053         numlen = strlen(numtext);
14054         newblock = FALSE;
14055
14056         /* Print move number */
14057         blank = linelen > 0 && numlen > 0;
14058         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
14059             fprintf(f, "\n");
14060             linelen = 0;
14061             blank = 0;
14062         }
14063         if (blank) {
14064             fprintf(f, " ");
14065             linelen++;
14066         }
14067         fprintf(f, "%s", numtext);
14068         linelen += numlen;
14069
14070         /* Get move */
14071         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
14072         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
14073
14074         /* Print move */
14075         blank = linelen > 0 && movelen > 0;
14076         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14077             fprintf(f, "\n");
14078             linelen = 0;
14079             blank = 0;
14080         }
14081         if (blank) {
14082             fprintf(f, " ");
14083             linelen++;
14084         }
14085         fprintf(f, "%s", move_buffer);
14086         linelen += movelen;
14087
14088         /* [AS] Add PV info if present */
14089         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
14090             /* [HGM] add time */
14091             char buf[MSG_SIZ]; int seconds;
14092
14093             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14094
14095             if( seconds <= 0)
14096               buf[0] = 0;
14097             else
14098               if( seconds < 30 )
14099                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14100               else
14101                 {
14102                   seconds = (seconds + 4)/10; // round to full seconds
14103                   if( seconds < 60 )
14104                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14105                   else
14106                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14107                 }
14108
14109             if(appData.cumulativeTimePGN) {
14110                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14111             }
14112
14113             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14114                       pvInfoList[i].score >= 0 ? "+" : "",
14115                       pvInfoList[i].score / 100.0,
14116                       pvInfoList[i].depth,
14117                       buf );
14118
14119             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14120
14121             /* Print score/depth */
14122             blank = linelen > 0 && movelen > 0;
14123             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14124                 fprintf(f, "\n");
14125                 linelen = 0;
14126                 blank = 0;
14127             }
14128             if (blank) {
14129                 fprintf(f, " ");
14130                 linelen++;
14131             }
14132             fprintf(f, "%s", move_buffer);
14133             linelen += movelen;
14134         }
14135
14136         i++;
14137     }
14138
14139     /* Start a new line */
14140     if (linelen > 0) fprintf(f, "\n");
14141
14142     /* Print comments after last move */
14143     if (commentList[i] != NULL) {
14144         fprintf(f, "%s\n", commentList[i]);
14145     }
14146
14147     /* Print result */
14148     if (gameInfo.resultDetails != NULL &&
14149         gameInfo.resultDetails[0] != NULLCHAR) {
14150         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14151         if(gameInfo.result == GameUnfinished && appData.clockMode &&
14152            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14153             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14154         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14155     } else {
14156         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14157     }
14158 }
14159
14160 /* Save game in PGN style and close the file */
14161 int
14162 SaveGamePGN (FILE *f)
14163 {
14164     SaveGamePGN2(f);
14165     fclose(f);
14166     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14167     return TRUE;
14168 }
14169
14170 /* Save game in old style and close the file */
14171 int
14172 SaveGameOldStyle (FILE *f)
14173 {
14174     int i, offset;
14175     time_t tm;
14176
14177     tm = time((time_t *) NULL);
14178
14179     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14180     PrintOpponents(f);
14181
14182     if (backwardMostMove > 0 || startedFromSetupPosition) {
14183         fprintf(f, "\n[--------------\n");
14184         PrintPosition(f, backwardMostMove);
14185         fprintf(f, "--------------]\n");
14186     } else {
14187         fprintf(f, "\n");
14188     }
14189
14190     i = backwardMostMove;
14191     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14192
14193     while (i < forwardMostMove) {
14194         if (commentList[i] != NULL) {
14195             fprintf(f, "[%s]\n", commentList[i]);
14196         }
14197
14198         if ((i % 2) == 1) {
14199             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14200             i++;
14201         } else {
14202             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14203             i++;
14204             if (commentList[i] != NULL) {
14205                 fprintf(f, "\n");
14206                 continue;
14207             }
14208             if (i >= forwardMostMove) {
14209                 fprintf(f, "\n");
14210                 break;
14211             }
14212             fprintf(f, "%s\n", parseList[i]);
14213             i++;
14214         }
14215     }
14216
14217     if (commentList[i] != NULL) {
14218         fprintf(f, "[%s]\n", commentList[i]);
14219     }
14220
14221     /* This isn't really the old style, but it's close enough */
14222     if (gameInfo.resultDetails != NULL &&
14223         gameInfo.resultDetails[0] != NULLCHAR) {
14224         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14225                 gameInfo.resultDetails);
14226     } else {
14227         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14228     }
14229
14230     fclose(f);
14231     return TRUE;
14232 }
14233
14234 /* Save the current game to open file f and close the file */
14235 int
14236 SaveGame (FILE *f, int dummy, char *dummy2)
14237 {
14238     if (gameMode == EditPosition) EditPositionDone(TRUE);
14239     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14240     if (appData.oldSaveStyle)
14241       return SaveGameOldStyle(f);
14242     else
14243       return SaveGamePGN(f);
14244 }
14245
14246 /* Save the current position to the given file */
14247 int
14248 SavePositionToFile (char *filename)
14249 {
14250     FILE *f;
14251     char buf[MSG_SIZ];
14252
14253     if (strcmp(filename, "-") == 0) {
14254         return SavePosition(stdout, 0, NULL);
14255     } else {
14256         f = fopen(filename, "a");
14257         if (f == NULL) {
14258             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14259             DisplayError(buf, errno);
14260             return FALSE;
14261         } else {
14262             safeStrCpy(buf, lastMsg, MSG_SIZ);
14263             DisplayMessage(_("Waiting for access to save file"), "");
14264             flock(fileno(f), LOCK_EX); // [HGM] lock
14265             DisplayMessage(_("Saving position"), "");
14266             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14267             SavePosition(f, 0, NULL);
14268             DisplayMessage(buf, "");
14269             return TRUE;
14270         }
14271     }
14272 }
14273
14274 /* Save the current position to the given open file and close the file */
14275 int
14276 SavePosition (FILE *f, int dummy, char *dummy2)
14277 {
14278     time_t tm;
14279     char *fen;
14280
14281     if (gameMode == EditPosition) EditPositionDone(TRUE);
14282     if (appData.oldSaveStyle) {
14283         tm = time((time_t *) NULL);
14284
14285         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14286         PrintOpponents(f);
14287         fprintf(f, "[--------------\n");
14288         PrintPosition(f, currentMove);
14289         fprintf(f, "--------------]\n");
14290     } else {
14291         fen = PositionToFEN(currentMove, NULL, 1);
14292         fprintf(f, "%s\n", fen);
14293         free(fen);
14294     }
14295     fclose(f);
14296     return TRUE;
14297 }
14298
14299 void
14300 ReloadCmailMsgEvent (int unregister)
14301 {
14302 #if !WIN32
14303     static char *inFilename = NULL;
14304     static char *outFilename;
14305     int i;
14306     struct stat inbuf, outbuf;
14307     int status;
14308
14309     /* Any registered moves are unregistered if unregister is set, */
14310     /* i.e. invoked by the signal handler */
14311     if (unregister) {
14312         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14313             cmailMoveRegistered[i] = FALSE;
14314             if (cmailCommentList[i] != NULL) {
14315                 free(cmailCommentList[i]);
14316                 cmailCommentList[i] = NULL;
14317             }
14318         }
14319         nCmailMovesRegistered = 0;
14320     }
14321
14322     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14323         cmailResult[i] = CMAIL_NOT_RESULT;
14324     }
14325     nCmailResults = 0;
14326
14327     if (inFilename == NULL) {
14328         /* Because the filenames are static they only get malloced once  */
14329         /* and they never get freed                                      */
14330         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14331         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14332
14333         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14334         sprintf(outFilename, "%s.out", appData.cmailGameName);
14335     }
14336
14337     status = stat(outFilename, &outbuf);
14338     if (status < 0) {
14339         cmailMailedMove = FALSE;
14340     } else {
14341         status = stat(inFilename, &inbuf);
14342         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14343     }
14344
14345     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14346        counts the games, notes how each one terminated, etc.
14347
14348        It would be nice to remove this kludge and instead gather all
14349        the information while building the game list.  (And to keep it
14350        in the game list nodes instead of having a bunch of fixed-size
14351        parallel arrays.)  Note this will require getting each game's
14352        termination from the PGN tags, as the game list builder does
14353        not process the game moves.  --mann
14354        */
14355     cmailMsgLoaded = TRUE;
14356     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14357
14358     /* Load first game in the file or popup game menu */
14359     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14360
14361 #endif /* !WIN32 */
14362     return;
14363 }
14364
14365 int
14366 RegisterMove ()
14367 {
14368     FILE *f;
14369     char string[MSG_SIZ];
14370
14371     if (   cmailMailedMove
14372         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14373         return TRUE;            /* Allow free viewing  */
14374     }
14375
14376     /* Unregister move to ensure that we don't leave RegisterMove        */
14377     /* with the move registered when the conditions for registering no   */
14378     /* longer hold                                                       */
14379     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14380         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14381         nCmailMovesRegistered --;
14382
14383         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14384           {
14385               free(cmailCommentList[lastLoadGameNumber - 1]);
14386               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14387           }
14388     }
14389
14390     if (cmailOldMove == -1) {
14391         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14392         return FALSE;
14393     }
14394
14395     if (currentMove > cmailOldMove + 1) {
14396         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14397         return FALSE;
14398     }
14399
14400     if (currentMove < cmailOldMove) {
14401         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14402         return FALSE;
14403     }
14404
14405     if (forwardMostMove > currentMove) {
14406         /* Silently truncate extra moves */
14407         TruncateGame();
14408     }
14409
14410     if (   (currentMove == cmailOldMove + 1)
14411         || (   (currentMove == cmailOldMove)
14412             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14413                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14414         if (gameInfo.result != GameUnfinished) {
14415             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14416         }
14417
14418         if (commentList[currentMove] != NULL) {
14419             cmailCommentList[lastLoadGameNumber - 1]
14420               = StrSave(commentList[currentMove]);
14421         }
14422         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14423
14424         if (appData.debugMode)
14425           fprintf(debugFP, "Saving %s for game %d\n",
14426                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14427
14428         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14429
14430         f = fopen(string, "w");
14431         if (appData.oldSaveStyle) {
14432             SaveGameOldStyle(f); /* also closes the file */
14433
14434             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14435             f = fopen(string, "w");
14436             SavePosition(f, 0, NULL); /* also closes the file */
14437         } else {
14438             fprintf(f, "{--------------\n");
14439             PrintPosition(f, currentMove);
14440             fprintf(f, "--------------}\n\n");
14441
14442             SaveGame(f, 0, NULL); /* also closes the file*/
14443         }
14444
14445         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14446         nCmailMovesRegistered ++;
14447     } else if (nCmailGames == 1) {
14448         DisplayError(_("You have not made a move yet"), 0);
14449         return FALSE;
14450     }
14451
14452     return TRUE;
14453 }
14454
14455 void
14456 MailMoveEvent ()
14457 {
14458 #if !WIN32
14459     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14460     FILE *commandOutput;
14461     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14462     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14463     int nBuffers;
14464     int i;
14465     int archived;
14466     char *arcDir;
14467
14468     if (! cmailMsgLoaded) {
14469         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14470         return;
14471     }
14472
14473     if (nCmailGames == nCmailResults) {
14474         DisplayError(_("No unfinished games"), 0);
14475         return;
14476     }
14477
14478 #if CMAIL_PROHIBIT_REMAIL
14479     if (cmailMailedMove) {
14480       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);
14481         DisplayError(msg, 0);
14482         return;
14483     }
14484 #endif
14485
14486     if (! (cmailMailedMove || RegisterMove())) return;
14487
14488     if (   cmailMailedMove
14489         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14490       snprintf(string, MSG_SIZ, partCommandString,
14491                appData.debugMode ? " -v" : "", appData.cmailGameName);
14492         commandOutput = popen(string, "r");
14493
14494         if (commandOutput == NULL) {
14495             DisplayError(_("Failed to invoke cmail"), 0);
14496         } else {
14497             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14498                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14499             }
14500             if (nBuffers > 1) {
14501                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14502                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14503                 nBytes = MSG_SIZ - 1;
14504             } else {
14505                 (void) memcpy(msg, buffer, nBytes);
14506             }
14507             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14508
14509             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14510                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14511
14512                 archived = TRUE;
14513                 for (i = 0; i < nCmailGames; i ++) {
14514                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14515                         archived = FALSE;
14516                     }
14517                 }
14518                 if (   archived
14519                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14520                         != NULL)) {
14521                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14522                            arcDir,
14523                            appData.cmailGameName,
14524                            gameInfo.date);
14525                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14526                     cmailMsgLoaded = FALSE;
14527                 }
14528             }
14529
14530             DisplayInformation(msg);
14531             pclose(commandOutput);
14532         }
14533     } else {
14534         if ((*cmailMsg) != '\0') {
14535             DisplayInformation(cmailMsg);
14536         }
14537     }
14538
14539     return;
14540 #endif /* !WIN32 */
14541 }
14542
14543 char *
14544 CmailMsg ()
14545 {
14546 #if WIN32
14547     return NULL;
14548 #else
14549     int  prependComma = 0;
14550     char number[5];
14551     char string[MSG_SIZ];       /* Space for game-list */
14552     int  i;
14553
14554     if (!cmailMsgLoaded) return "";
14555
14556     if (cmailMailedMove) {
14557       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14558     } else {
14559         /* Create a list of games left */
14560       snprintf(string, MSG_SIZ, "[");
14561         for (i = 0; i < nCmailGames; i ++) {
14562             if (! (   cmailMoveRegistered[i]
14563                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14564                 if (prependComma) {
14565                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14566                 } else {
14567                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14568                     prependComma = 1;
14569                 }
14570
14571                 strcat(string, number);
14572             }
14573         }
14574         strcat(string, "]");
14575
14576         if (nCmailMovesRegistered + nCmailResults == 0) {
14577             switch (nCmailGames) {
14578               case 1:
14579                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14580                 break;
14581
14582               case 2:
14583                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14584                 break;
14585
14586               default:
14587                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14588                          nCmailGames);
14589                 break;
14590             }
14591         } else {
14592             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14593               case 1:
14594                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14595                          string);
14596                 break;
14597
14598               case 0:
14599                 if (nCmailResults == nCmailGames) {
14600                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14601                 } else {
14602                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14603                 }
14604                 break;
14605
14606               default:
14607                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14608                          string);
14609             }
14610         }
14611     }
14612     return cmailMsg;
14613 #endif /* WIN32 */
14614 }
14615
14616 void
14617 ResetGameEvent ()
14618 {
14619     if (gameMode == Training)
14620       SetTrainingModeOff();
14621
14622     Reset(TRUE, TRUE);
14623     cmailMsgLoaded = FALSE;
14624     if (appData.icsActive) {
14625       SendToICS(ics_prefix);
14626       SendToICS("refresh\n");
14627     }
14628 }
14629
14630 void
14631 ExitEvent (int status)
14632 {
14633     exiting++;
14634     if (exiting > 2) {
14635       /* Give up on clean exit */
14636       exit(status);
14637     }
14638     if (exiting > 1) {
14639       /* Keep trying for clean exit */
14640       return;
14641     }
14642
14643     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14644     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14645
14646     if (telnetISR != NULL) {
14647       RemoveInputSource(telnetISR);
14648     }
14649     if (icsPR != NoProc) {
14650       DestroyChildProcess(icsPR, TRUE);
14651     }
14652
14653     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14654     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14655
14656     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14657     /* make sure this other one finishes before killing it!                  */
14658     if(endingGame) { int count = 0;
14659         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14660         while(endingGame && count++ < 10) DoSleep(1);
14661         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14662     }
14663
14664     /* Kill off chess programs */
14665     if (first.pr != NoProc) {
14666         ExitAnalyzeMode();
14667
14668         DoSleep( appData.delayBeforeQuit );
14669         SendToProgram("quit\n", &first);
14670         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14671     }
14672     if (second.pr != NoProc) {
14673         DoSleep( appData.delayBeforeQuit );
14674         SendToProgram("quit\n", &second);
14675         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14676     }
14677     if (first.isr != NULL) {
14678         RemoveInputSource(first.isr);
14679     }
14680     if (second.isr != NULL) {
14681         RemoveInputSource(second.isr);
14682     }
14683
14684     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14685     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14686
14687     ShutDownFrontEnd();
14688     exit(status);
14689 }
14690
14691 void
14692 PauseEngine (ChessProgramState *cps)
14693 {
14694     SendToProgram("pause\n", cps);
14695     cps->pause = 2;
14696 }
14697
14698 void
14699 UnPauseEngine (ChessProgramState *cps)
14700 {
14701     SendToProgram("resume\n", cps);
14702     cps->pause = 1;
14703 }
14704
14705 void
14706 PauseEvent ()
14707 {
14708     if (appData.debugMode)
14709         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14710     if (pausing) {
14711         pausing = FALSE;
14712         ModeHighlight();
14713         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14714             StartClocks();
14715             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14716                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14717                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14718             }
14719             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14720             HandleMachineMove(stashedInputMove, stalledEngine);
14721             stalledEngine = NULL;
14722             return;
14723         }
14724         if (gameMode == MachinePlaysWhite ||
14725             gameMode == TwoMachinesPlay   ||
14726             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14727             if(first.pause)  UnPauseEngine(&first);
14728             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14729             if(second.pause) UnPauseEngine(&second);
14730             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14731             StartClocks();
14732         } else {
14733             DisplayBothClocks();
14734         }
14735         if (gameMode == PlayFromGameFile) {
14736             if (appData.timeDelay >= 0)
14737                 AutoPlayGameLoop();
14738         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14739             Reset(FALSE, TRUE);
14740             SendToICS(ics_prefix);
14741             SendToICS("refresh\n");
14742         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14743             ForwardInner(forwardMostMove);
14744         }
14745         pauseExamInvalid = FALSE;
14746     } else {
14747         switch (gameMode) {
14748           default:
14749             return;
14750           case IcsExamining:
14751             pauseExamForwardMostMove = forwardMostMove;
14752             pauseExamInvalid = FALSE;
14753             /* fall through */
14754           case IcsObserving:
14755           case IcsPlayingWhite:
14756           case IcsPlayingBlack:
14757             pausing = TRUE;
14758             ModeHighlight();
14759             return;
14760           case PlayFromGameFile:
14761             (void) StopLoadGameTimer();
14762             pausing = TRUE;
14763             ModeHighlight();
14764             break;
14765           case BeginningOfGame:
14766             if (appData.icsActive) return;
14767             /* else fall through */
14768           case MachinePlaysWhite:
14769           case MachinePlaysBlack:
14770           case TwoMachinesPlay:
14771             if (forwardMostMove == 0)
14772               return;           /* don't pause if no one has moved */
14773             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14774                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14775                 if(onMove->pause) {           // thinking engine can be paused
14776                     PauseEngine(onMove);      // do it
14777                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14778                         PauseEngine(onMove->other);
14779                     else
14780                         SendToProgram("easy\n", onMove->other);
14781                     StopClocks();
14782                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14783             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14784                 if(first.pause) {
14785                     PauseEngine(&first);
14786                     StopClocks();
14787                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14788             } else { // human on move, pause pondering by either method
14789                 if(first.pause)
14790                     PauseEngine(&first);
14791                 else if(appData.ponderNextMove)
14792                     SendToProgram("easy\n", &first);
14793                 StopClocks();
14794             }
14795             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14796           case AnalyzeMode:
14797             pausing = TRUE;
14798             ModeHighlight();
14799             break;
14800         }
14801     }
14802 }
14803
14804 void
14805 EditCommentEvent ()
14806 {
14807     char title[MSG_SIZ];
14808
14809     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14810       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14811     } else {
14812       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14813                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14814                parseList[currentMove - 1]);
14815     }
14816
14817     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14818 }
14819
14820
14821 void
14822 EditTagsEvent ()
14823 {
14824     char *tags = PGNTags(&gameInfo);
14825     bookUp = FALSE;
14826     EditTagsPopUp(tags, NULL);
14827     free(tags);
14828 }
14829
14830 void
14831 StartSecond ()
14832 {
14833     if(WaitForEngine(&second, StartSecond)) return;
14834     InitChessProgram(&second, FALSE);
14835     FeedMovesToProgram(&second, currentMove);
14836
14837     SendToProgram("analyze\n", &second);
14838     second.analyzing = TRUE;
14839     ThawUI();
14840 }
14841
14842 void
14843 ToggleSecond ()
14844 {
14845   if(second.analyzing) {
14846     SendToProgram("exit\n", &second);
14847     second.analyzing = FALSE;
14848   } else {
14849     StartSecond();
14850   }
14851 }
14852
14853 /* Toggle ShowThinking */
14854 void
14855 ToggleShowThinking()
14856 {
14857   appData.showThinking = !appData.showThinking;
14858   ShowThinkingEvent();
14859 }
14860
14861 int
14862 AnalyzeModeEvent ()
14863 {
14864     char buf[MSG_SIZ];
14865
14866     if (!first.analysisSupport) {
14867       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14868       DisplayError(buf, 0);
14869       return 0;
14870     }
14871     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14872     if (appData.icsActive) {
14873         if (gameMode != IcsObserving) {
14874           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14875             DisplayError(buf, 0);
14876             /* secure check */
14877             if (appData.icsEngineAnalyze) {
14878                 if (appData.debugMode)
14879                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14880                 ExitAnalyzeMode();
14881                 ModeHighlight();
14882             }
14883             return 0;
14884         }
14885         /* if enable, user wants to disable icsEngineAnalyze */
14886         if (appData.icsEngineAnalyze) {
14887                 ExitAnalyzeMode();
14888                 ModeHighlight();
14889                 return 0;
14890         }
14891         appData.icsEngineAnalyze = TRUE;
14892         if (appData.debugMode)
14893             fprintf(debugFP, "ICS engine analyze starting... \n");
14894     }
14895
14896     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14897     if (appData.noChessProgram || gameMode == AnalyzeMode)
14898       return 0;
14899
14900     if (gameMode != AnalyzeFile) {
14901         if (!appData.icsEngineAnalyze) {
14902                EditGameEvent();
14903                if (gameMode != EditGame) return 0;
14904         }
14905         if (!appData.showThinking) ToggleShowThinking();
14906         ResurrectChessProgram();
14907         SendToProgram("analyze\n", &first);
14908         first.analyzing = TRUE;
14909         /*first.maybeThinking = TRUE;*/
14910         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14911         EngineOutputPopUp();
14912     }
14913     if (!appData.icsEngineAnalyze) {
14914         gameMode = AnalyzeMode;
14915         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14916     }
14917     pausing = FALSE;
14918     ModeHighlight();
14919     SetGameInfo();
14920
14921     StartAnalysisClock();
14922     GetTimeMark(&lastNodeCountTime);
14923     lastNodeCount = 0;
14924     return 1;
14925 }
14926
14927 void
14928 AnalyzeFileEvent ()
14929 {
14930     if (appData.noChessProgram || gameMode == AnalyzeFile)
14931       return;
14932
14933     if (!first.analysisSupport) {
14934       char buf[MSG_SIZ];
14935       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14936       DisplayError(buf, 0);
14937       return;
14938     }
14939
14940     if (gameMode != AnalyzeMode) {
14941         keepInfo = 1; // mere annotating should not alter PGN tags
14942         EditGameEvent();
14943         keepInfo = 0;
14944         if (gameMode != EditGame) return;
14945         if (!appData.showThinking) ToggleShowThinking();
14946         ResurrectChessProgram();
14947         SendToProgram("analyze\n", &first);
14948         first.analyzing = TRUE;
14949         /*first.maybeThinking = TRUE;*/
14950         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14951         EngineOutputPopUp();
14952     }
14953     gameMode = AnalyzeFile;
14954     pausing = FALSE;
14955     ModeHighlight();
14956
14957     StartAnalysisClock();
14958     GetTimeMark(&lastNodeCountTime);
14959     lastNodeCount = 0;
14960     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14961     AnalysisPeriodicEvent(1);
14962 }
14963
14964 void
14965 MachineWhiteEvent ()
14966 {
14967     char buf[MSG_SIZ];
14968     char *bookHit = NULL;
14969
14970     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14971       return;
14972
14973
14974     if (gameMode == PlayFromGameFile ||
14975         gameMode == TwoMachinesPlay  ||
14976         gameMode == Training         ||
14977         gameMode == AnalyzeMode      ||
14978         gameMode == EndOfGame)
14979         EditGameEvent();
14980
14981     if (gameMode == EditPosition)
14982         EditPositionDone(TRUE);
14983
14984     if (!WhiteOnMove(currentMove)) {
14985         DisplayError(_("It is not White's turn"), 0);
14986         return;
14987     }
14988
14989     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14990       ExitAnalyzeMode();
14991
14992     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14993         gameMode == AnalyzeFile)
14994         TruncateGame();
14995
14996     ResurrectChessProgram();    /* in case it isn't running */
14997     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14998         gameMode = MachinePlaysWhite;
14999         ResetClocks();
15000     } else
15001     gameMode = MachinePlaysWhite;
15002     pausing = FALSE;
15003     ModeHighlight();
15004     SetGameInfo();
15005     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15006     DisplayTitle(buf);
15007     if (first.sendName) {
15008       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
15009       SendToProgram(buf, &first);
15010     }
15011     if (first.sendTime) {
15012       if (first.useColors) {
15013         SendToProgram("black\n", &first); /*gnu kludge*/
15014       }
15015       SendTimeRemaining(&first, TRUE);
15016     }
15017     if (first.useColors) {
15018       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
15019     }
15020     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15021     SetMachineThinkingEnables();
15022     first.maybeThinking = TRUE;
15023     StartClocks();
15024     firstMove = FALSE;
15025
15026     if (appData.autoFlipView && !flipView) {
15027       flipView = !flipView;
15028       DrawPosition(FALSE, NULL);
15029       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15030     }
15031
15032     if(bookHit) { // [HGM] book: simulate book reply
15033         static char bookMove[MSG_SIZ]; // a bit generous?
15034
15035         programStats.nodes = programStats.depth = programStats.time =
15036         programStats.score = programStats.got_only_move = 0;
15037         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15038
15039         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15040         strcat(bookMove, bookHit);
15041         savedMessage = bookMove; // args for deferred call
15042         savedState = &first;
15043         ScheduleDelayedEvent(DeferredBookMove, 1);
15044     }
15045 }
15046
15047 void
15048 MachineBlackEvent ()
15049 {
15050   char buf[MSG_SIZ];
15051   char *bookHit = NULL;
15052
15053     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
15054         return;
15055
15056
15057     if (gameMode == PlayFromGameFile ||
15058         gameMode == TwoMachinesPlay  ||
15059         gameMode == Training         ||
15060         gameMode == AnalyzeMode      ||
15061         gameMode == EndOfGame)
15062         EditGameEvent();
15063
15064     if (gameMode == EditPosition)
15065         EditPositionDone(TRUE);
15066
15067     if (WhiteOnMove(currentMove)) {
15068         DisplayError(_("It is not Black's turn"), 0);
15069         return;
15070     }
15071
15072     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15073       ExitAnalyzeMode();
15074
15075     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15076         gameMode == AnalyzeFile)
15077         TruncateGame();
15078
15079     ResurrectChessProgram();    /* in case it isn't running */
15080     gameMode = MachinePlaysBlack;
15081     pausing = FALSE;
15082     ModeHighlight();
15083     SetGameInfo();
15084     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15085     DisplayTitle(buf);
15086     if (first.sendName) {
15087       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
15088       SendToProgram(buf, &first);
15089     }
15090     if (first.sendTime) {
15091       if (first.useColors) {
15092         SendToProgram("white\n", &first); /*gnu kludge*/
15093       }
15094       SendTimeRemaining(&first, FALSE);
15095     }
15096     if (first.useColors) {
15097       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
15098     }
15099     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15100     SetMachineThinkingEnables();
15101     first.maybeThinking = TRUE;
15102     StartClocks();
15103
15104     if (appData.autoFlipView && flipView) {
15105       flipView = !flipView;
15106       DrawPosition(FALSE, NULL);
15107       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15108     }
15109     if(bookHit) { // [HGM] book: simulate book reply
15110         static char bookMove[MSG_SIZ]; // a bit generous?
15111
15112         programStats.nodes = programStats.depth = programStats.time =
15113         programStats.score = programStats.got_only_move = 0;
15114         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15115
15116         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15117         strcat(bookMove, bookHit);
15118         savedMessage = bookMove; // args for deferred call
15119         savedState = &first;
15120         ScheduleDelayedEvent(DeferredBookMove, 1);
15121     }
15122 }
15123
15124
15125 void
15126 DisplayTwoMachinesTitle ()
15127 {
15128     char buf[MSG_SIZ];
15129     if (appData.matchGames > 0) {
15130         if(appData.tourneyFile[0]) {
15131           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15132                    gameInfo.white, _("vs."), gameInfo.black,
15133                    nextGame+1, appData.matchGames+1,
15134                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15135         } else
15136         if (first.twoMachinesColor[0] == 'w') {
15137           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15138                    gameInfo.white, _("vs."),  gameInfo.black,
15139                    first.matchWins, second.matchWins,
15140                    matchGame - 1 - (first.matchWins + second.matchWins));
15141         } else {
15142           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15143                    gameInfo.white, _("vs."), gameInfo.black,
15144                    second.matchWins, first.matchWins,
15145                    matchGame - 1 - (first.matchWins + second.matchWins));
15146         }
15147     } else {
15148       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15149     }
15150     DisplayTitle(buf);
15151 }
15152
15153 void
15154 SettingsMenuIfReady ()
15155 {
15156   if (second.lastPing != second.lastPong) {
15157     DisplayMessage("", _("Waiting for second chess program"));
15158     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15159     return;
15160   }
15161   ThawUI();
15162   DisplayMessage("", "");
15163   SettingsPopUp(&second);
15164 }
15165
15166 int
15167 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15168 {
15169     char buf[MSG_SIZ];
15170     if (cps->pr == NoProc) {
15171         StartChessProgram(cps);
15172         if (cps->protocolVersion == 1) {
15173           retry();
15174           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15175         } else {
15176           /* kludge: allow timeout for initial "feature" command */
15177           if(retry != TwoMachinesEventIfReady) FreezeUI();
15178           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15179           DisplayMessage("", buf);
15180           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15181         }
15182         return 1;
15183     }
15184     return 0;
15185 }
15186
15187 void
15188 TwoMachinesEvent P((void))
15189 {
15190     int i, move = forwardMostMove;
15191     char buf[MSG_SIZ];
15192     ChessProgramState *onmove;
15193     char *bookHit = NULL;
15194     static int stalling = 0;
15195     TimeMark now;
15196     long wait;
15197
15198     if (appData.noChessProgram) return;
15199
15200     switch (gameMode) {
15201       case TwoMachinesPlay:
15202         return;
15203       case MachinePlaysWhite:
15204       case MachinePlaysBlack:
15205         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15206             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15207             return;
15208         }
15209         /* fall through */
15210       case BeginningOfGame:
15211       case PlayFromGameFile:
15212       case EndOfGame:
15213         EditGameEvent();
15214         if (gameMode != EditGame) return;
15215         break;
15216       case EditPosition:
15217         EditPositionDone(TRUE);
15218         break;
15219       case AnalyzeMode:
15220       case AnalyzeFile:
15221         ExitAnalyzeMode();
15222         break;
15223       case EditGame:
15224       default:
15225         break;
15226     }
15227
15228 //    forwardMostMove = currentMove;
15229     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15230     startingEngine = TRUE;
15231
15232     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15233
15234     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15235     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15236       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15237       return;
15238     }
15239   if(!appData.epd) {
15240     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15241
15242     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15243                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15244         startingEngine = matchMode = FALSE;
15245         DisplayError("second engine does not play this", 0);
15246         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15247         EditGameEvent(); // switch back to EditGame mode
15248         return;
15249     }
15250
15251     if(!stalling) {
15252       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15253       SendToProgram("force\n", &second);
15254       stalling = 1;
15255       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15256       return;
15257     }
15258   }
15259     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15260     if(appData.matchPause>10000 || appData.matchPause<10)
15261                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15262     wait = SubtractTimeMarks(&now, &pauseStart);
15263     if(wait < appData.matchPause) {
15264         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15265         return;
15266     }
15267     // we are now committed to starting the game
15268     stalling = 0;
15269     DisplayMessage("", "");
15270   if(!appData.epd) {
15271     if (startedFromSetupPosition) {
15272         SendBoard(&second, backwardMostMove);
15273     if (appData.debugMode) {
15274         fprintf(debugFP, "Two Machines\n");
15275     }
15276     }
15277     for (i = backwardMostMove; i < forwardMostMove; i++) {
15278         SendMoveToProgram(i, &second);
15279     }
15280   }
15281
15282     gameMode = TwoMachinesPlay;
15283     pausing = startingEngine = FALSE;
15284     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15285     SetGameInfo();
15286     DisplayTwoMachinesTitle();
15287     firstMove = TRUE;
15288     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15289         onmove = &first;
15290     } else {
15291         onmove = &second;
15292     }
15293     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15294     SendToProgram(first.computerString, &first);
15295     if (first.sendName) {
15296       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15297       SendToProgram(buf, &first);
15298     }
15299   if(!appData.epd) {
15300     SendToProgram(second.computerString, &second);
15301     if (second.sendName) {
15302       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15303       SendToProgram(buf, &second);
15304     }
15305   }
15306
15307     if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15308         ResetClocks();
15309         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15310         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15311     }
15312     if (onmove->sendTime) {
15313       if (onmove->useColors) {
15314         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15315       }
15316       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15317     }
15318     if (onmove->useColors) {
15319       SendToProgram(onmove->twoMachinesColor, onmove);
15320     }
15321     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15322 //    SendToProgram("go\n", onmove);
15323     onmove->maybeThinking = TRUE;
15324     SetMachineThinkingEnables();
15325
15326     StartClocks();
15327
15328     if(bookHit) { // [HGM] book: simulate book reply
15329         static char bookMove[MSG_SIZ]; // a bit generous?
15330
15331         programStats.nodes = programStats.depth = programStats.time =
15332         programStats.score = programStats.got_only_move = 0;
15333         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15334
15335         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15336         strcat(bookMove, bookHit);
15337         savedMessage = bookMove; // args for deferred call
15338         savedState = onmove;
15339         ScheduleDelayedEvent(DeferredBookMove, 1);
15340     }
15341 }
15342
15343 void
15344 TrainingEvent ()
15345 {
15346     if (gameMode == Training) {
15347       SetTrainingModeOff();
15348       gameMode = PlayFromGameFile;
15349       DisplayMessage("", _("Training mode off"));
15350     } else {
15351       gameMode = Training;
15352       animateTraining = appData.animate;
15353
15354       /* make sure we are not already at the end of the game */
15355       if (currentMove < forwardMostMove) {
15356         SetTrainingModeOn();
15357         DisplayMessage("", _("Training mode on"));
15358       } else {
15359         gameMode = PlayFromGameFile;
15360         DisplayError(_("Already at end of game"), 0);
15361       }
15362     }
15363     ModeHighlight();
15364 }
15365
15366 void
15367 IcsClientEvent ()
15368 {
15369     if (!appData.icsActive) return;
15370     switch (gameMode) {
15371       case IcsPlayingWhite:
15372       case IcsPlayingBlack:
15373       case IcsObserving:
15374       case IcsIdle:
15375       case BeginningOfGame:
15376       case IcsExamining:
15377         return;
15378
15379       case EditGame:
15380         break;
15381
15382       case EditPosition:
15383         EditPositionDone(TRUE);
15384         break;
15385
15386       case AnalyzeMode:
15387       case AnalyzeFile:
15388         ExitAnalyzeMode();
15389         break;
15390
15391       default:
15392         EditGameEvent();
15393         break;
15394     }
15395
15396     gameMode = IcsIdle;
15397     ModeHighlight();
15398     return;
15399 }
15400
15401 void
15402 EditGameEvent ()
15403 {
15404     int i;
15405
15406     switch (gameMode) {
15407       case Training:
15408         SetTrainingModeOff();
15409         break;
15410       case MachinePlaysWhite:
15411       case MachinePlaysBlack:
15412       case BeginningOfGame:
15413         SendToProgram("force\n", &first);
15414         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15415             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15416                 char buf[MSG_SIZ];
15417                 abortEngineThink = TRUE;
15418                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15419                 SendToProgram(buf, &first);
15420                 DisplayMessage("Aborting engine think", "");
15421                 FreezeUI();
15422             }
15423         }
15424         SetUserThinkingEnables();
15425         break;
15426       case PlayFromGameFile:
15427         (void) StopLoadGameTimer();
15428         if (gameFileFP != NULL) {
15429             gameFileFP = NULL;
15430         }
15431         break;
15432       case EditPosition:
15433         EditPositionDone(TRUE);
15434         break;
15435       case AnalyzeMode:
15436       case AnalyzeFile:
15437         ExitAnalyzeMode();
15438         SendToProgram("force\n", &first);
15439         break;
15440       case TwoMachinesPlay:
15441         GameEnds(EndOfFile, NULL, GE_PLAYER);
15442         ResurrectChessProgram();
15443         SetUserThinkingEnables();
15444         break;
15445       case EndOfGame:
15446         ResurrectChessProgram();
15447         break;
15448       case IcsPlayingBlack:
15449       case IcsPlayingWhite:
15450         DisplayError(_("Warning: You are still playing a game"), 0);
15451         break;
15452       case IcsObserving:
15453         DisplayError(_("Warning: You are still observing a game"), 0);
15454         break;
15455       case IcsExamining:
15456         DisplayError(_("Warning: You are still examining a game"), 0);
15457         break;
15458       case IcsIdle:
15459         break;
15460       case EditGame:
15461       default:
15462         return;
15463     }
15464
15465     pausing = FALSE;
15466     StopClocks();
15467     first.offeredDraw = second.offeredDraw = 0;
15468
15469     if (gameMode == PlayFromGameFile) {
15470         whiteTimeRemaining = timeRemaining[0][currentMove];
15471         blackTimeRemaining = timeRemaining[1][currentMove];
15472         DisplayTitle("");
15473     }
15474
15475     if (gameMode == MachinePlaysWhite ||
15476         gameMode == MachinePlaysBlack ||
15477         gameMode == TwoMachinesPlay ||
15478         gameMode == EndOfGame) {
15479         i = forwardMostMove;
15480         while (i > currentMove) {
15481             SendToProgram("undo\n", &first);
15482             i--;
15483         }
15484         if(!adjustedClock) {
15485         whiteTimeRemaining = timeRemaining[0][currentMove];
15486         blackTimeRemaining = timeRemaining[1][currentMove];
15487         DisplayBothClocks();
15488         }
15489         if (whiteFlag || blackFlag) {
15490             whiteFlag = blackFlag = 0;
15491         }
15492         DisplayTitle("");
15493     }
15494
15495     gameMode = EditGame;
15496     ModeHighlight();
15497     SetGameInfo();
15498 }
15499
15500 void
15501 EditPositionEvent ()
15502 {
15503     int i;
15504     if (gameMode == EditPosition) {
15505         EditGameEvent();
15506         return;
15507     }
15508
15509     EditGameEvent();
15510     if (gameMode != EditGame) return;
15511
15512     gameMode = EditPosition;
15513     ModeHighlight();
15514     SetGameInfo();
15515     CopyBoard(rightsBoard, nullBoard);
15516     if (currentMove > 0)
15517       CopyBoard(boards[0], boards[currentMove]);
15518     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15519       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15520
15521     blackPlaysFirst = !WhiteOnMove(currentMove);
15522     ResetClocks();
15523     currentMove = forwardMostMove = backwardMostMove = 0;
15524     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15525     DisplayMove(-1);
15526     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15527 }
15528
15529 void
15530 ExitAnalyzeMode ()
15531 {
15532     /* [DM] icsEngineAnalyze - possible call from other functions */
15533     if (appData.icsEngineAnalyze) {
15534         appData.icsEngineAnalyze = FALSE;
15535
15536         DisplayMessage("",_("Close ICS engine analyze..."));
15537     }
15538     if (first.analysisSupport && first.analyzing) {
15539       SendToBoth("exit\n");
15540       first.analyzing = second.analyzing = FALSE;
15541     }
15542     thinkOutput[0] = NULLCHAR;
15543 }
15544
15545 void
15546 EditPositionDone (Boolean fakeRights)
15547 {
15548     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15549
15550     startedFromSetupPosition = TRUE;
15551     InitChessProgram(&first, FALSE);
15552     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15553       int r, f;
15554       boards[0][EP_STATUS] = EP_NONE;
15555       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15556       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15557         if(rightsBoard[r][f]) {
15558           ChessSquare p = boards[0][r][f];
15559           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15560           else if(p == king) boards[0][CASTLING][2] = f;
15561           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15562           else rightsBoard[r][f] = 2; // mark for second pass
15563         }
15564       }
15565       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15566         if(rightsBoard[r][f] == 2) {
15567           ChessSquare p = boards[0][r][f];
15568           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15569           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15570         }
15571       }
15572     }
15573     SendToProgram("force\n", &first);
15574     if (blackPlaysFirst) {
15575         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15576         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15577         currentMove = forwardMostMove = backwardMostMove = 1;
15578         CopyBoard(boards[1], boards[0]);
15579     } else {
15580         currentMove = forwardMostMove = backwardMostMove = 0;
15581     }
15582     SendBoard(&first, forwardMostMove);
15583     if (appData.debugMode) {
15584         fprintf(debugFP, "EditPosDone\n");
15585     }
15586     DisplayTitle("");
15587     DisplayMessage("", "");
15588     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15589     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15590     gameMode = EditGame;
15591     ModeHighlight();
15592     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15593     ClearHighlights(); /* [AS] */
15594 }
15595
15596 /* Pause for `ms' milliseconds */
15597 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15598 void
15599 TimeDelay (long ms)
15600 {
15601     TimeMark m1, m2;
15602
15603     GetTimeMark(&m1);
15604     do {
15605         GetTimeMark(&m2);
15606     } while (SubtractTimeMarks(&m2, &m1) < ms);
15607 }
15608
15609 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15610 void
15611 SendMultiLineToICS (char *buf)
15612 {
15613     char temp[MSG_SIZ+1], *p;
15614     int len;
15615
15616     len = strlen(buf);
15617     if (len > MSG_SIZ)
15618       len = MSG_SIZ;
15619
15620     strncpy(temp, buf, len);
15621     temp[len] = 0;
15622
15623     p = temp;
15624     while (*p) {
15625         if (*p == '\n' || *p == '\r')
15626           *p = ' ';
15627         ++p;
15628     }
15629
15630     strcat(temp, "\n");
15631     SendToICS(temp);
15632     SendToPlayer(temp, strlen(temp));
15633 }
15634
15635 void
15636 SetWhiteToPlayEvent ()
15637 {
15638     if (gameMode == EditPosition) {
15639         blackPlaysFirst = FALSE;
15640         DisplayBothClocks();    /* works because currentMove is 0 */
15641     } else if (gameMode == IcsExamining) {
15642         SendToICS(ics_prefix);
15643         SendToICS("tomove white\n");
15644     }
15645 }
15646
15647 void
15648 SetBlackToPlayEvent ()
15649 {
15650     if (gameMode == EditPosition) {
15651         blackPlaysFirst = TRUE;
15652         currentMove = 1;        /* kludge */
15653         DisplayBothClocks();
15654         currentMove = 0;
15655     } else if (gameMode == IcsExamining) {
15656         SendToICS(ics_prefix);
15657         SendToICS("tomove black\n");
15658     }
15659 }
15660
15661 void
15662 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15663 {
15664     char buf[MSG_SIZ];
15665     ChessSquare piece = boards[0][y][x];
15666     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15667     static int lastVariant;
15668     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15669
15670     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15671
15672     switch (selection) {
15673       case ClearBoard:
15674         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15675         MarkTargetSquares(1);
15676         CopyBoard(currentBoard, boards[0]);
15677         CopyBoard(menuBoard, initialPosition);
15678         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15679             SendToICS(ics_prefix);
15680             SendToICS("bsetup clear\n");
15681         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15682             SendToICS(ics_prefix);
15683             SendToICS("clearboard\n");
15684         } else {
15685             int nonEmpty = 0;
15686             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15687                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15688                 for (y = 0; y < BOARD_HEIGHT; y++) {
15689                     if (gameMode == IcsExamining) {
15690                         if (boards[currentMove][y][x] != EmptySquare) {
15691                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15692                                     AAA + x, ONE + y);
15693                             SendToICS(buf);
15694                         }
15695                     } else if(boards[0][y][x] != DarkSquare) {
15696                         if(boards[0][y][x] != p) nonEmpty++;
15697                         boards[0][y][x] = p;
15698                     }
15699                 }
15700             }
15701             CopyBoard(rightsBoard, nullBoard);
15702             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15703                 int r, i;
15704                 for(r = 0; r < BOARD_HEIGHT; r++) {
15705                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15706                     ChessSquare p = menuBoard[r][x];
15707                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15708                   }
15709                 }
15710                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15711                 DisplayMessage("Clicking clock again restores position", "");
15712                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15713                 if(!nonEmpty) { // asked to clear an empty board
15714                     CopyBoard(boards[0], menuBoard);
15715                 } else
15716                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15717                     CopyBoard(boards[0], initialPosition);
15718                 } else
15719                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15720                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15721                     CopyBoard(boards[0], erasedBoard);
15722                 } else
15723                     CopyBoard(erasedBoard, currentBoard);
15724
15725                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15726                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15727             }
15728         }
15729         if (gameMode == EditPosition) {
15730             DrawPosition(FALSE, boards[0]);
15731         }
15732         break;
15733
15734       case WhitePlay:
15735         SetWhiteToPlayEvent();
15736         break;
15737
15738       case BlackPlay:
15739         SetBlackToPlayEvent();
15740         break;
15741
15742       case EmptySquare:
15743         if (gameMode == IcsExamining) {
15744             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15745             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15746             SendToICS(buf);
15747         } else {
15748             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15749                 if(x == BOARD_LEFT-2) {
15750                     if(y < handSize-1-gameInfo.holdingsSize) break;
15751                     boards[0][y][1] = 0;
15752                 } else
15753                 if(x == BOARD_RGHT+1) {
15754                     if(y >= gameInfo.holdingsSize) break;
15755                     boards[0][y][BOARD_WIDTH-2] = 0;
15756                 } else break;
15757             }
15758             boards[0][y][x] = EmptySquare;
15759             DrawPosition(FALSE, boards[0]);
15760         }
15761         break;
15762
15763       case PromotePiece:
15764         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15765            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15766             selection = (ChessSquare) (PROMOTED(piece));
15767         } else if(piece == EmptySquare) selection = WhiteSilver;
15768         else selection = (ChessSquare)((int)piece - 1);
15769         goto defaultlabel;
15770
15771       case DemotePiece:
15772         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15773            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15774             selection = (ChessSquare) (DEMOTED(piece));
15775         } else if(piece == EmptySquare) selection = BlackSilver;
15776         else selection = (ChessSquare)((int)piece + 1);
15777         goto defaultlabel;
15778
15779       case WhiteQueen:
15780       case BlackQueen:
15781         if(gameInfo.variant == VariantShatranj ||
15782            gameInfo.variant == VariantXiangqi  ||
15783            gameInfo.variant == VariantCourier  ||
15784            gameInfo.variant == VariantASEAN    ||
15785            gameInfo.variant == VariantMakruk     )
15786             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15787         goto defaultlabel;
15788
15789       case WhiteRook:
15790         baseRank = 0;
15791       case BlackRook:
15792         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15793         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15794         goto defaultlabel;
15795
15796       case WhiteKing:
15797         baseRank = 0;
15798       case BlackKing:
15799         if(gameInfo.variant == VariantXiangqi)
15800             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15801         if(gameInfo.variant == VariantKnightmate)
15802             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15803         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15804       default:
15805         defaultlabel:
15806         if (gameMode == IcsExamining) {
15807             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15808             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15809                      PieceToChar(selection), AAA + x, ONE + y);
15810             SendToICS(buf);
15811         } else {
15812             rightsBoard[y][x] = hasRights;
15813             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15814                 int n;
15815                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15816                     n = PieceToNumber(selection - BlackPawn);
15817                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15818                     boards[0][handSize-1-n][0] = selection;
15819                     boards[0][handSize-1-n][1]++;
15820                 } else
15821                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15822                     n = PieceToNumber(selection);
15823                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15824                     boards[0][n][BOARD_WIDTH-1] = selection;
15825                     boards[0][n][BOARD_WIDTH-2]++;
15826                 }
15827             } else
15828             boards[0][y][x] = selection;
15829             DrawPosition(TRUE, boards[0]);
15830             ClearHighlights();
15831             fromX = fromY = -1;
15832         }
15833         break;
15834     }
15835 }
15836
15837
15838 void
15839 DropMenuEvent (ChessSquare selection, int x, int y)
15840 {
15841     ChessMove moveType;
15842
15843     switch (gameMode) {
15844       case IcsPlayingWhite:
15845       case MachinePlaysBlack:
15846         if (!WhiteOnMove(currentMove)) {
15847             DisplayMoveError(_("It is Black's turn"));
15848             return;
15849         }
15850         moveType = WhiteDrop;
15851         break;
15852       case IcsPlayingBlack:
15853       case MachinePlaysWhite:
15854         if (WhiteOnMove(currentMove)) {
15855             DisplayMoveError(_("It is White's turn"));
15856             return;
15857         }
15858         moveType = BlackDrop;
15859         break;
15860       case EditGame:
15861         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15862         break;
15863       default:
15864         return;
15865     }
15866
15867     if (moveType == BlackDrop && selection < BlackPawn) {
15868       selection = (ChessSquare) ((int) selection
15869                                  + (int) BlackPawn - (int) WhitePawn);
15870     }
15871     if (boards[currentMove][y][x] != EmptySquare) {
15872         DisplayMoveError(_("That square is occupied"));
15873         return;
15874     }
15875
15876     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15877 }
15878
15879 void
15880 AcceptEvent ()
15881 {
15882     /* Accept a pending offer of any kind from opponent */
15883
15884     if (appData.icsActive) {
15885         SendToICS(ics_prefix);
15886         SendToICS("accept\n");
15887     } else if (cmailMsgLoaded) {
15888         if (currentMove == cmailOldMove &&
15889             commentList[cmailOldMove] != NULL &&
15890             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15891                    "Black offers a draw" : "White offers a draw")) {
15892             TruncateGame();
15893             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15894             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15895         } else {
15896             DisplayError(_("There is no pending offer on this move"), 0);
15897             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15898         }
15899     } else {
15900         /* Not used for offers from chess program */
15901     }
15902 }
15903
15904 void
15905 DeclineEvent ()
15906 {
15907     /* Decline a pending offer of any kind from opponent */
15908
15909     if (appData.icsActive) {
15910         SendToICS(ics_prefix);
15911         SendToICS("decline\n");
15912     } else if (cmailMsgLoaded) {
15913         if (currentMove == cmailOldMove &&
15914             commentList[cmailOldMove] != NULL &&
15915             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15916                    "Black offers a draw" : "White offers a draw")) {
15917 #ifdef NOTDEF
15918             AppendComment(cmailOldMove, "Draw declined", TRUE);
15919             DisplayComment(cmailOldMove - 1, "Draw declined");
15920 #endif /*NOTDEF*/
15921         } else {
15922             DisplayError(_("There is no pending offer on this move"), 0);
15923         }
15924     } else {
15925         /* Not used for offers from chess program */
15926     }
15927 }
15928
15929 void
15930 RematchEvent ()
15931 {
15932     /* Issue ICS rematch command */
15933     if (appData.icsActive) {
15934         SendToICS(ics_prefix);
15935         SendToICS("rematch\n");
15936     }
15937 }
15938
15939 void
15940 CallFlagEvent ()
15941 {
15942     /* Call your opponent's flag (claim a win on time) */
15943     if (appData.icsActive) {
15944         SendToICS(ics_prefix);
15945         SendToICS("flag\n");
15946     } else {
15947         switch (gameMode) {
15948           default:
15949             return;
15950           case MachinePlaysWhite:
15951             if (whiteFlag) {
15952                 if (blackFlag)
15953                   GameEnds(GameIsDrawn, "Both players ran out of time",
15954                            GE_PLAYER);
15955                 else
15956                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15957             } else {
15958                 DisplayError(_("Your opponent is not out of time"), 0);
15959             }
15960             break;
15961           case MachinePlaysBlack:
15962             if (blackFlag) {
15963                 if (whiteFlag)
15964                   GameEnds(GameIsDrawn, "Both players ran out of time",
15965                            GE_PLAYER);
15966                 else
15967                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15968             } else {
15969                 DisplayError(_("Your opponent is not out of time"), 0);
15970             }
15971             break;
15972         }
15973     }
15974 }
15975
15976 void
15977 ClockClick (int which)
15978 {       // [HGM] code moved to back-end from winboard.c
15979         if(which) { // black clock
15980           if (gameMode == EditPosition || gameMode == IcsExamining) {
15981             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15982             SetBlackToPlayEvent();
15983           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15984                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15985           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15986           } else if (shiftKey) {
15987             AdjustClock(which, -1);
15988           } else if (gameMode == IcsPlayingWhite ||
15989                      gameMode == MachinePlaysBlack) {
15990             CallFlagEvent();
15991           }
15992         } else { // white clock
15993           if (gameMode == EditPosition || gameMode == IcsExamining) {
15994             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15995             SetWhiteToPlayEvent();
15996           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15997                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15998           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15999           } else if (shiftKey) {
16000             AdjustClock(which, -1);
16001           } else if (gameMode == IcsPlayingBlack ||
16002                    gameMode == MachinePlaysWhite) {
16003             CallFlagEvent();
16004           }
16005         }
16006 }
16007
16008 void
16009 DrawEvent ()
16010 {
16011     /* Offer draw or accept pending draw offer from opponent */
16012
16013     if (appData.icsActive) {
16014         /* Note: tournament rules require draw offers to be
16015            made after you make your move but before you punch
16016            your clock.  Currently ICS doesn't let you do that;
16017            instead, you immediately punch your clock after making
16018            a move, but you can offer a draw at any time. */
16019
16020         SendToICS(ics_prefix);
16021         SendToICS("draw\n");
16022         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
16023     } else if (cmailMsgLoaded) {
16024         if (currentMove == cmailOldMove &&
16025             commentList[cmailOldMove] != NULL &&
16026             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
16027                    "Black offers a draw" : "White offers a draw")) {
16028             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
16029             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
16030         } else if (currentMove == cmailOldMove + 1) {
16031             char *offer = WhiteOnMove(cmailOldMove) ?
16032               "White offers a draw" : "Black offers a draw";
16033             AppendComment(currentMove, offer, TRUE);
16034             DisplayComment(currentMove - 1, offer);
16035             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
16036         } else {
16037             DisplayError(_("You must make your move before offering a draw"), 0);
16038             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
16039         }
16040     } else if (first.offeredDraw) {
16041         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
16042     } else {
16043         if (first.sendDrawOffers) {
16044             SendToProgram("draw\n", &first);
16045             userOfferedDraw = TRUE;
16046         }
16047     }
16048 }
16049
16050 void
16051 AdjournEvent ()
16052 {
16053     /* Offer Adjourn or accept pending Adjourn offer from opponent */
16054
16055     if (appData.icsActive) {
16056         SendToICS(ics_prefix);
16057         SendToICS("adjourn\n");
16058     } else {
16059         /* Currently GNU Chess doesn't offer or accept Adjourns */
16060     }
16061 }
16062
16063
16064 void
16065 AbortEvent ()
16066 {
16067     /* Offer Abort or accept pending Abort offer from opponent */
16068
16069     if (appData.icsActive) {
16070         SendToICS(ics_prefix);
16071         SendToICS("abort\n");
16072     } else {
16073         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
16074     }
16075 }
16076
16077 void
16078 ResignEvent ()
16079 {
16080     /* Resign.  You can do this even if it's not your turn. */
16081
16082     if (appData.icsActive) {
16083         SendToICS(ics_prefix);
16084         SendToICS("resign\n");
16085     } else {
16086         switch (gameMode) {
16087           case MachinePlaysWhite:
16088             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16089             break;
16090           case MachinePlaysBlack:
16091             GameEnds(BlackWins, "White resigns", GE_PLAYER);
16092             break;
16093           case EditGame:
16094             if (cmailMsgLoaded) {
16095                 TruncateGame();
16096                 if (WhiteOnMove(cmailOldMove)) {
16097                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
16098                 } else {
16099                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16100                 }
16101                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16102             }
16103             break;
16104           default:
16105             break;
16106         }
16107     }
16108 }
16109
16110
16111 void
16112 StopObservingEvent ()
16113 {
16114     /* Stop observing current games */
16115     SendToICS(ics_prefix);
16116     SendToICS("unobserve\n");
16117 }
16118
16119 void
16120 StopExaminingEvent ()
16121 {
16122     /* Stop observing current game */
16123     SendToICS(ics_prefix);
16124     SendToICS("unexamine\n");
16125 }
16126
16127 void
16128 ForwardInner (int target)
16129 {
16130     int limit; int oldSeekGraphUp = seekGraphUp;
16131
16132     if (appData.debugMode)
16133         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16134                 target, currentMove, forwardMostMove);
16135
16136     if (gameMode == EditPosition)
16137       return;
16138
16139     seekGraphUp = FALSE;
16140     MarkTargetSquares(1);
16141     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16142
16143     if (gameMode == PlayFromGameFile && !pausing)
16144       PauseEvent();
16145
16146     if (gameMode == IcsExamining && pausing)
16147       limit = pauseExamForwardMostMove;
16148     else
16149       limit = forwardMostMove;
16150
16151     if (target > limit) target = limit;
16152
16153     if (target > 0 && moveList[target - 1][0]) {
16154         int fromX, fromY, toX, toY;
16155         toX = moveList[target - 1][2] - AAA;
16156         toY = moveList[target - 1][3] - ONE;
16157         if (moveList[target - 1][1] == '@') {
16158             if (appData.highlightLastMove) {
16159                 SetHighlights(-1, -1, toX, toY);
16160             }
16161         } else {
16162             fromX = moveList[target - 1][0] - AAA;
16163             fromY = moveList[target - 1][1] - ONE;
16164             if (target == currentMove + 1) {
16165                 if(moveList[target - 1][4] == ';') { // multi-leg
16166                     killX = moveList[target - 1][5] - AAA;
16167                     killY = moveList[target - 1][6] - ONE;
16168                 }
16169                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16170                 killX = killY = -1;
16171             }
16172             if (appData.highlightLastMove) {
16173                 SetHighlights(fromX, fromY, toX, toY);
16174             }
16175         }
16176     }
16177     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16178         gameMode == Training || gameMode == PlayFromGameFile ||
16179         gameMode == AnalyzeFile) {
16180         while (currentMove < target) {
16181             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16182             SendMoveToProgram(currentMove++, &first);
16183         }
16184     } else {
16185         currentMove = target;
16186     }
16187
16188     if (gameMode == EditGame || gameMode == EndOfGame) {
16189         whiteTimeRemaining = timeRemaining[0][currentMove];
16190         blackTimeRemaining = timeRemaining[1][currentMove];
16191     }
16192     DisplayBothClocks();
16193     DisplayMove(currentMove - 1);
16194     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16195     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16196     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16197         DisplayComment(currentMove - 1, commentList[currentMove]);
16198     }
16199     ClearMap(); // [HGM] exclude: invalidate map
16200 }
16201
16202
16203 void
16204 ForwardEvent ()
16205 {
16206     if (gameMode == IcsExamining && !pausing) {
16207         SendToICS(ics_prefix);
16208         SendToICS("forward\n");
16209     } else {
16210         ForwardInner(currentMove + 1);
16211     }
16212 }
16213
16214 void
16215 ToEndEvent ()
16216 {
16217     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16218         /* to optimze, we temporarily turn off analysis mode while we feed
16219          * the remaining moves to the engine. Otherwise we get analysis output
16220          * after each move.
16221          */
16222         if (first.analysisSupport) {
16223           SendToProgram("exit\nforce\n", &first);
16224           first.analyzing = FALSE;
16225         }
16226     }
16227
16228     if (gameMode == IcsExamining && !pausing) {
16229         SendToICS(ics_prefix);
16230         SendToICS("forward 999999\n");
16231     } else {
16232         ForwardInner(forwardMostMove);
16233     }
16234
16235     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16236         /* we have fed all the moves, so reactivate analysis mode */
16237         SendToProgram("analyze\n", &first);
16238         first.analyzing = TRUE;
16239         /*first.maybeThinking = TRUE;*/
16240         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16241     }
16242 }
16243
16244 void
16245 BackwardInner (int target)
16246 {
16247     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16248
16249     if (appData.debugMode)
16250         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16251                 target, currentMove, forwardMostMove);
16252
16253     if (gameMode == EditPosition) return;
16254     seekGraphUp = FALSE;
16255     MarkTargetSquares(1);
16256     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16257     if (currentMove <= backwardMostMove) {
16258         ClearHighlights();
16259         DrawPosition(full_redraw, boards[currentMove]);
16260         return;
16261     }
16262     if (gameMode == PlayFromGameFile && !pausing)
16263       PauseEvent();
16264
16265     if (moveList[target][0]) {
16266         int fromX, fromY, toX, toY;
16267         toX = moveList[target][2] - AAA;
16268         toY = moveList[target][3] - ONE;
16269         if (moveList[target][1] == '@') {
16270             if (appData.highlightLastMove) {
16271                 SetHighlights(-1, -1, toX, toY);
16272             }
16273         } else {
16274             fromX = moveList[target][0] - AAA;
16275             fromY = moveList[target][1] - ONE;
16276             if (target == currentMove - 1) {
16277                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16278             }
16279             if (appData.highlightLastMove) {
16280                 SetHighlights(fromX, fromY, toX, toY);
16281             }
16282         }
16283     }
16284     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16285         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16286         while (currentMove > target) {
16287             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16288                 // null move cannot be undone. Reload program with move history before it.
16289                 int i;
16290                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16291                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16292                 }
16293                 SendBoard(&first, i);
16294               if(second.analyzing) SendBoard(&second, i);
16295                 for(currentMove=i; currentMove<target; currentMove++) {
16296                     SendMoveToProgram(currentMove, &first);
16297                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16298                 }
16299                 break;
16300             }
16301             SendToBoth("undo\n");
16302             currentMove--;
16303         }
16304     } else {
16305         currentMove = target;
16306     }
16307
16308     if (gameMode == EditGame || gameMode == EndOfGame) {
16309         whiteTimeRemaining = timeRemaining[0][currentMove];
16310         blackTimeRemaining = timeRemaining[1][currentMove];
16311     }
16312     DisplayBothClocks();
16313     DisplayMove(currentMove - 1);
16314     DrawPosition(full_redraw, boards[currentMove]);
16315     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16316     // [HGM] PV info: routine tests if comment empty
16317     DisplayComment(currentMove - 1, commentList[currentMove]);
16318     ClearMap(); // [HGM] exclude: invalidate map
16319 }
16320
16321 void
16322 BackwardEvent ()
16323 {
16324     if (gameMode == IcsExamining && !pausing) {
16325         SendToICS(ics_prefix);
16326         SendToICS("backward\n");
16327     } else {
16328         BackwardInner(currentMove - 1);
16329     }
16330 }
16331
16332 void
16333 ToStartEvent ()
16334 {
16335     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16336         /* to optimize, we temporarily turn off analysis mode while we undo
16337          * all the moves. Otherwise we get analysis output after each undo.
16338          */
16339         if (first.analysisSupport) {
16340           SendToProgram("exit\nforce\n", &first);
16341           first.analyzing = FALSE;
16342         }
16343     }
16344
16345     if (gameMode == IcsExamining && !pausing) {
16346         SendToICS(ics_prefix);
16347         SendToICS("backward 999999\n");
16348     } else {
16349         BackwardInner(backwardMostMove);
16350     }
16351
16352     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16353         /* we have fed all the moves, so reactivate analysis mode */
16354         SendToProgram("analyze\n", &first);
16355         first.analyzing = TRUE;
16356         /*first.maybeThinking = TRUE;*/
16357         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16358     }
16359 }
16360
16361 void
16362 ToNrEvent (int to)
16363 {
16364   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16365   if (to >= forwardMostMove) to = forwardMostMove;
16366   if (to <= backwardMostMove) to = backwardMostMove;
16367   if (to < currentMove) {
16368     BackwardInner(to);
16369   } else {
16370     ForwardInner(to);
16371   }
16372 }
16373
16374 void
16375 RevertEvent (Boolean annotate)
16376 {
16377     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16378         return;
16379     }
16380     if (gameMode != IcsExamining) {
16381         DisplayError(_("You are not examining a game"), 0);
16382         return;
16383     }
16384     if (pausing) {
16385         DisplayError(_("You can't revert while pausing"), 0);
16386         return;
16387     }
16388     SendToICS(ics_prefix);
16389     SendToICS("revert\n");
16390 }
16391
16392 void
16393 RetractMoveEvent ()
16394 {
16395     switch (gameMode) {
16396       case MachinePlaysWhite:
16397       case MachinePlaysBlack:
16398         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16399             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16400             return;
16401         }
16402         if (forwardMostMove < 2) return;
16403         currentMove = forwardMostMove = forwardMostMove - 2;
16404         whiteTimeRemaining = timeRemaining[0][currentMove];
16405         blackTimeRemaining = timeRemaining[1][currentMove];
16406         DisplayBothClocks();
16407         DisplayMove(currentMove - 1);
16408         ClearHighlights();/*!! could figure this out*/
16409         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16410         SendToProgram("remove\n", &first);
16411         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16412         break;
16413
16414       case BeginningOfGame:
16415       default:
16416         break;
16417
16418       case IcsPlayingWhite:
16419       case IcsPlayingBlack:
16420         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16421             SendToICS(ics_prefix);
16422             SendToICS("takeback 2\n");
16423         } else {
16424             SendToICS(ics_prefix);
16425             SendToICS("takeback 1\n");
16426         }
16427         break;
16428     }
16429 }
16430
16431 void
16432 MoveNowEvent ()
16433 {
16434     ChessProgramState *cps;
16435
16436     switch (gameMode) {
16437       case MachinePlaysWhite:
16438         if (!WhiteOnMove(forwardMostMove)) {
16439             DisplayError(_("It is your turn"), 0);
16440             return;
16441         }
16442         cps = &first;
16443         break;
16444       case MachinePlaysBlack:
16445         if (WhiteOnMove(forwardMostMove)) {
16446             DisplayError(_("It is your turn"), 0);
16447             return;
16448         }
16449         cps = &first;
16450         break;
16451       case TwoMachinesPlay:
16452         if (WhiteOnMove(forwardMostMove) ==
16453             (first.twoMachinesColor[0] == 'w')) {
16454             cps = &first;
16455         } else {
16456             cps = &second;
16457         }
16458         break;
16459       case BeginningOfGame:
16460       default:
16461         return;
16462     }
16463     SendToProgram("?\n", cps);
16464 }
16465
16466 void
16467 TruncateGameEvent ()
16468 {
16469     EditGameEvent();
16470     if (gameMode != EditGame) return;
16471     TruncateGame();
16472 }
16473
16474 void
16475 TruncateGame ()
16476 {
16477     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16478     if (forwardMostMove > currentMove) {
16479         if (gameInfo.resultDetails != NULL) {
16480             free(gameInfo.resultDetails);
16481             gameInfo.resultDetails = NULL;
16482             gameInfo.result = GameUnfinished;
16483         }
16484         forwardMostMove = currentMove;
16485         HistorySet(parseList, backwardMostMove, forwardMostMove,
16486                    currentMove-1);
16487     }
16488 }
16489
16490 void
16491 HintEvent ()
16492 {
16493     if (appData.noChessProgram) return;
16494     switch (gameMode) {
16495       case MachinePlaysWhite:
16496         if (WhiteOnMove(forwardMostMove)) {
16497             DisplayError(_("Wait until your turn."), 0);
16498             return;
16499         }
16500         break;
16501       case BeginningOfGame:
16502       case MachinePlaysBlack:
16503         if (!WhiteOnMove(forwardMostMove)) {
16504             DisplayError(_("Wait until your turn."), 0);
16505             return;
16506         }
16507         break;
16508       default:
16509         DisplayError(_("No hint available"), 0);
16510         return;
16511     }
16512     SendToProgram("hint\n", &first);
16513     hintRequested = TRUE;
16514 }
16515
16516 int
16517 SaveSelected (FILE *g, int dummy, char *dummy2)
16518 {
16519     ListGame * lg = (ListGame *) gameList.head;
16520     int nItem, cnt=0;
16521     FILE *f;
16522
16523     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16524         DisplayError(_("Game list not loaded or empty"), 0);
16525         return 0;
16526     }
16527
16528     creatingBook = TRUE; // suppresses stuff during load game
16529
16530     /* Get list size */
16531     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16532         if(lg->position >= 0) { // selected?
16533             LoadGame(f, nItem, "", TRUE);
16534             SaveGamePGN2(g); // leaves g open
16535             cnt++; DoEvents();
16536         }
16537         lg = (ListGame *) lg->node.succ;
16538     }
16539
16540     fclose(g);
16541     creatingBook = FALSE;
16542
16543     return cnt;
16544 }
16545
16546 void
16547 CreateBookEvent ()
16548 {
16549     ListGame * lg = (ListGame *) gameList.head;
16550     FILE *f, *g;
16551     int nItem;
16552     static int secondTime = FALSE;
16553
16554     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16555         DisplayError(_("Game list not loaded or empty"), 0);
16556         return;
16557     }
16558
16559     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16560         fclose(g);
16561         secondTime++;
16562         DisplayNote(_("Book file exists! Try again for overwrite."));
16563         return;
16564     }
16565
16566     creatingBook = TRUE;
16567     secondTime = FALSE;
16568
16569     /* Get list size */
16570     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16571         if(lg->position >= 0) {
16572             LoadGame(f, nItem, "", TRUE);
16573             AddGameToBook(TRUE);
16574             DoEvents();
16575         }
16576         lg = (ListGame *) lg->node.succ;
16577     }
16578
16579     creatingBook = FALSE;
16580     FlushBook();
16581 }
16582
16583 void
16584 BookEvent ()
16585 {
16586     if (appData.noChessProgram) return;
16587     switch (gameMode) {
16588       case MachinePlaysWhite:
16589         if (WhiteOnMove(forwardMostMove)) {
16590             DisplayError(_("Wait until your turn."), 0);
16591             return;
16592         }
16593         break;
16594       case BeginningOfGame:
16595       case MachinePlaysBlack:
16596         if (!WhiteOnMove(forwardMostMove)) {
16597             DisplayError(_("Wait until your turn."), 0);
16598             return;
16599         }
16600         break;
16601       case EditPosition:
16602         EditPositionDone(TRUE);
16603         break;
16604       case TwoMachinesPlay:
16605         return;
16606       default:
16607         break;
16608     }
16609     SendToProgram("bk\n", &first);
16610     bookOutput[0] = NULLCHAR;
16611     bookRequested = TRUE;
16612 }
16613
16614 void
16615 AboutGameEvent ()
16616 {
16617     char *tags = PGNTags(&gameInfo);
16618     TagsPopUp(tags, CmailMsg());
16619     free(tags);
16620 }
16621
16622 /* end button procedures */
16623
16624 void
16625 PrintPosition (FILE *fp, int move)
16626 {
16627     int i, j;
16628
16629     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16630         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16631             char c = PieceToChar(boards[move][i][j]);
16632             fputc(c == '?' ? '.' : c, fp);
16633             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16634         }
16635     }
16636     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16637       fprintf(fp, "white to play\n");
16638     else
16639       fprintf(fp, "black to play\n");
16640 }
16641
16642 void
16643 PrintOpponents (FILE *fp)
16644 {
16645     if (gameInfo.white != NULL) {
16646         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16647     } else {
16648         fprintf(fp, "\n");
16649     }
16650 }
16651
16652 /* Find last component of program's own name, using some heuristics */
16653 void
16654 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16655 {
16656     char *p, *q, c;
16657     int local = (strcmp(host, "localhost") == 0);
16658     while (!local && (p = strchr(prog, ';')) != NULL) {
16659         p++;
16660         while (*p == ' ') p++;
16661         prog = p;
16662     }
16663     if (*prog == '"' || *prog == '\'') {
16664         q = strchr(prog + 1, *prog);
16665     } else {
16666         q = strchr(prog, ' ');
16667     }
16668     if (q == NULL) q = prog + strlen(prog);
16669     p = q;
16670     while (p >= prog && *p != '/' && *p != '\\') p--;
16671     p++;
16672     if(p == prog && *p == '"') p++;
16673     c = *q; *q = 0;
16674     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16675     memcpy(buf, p, q - p);
16676     buf[q - p] = NULLCHAR;
16677     if (!local) {
16678         strcat(buf, "@");
16679         strcat(buf, host);
16680     }
16681 }
16682
16683 char *
16684 TimeControlTagValue ()
16685 {
16686     char buf[MSG_SIZ];
16687     if (!appData.clockMode) {
16688       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16689     } else if (movesPerSession > 0) {
16690       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16691     } else if (timeIncrement == 0) {
16692       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16693     } else {
16694       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16695     }
16696     return StrSave(buf);
16697 }
16698
16699 void
16700 SetGameInfo ()
16701 {
16702     /* This routine is used only for certain modes */
16703     VariantClass v = gameInfo.variant;
16704     ChessMove r = GameUnfinished;
16705     char *p = NULL;
16706
16707     if(keepInfo) return;
16708
16709     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16710         r = gameInfo.result;
16711         p = gameInfo.resultDetails;
16712         gameInfo.resultDetails = NULL;
16713     }
16714     ClearGameInfo(&gameInfo);
16715     gameInfo.variant = v;
16716
16717     switch (gameMode) {
16718       case MachinePlaysWhite:
16719         gameInfo.event = StrSave( appData.pgnEventHeader );
16720         gameInfo.site = StrSave(HostName());
16721         gameInfo.date = PGNDate();
16722         gameInfo.round = StrSave("-");
16723         gameInfo.white = StrSave(first.tidy);
16724         gameInfo.black = StrSave(UserName());
16725         gameInfo.timeControl = TimeControlTagValue();
16726         break;
16727
16728       case MachinePlaysBlack:
16729         gameInfo.event = StrSave( appData.pgnEventHeader );
16730         gameInfo.site = StrSave(HostName());
16731         gameInfo.date = PGNDate();
16732         gameInfo.round = StrSave("-");
16733         gameInfo.white = StrSave(UserName());
16734         gameInfo.black = StrSave(first.tidy);
16735         gameInfo.timeControl = TimeControlTagValue();
16736         break;
16737
16738       case TwoMachinesPlay:
16739         gameInfo.event = StrSave( appData.pgnEventHeader );
16740         gameInfo.site = StrSave(HostName());
16741         gameInfo.date = PGNDate();
16742         if (roundNr > 0) {
16743             char buf[MSG_SIZ];
16744             snprintf(buf, MSG_SIZ, "%d", roundNr);
16745             gameInfo.round = StrSave(buf);
16746         } else {
16747             gameInfo.round = StrSave("-");
16748         }
16749         if (first.twoMachinesColor[0] == 'w') {
16750             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16751             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16752         } else {
16753             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16754             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16755         }
16756         gameInfo.timeControl = TimeControlTagValue();
16757         break;
16758
16759       case EditGame:
16760         gameInfo.event = StrSave("Edited game");
16761         gameInfo.site = StrSave(HostName());
16762         gameInfo.date = PGNDate();
16763         gameInfo.round = StrSave("-");
16764         gameInfo.white = StrSave("-");
16765         gameInfo.black = StrSave("-");
16766         gameInfo.result = r;
16767         gameInfo.resultDetails = p;
16768         break;
16769
16770       case EditPosition:
16771         gameInfo.event = StrSave("Edited position");
16772         gameInfo.site = StrSave(HostName());
16773         gameInfo.date = PGNDate();
16774         gameInfo.round = StrSave("-");
16775         gameInfo.white = StrSave("-");
16776         gameInfo.black = StrSave("-");
16777         break;
16778
16779       case IcsPlayingWhite:
16780       case IcsPlayingBlack:
16781       case IcsObserving:
16782       case IcsExamining:
16783         break;
16784
16785       case PlayFromGameFile:
16786         gameInfo.event = StrSave("Game from non-PGN file");
16787         gameInfo.site = StrSave(HostName());
16788         gameInfo.date = PGNDate();
16789         gameInfo.round = StrSave("-");
16790         gameInfo.white = StrSave("?");
16791         gameInfo.black = StrSave("?");
16792         break;
16793
16794       default:
16795         break;
16796     }
16797 }
16798
16799 void
16800 ReplaceComment (int index, char *text)
16801 {
16802     int len;
16803     char *p;
16804     float score;
16805
16806     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16807        pvInfoList[index-1].depth == len &&
16808        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16809        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16810     while (*text == '\n') text++;
16811     len = strlen(text);
16812     while (len > 0 && text[len - 1] == '\n') len--;
16813
16814     if (commentList[index] != NULL)
16815       free(commentList[index]);
16816
16817     if (len == 0) {
16818         commentList[index] = NULL;
16819         return;
16820     }
16821   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16822       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16823       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16824     commentList[index] = (char *) malloc(len + 2);
16825     strncpy(commentList[index], text, len);
16826     commentList[index][len] = '\n';
16827     commentList[index][len + 1] = NULLCHAR;
16828   } else {
16829     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16830     char *p;
16831     commentList[index] = (char *) malloc(len + 7);
16832     safeStrCpy(commentList[index], "{\n", 3);
16833     safeStrCpy(commentList[index]+2, text, len+1);
16834     commentList[index][len+2] = NULLCHAR;
16835     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16836     strcat(commentList[index], "\n}\n");
16837   }
16838 }
16839
16840 void
16841 CrushCRs (char *text)
16842 {
16843   char *p = text;
16844   char *q = text;
16845   char ch;
16846
16847   do {
16848     ch = *p++;
16849     if (ch == '\r') continue;
16850     *q++ = ch;
16851   } while (ch != '\0');
16852 }
16853
16854 void
16855 AppendComment (int index, char *text, Boolean addBraces)
16856 /* addBraces  tells if we should add {} */
16857 {
16858     int oldlen, len;
16859     char *old;
16860
16861 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16862     if(addBraces == 3) addBraces = 0; else // force appending literally
16863     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16864
16865     CrushCRs(text);
16866     while (*text == '\n') text++;
16867     len = strlen(text);
16868     while (len > 0 && text[len - 1] == '\n') len--;
16869     text[len] = NULLCHAR;
16870
16871     if (len == 0) return;
16872
16873     if (commentList[index] != NULL) {
16874       Boolean addClosingBrace = addBraces;
16875         old = commentList[index];
16876         oldlen = strlen(old);
16877         while(commentList[index][oldlen-1] ==  '\n')
16878           commentList[index][--oldlen] = NULLCHAR;
16879         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16880         safeStrCpy(commentList[index], old, oldlen + len + 6);
16881         free(old);
16882         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16883         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16884           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16885           while (*text == '\n') { text++; len--; }
16886           commentList[index][--oldlen] = NULLCHAR;
16887       }
16888         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16889         else          strcat(commentList[index], "\n");
16890         strcat(commentList[index], text);
16891         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16892         else          strcat(commentList[index], "\n");
16893     } else {
16894         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16895         if(addBraces)
16896           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16897         else commentList[index][0] = NULLCHAR;
16898         strcat(commentList[index], text);
16899         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16900         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16901     }
16902 }
16903
16904 static char *
16905 FindStr (char * text, char * sub_text)
16906 {
16907     char * result = strstr( text, sub_text );
16908
16909     if( result != NULL ) {
16910         result += strlen( sub_text );
16911     }
16912
16913     return result;
16914 }
16915
16916 /* [AS] Try to extract PV info from PGN comment */
16917 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16918 char *
16919 GetInfoFromComment (int index, char * text)
16920 {
16921     char * sep = text, *p;
16922
16923     if( text != NULL && index > 0 ) {
16924         int score = 0;
16925         int depth = 0;
16926         int time = -1, sec = 0, deci;
16927         char * s_eval = FindStr( text, "[%eval " );
16928         char * s_emt = FindStr( text, "[%emt " );
16929 #if 0
16930         if( s_eval != NULL || s_emt != NULL ) {
16931 #else
16932         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16933 #endif
16934             /* New style */
16935             char delim;
16936
16937             if( s_eval != NULL ) {
16938                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16939                     return text;
16940                 }
16941
16942                 if( delim != ']' ) {
16943                     return text;
16944                 }
16945             }
16946
16947             if( s_emt != NULL ) {
16948             }
16949                 return text;
16950         }
16951         else {
16952             /* We expect something like: [+|-]nnn.nn/dd */
16953             int score_lo = 0;
16954
16955             if(*text != '{') return text; // [HGM] braces: must be normal comment
16956
16957             sep = strchr( text, '/' );
16958             if( sep == NULL || sep < (text+4) ) {
16959                 return text;
16960             }
16961
16962             p = text;
16963             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16964             if(p[1] == '(') { // comment starts with PV
16965                p = strchr(p, ')'); // locate end of PV
16966                if(p == NULL || sep < p+5) return text;
16967                // at this point we have something like "{(.*) +0.23/6 ..."
16968                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16969                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16970                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16971             }
16972             time = -1; sec = -1; deci = -1;
16973             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16974                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16975                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16976                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16977                 return text;
16978             }
16979
16980             if( score_lo < 0 || score_lo >= 100 ) {
16981                 return text;
16982             }
16983
16984             if(sec >= 0) time = 600*time + 10*sec; else
16985             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16986
16987             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16988
16989             /* [HGM] PV time: now locate end of PV info */
16990             while( *++sep >= '0' && *sep <= '9'); // strip depth
16991             if(time >= 0)
16992             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16993             if(sec >= 0)
16994             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16995             if(deci >= 0)
16996             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16997             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16998         }
16999
17000         if( depth <= 0 ) {
17001             return text;
17002         }
17003
17004         if( time < 0 ) {
17005             time = -1;
17006         }
17007
17008         pvInfoList[index-1].depth = depth;
17009         pvInfoList[index-1].score = score;
17010         pvInfoList[index-1].time  = 10*time; // centi-sec
17011         if(*sep == '}') *sep = 0; else *--sep = '{';
17012         if(p != text) {
17013             while(*p++ = *sep++)
17014                                 ;
17015             sep = text;
17016         } // squeeze out space between PV and comment, and return both
17017     }
17018     return sep;
17019 }
17020
17021 void
17022 SendToProgram (char *message, ChessProgramState *cps)
17023 {
17024     int count, outCount, error;
17025     char buf[MSG_SIZ];
17026
17027     if (cps->pr == NoProc) return;
17028     Attention(cps);
17029
17030     if (appData.debugMode) {
17031         TimeMark now;
17032         GetTimeMark(&now);
17033         fprintf(debugFP, "%ld >%-6s: %s",
17034                 SubtractTimeMarks(&now, &programStartTime),
17035                 cps->which, message);
17036         if(serverFP)
17037             fprintf(serverFP, "%ld >%-6s: %s",
17038                 SubtractTimeMarks(&now, &programStartTime),
17039                 cps->which, message), fflush(serverFP);
17040     }
17041
17042     count = strlen(message);
17043     outCount = OutputToProcess(cps->pr, message, count, &error);
17044     if (outCount < count && !exiting
17045                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
17046       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
17047       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
17048         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17049             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17050                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17051                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17052                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17053             } else {
17054                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17055                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17056                 gameInfo.result = res;
17057             }
17058             gameInfo.resultDetails = StrSave(buf);
17059         }
17060         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17061         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17062     }
17063 }
17064
17065 void
17066 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
17067 {
17068     char *end_str;
17069     char buf[MSG_SIZ];
17070     ChessProgramState *cps = (ChessProgramState *)closure;
17071
17072     if (isr != cps->isr) return; /* Killed intentionally */
17073     if (count <= 0) {
17074         if (count == 0) {
17075             RemoveInputSource(cps->isr);
17076             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
17077                     _(cps->which), cps->program);
17078             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
17079             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17080                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17081                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17082                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17083                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17084                 } else {
17085                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17086                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17087                     gameInfo.result = res;
17088                 }
17089                 gameInfo.resultDetails = StrSave(buf);
17090             }
17091             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17092             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
17093         } else {
17094             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
17095                     _(cps->which), cps->program);
17096             RemoveInputSource(cps->isr);
17097
17098             /* [AS] Program is misbehaving badly... kill it */
17099             if( count == -2 ) {
17100                 DestroyChildProcess( cps->pr, 9 );
17101                 cps->pr = NoProc;
17102             }
17103
17104             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17105         }
17106         return;
17107     }
17108
17109     if ((end_str = strchr(message, '\r')) != NULL)
17110       *end_str = NULLCHAR;
17111     if ((end_str = strchr(message, '\n')) != NULL)
17112       *end_str = NULLCHAR;
17113
17114     if (appData.debugMode) {
17115         TimeMark now; int print = 1;
17116         char *quote = ""; char c; int i;
17117
17118         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17119                 char start = message[0];
17120                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17121                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17122                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
17123                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17124                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17125                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
17126                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17127                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
17128                    sscanf(message, "hint: %c", &c)!=1 &&
17129                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
17130                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17131                     print = (appData.engineComments >= 2);
17132                 }
17133                 message[0] = start; // restore original message
17134         }
17135         if(print) {
17136                 GetTimeMark(&now);
17137                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17138                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17139                         quote,
17140                         message);
17141                 if(serverFP)
17142                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
17143                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17144                         quote,
17145                         message), fflush(serverFP);
17146         }
17147     }
17148
17149     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17150     if (appData.icsEngineAnalyze) {
17151         if (strstr(message, "whisper") != NULL ||
17152              strstr(message, "kibitz") != NULL ||
17153             strstr(message, "tellics") != NULL) return;
17154     }
17155
17156     HandleMachineMove(message, cps);
17157 }
17158
17159
17160 void
17161 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17162 {
17163     char buf[MSG_SIZ];
17164     int seconds;
17165
17166     if( timeControl_2 > 0 ) {
17167         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17168             tc = timeControl_2;
17169         }
17170     }
17171     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17172     inc /= cps->timeOdds;
17173     st  /= cps->timeOdds;
17174
17175     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17176
17177     if (st > 0) {
17178       /* Set exact time per move, normally using st command */
17179       if (cps->stKludge) {
17180         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17181         seconds = st % 60;
17182         if (seconds == 0) {
17183           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17184         } else {
17185           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17186         }
17187       } else {
17188         snprintf(buf, MSG_SIZ, "st %d\n", st);
17189       }
17190     } else {
17191       /* Set conventional or incremental time control, using level command */
17192       if (seconds == 0) {
17193         /* Note old gnuchess bug -- minutes:seconds used to not work.
17194            Fixed in later versions, but still avoid :seconds
17195            when seconds is 0. */
17196         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17197       } else {
17198         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17199                  seconds, inc/1000.);
17200       }
17201     }
17202     SendToProgram(buf, cps);
17203
17204     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17205     /* Orthogonally, limit search to given depth */
17206     if (sd > 0) {
17207       if (cps->sdKludge) {
17208         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17209       } else {
17210         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17211       }
17212       SendToProgram(buf, cps);
17213     }
17214
17215     if(cps->nps >= 0) { /* [HGM] nps */
17216         if(cps->supportsNPS == FALSE)
17217           cps->nps = -1; // don't use if engine explicitly says not supported!
17218         else {
17219           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17220           SendToProgram(buf, cps);
17221         }
17222     }
17223 }
17224
17225 ChessProgramState *
17226 WhitePlayer ()
17227 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17228 {
17229     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17230        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17231         return &second;
17232     return &first;
17233 }
17234
17235 void
17236 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17237 {
17238     char message[MSG_SIZ];
17239     long time, otime;
17240
17241     /* Note: this routine must be called when the clocks are stopped
17242        or when they have *just* been set or switched; otherwise
17243        it will be off by the time since the current tick started.
17244     */
17245     if (machineWhite) {
17246         time = whiteTimeRemaining / 10;
17247         otime = blackTimeRemaining / 10;
17248     } else {
17249         time = blackTimeRemaining / 10;
17250         otime = whiteTimeRemaining / 10;
17251     }
17252     /* [HGM] translate opponent's time by time-odds factor */
17253     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17254
17255     if (time <= 0) time = 1;
17256     if (otime <= 0) otime = 1;
17257
17258     snprintf(message, MSG_SIZ, "time %ld\n", time);
17259     SendToProgram(message, cps);
17260
17261     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17262     SendToProgram(message, cps);
17263 }
17264
17265 char *
17266 EngineDefinedVariant (ChessProgramState *cps, int n)
17267 {   // return name of n-th unknown variant that engine supports
17268     static char buf[MSG_SIZ];
17269     char *p, *s = cps->variants;
17270     if(!s) return NULL;
17271     do { // parse string from variants feature
17272       VariantClass v;
17273         p = strchr(s, ',');
17274         if(p) *p = NULLCHAR;
17275       v = StringToVariant(s);
17276       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17277         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17278             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17279                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17280                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17281                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17282             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17283         }
17284         if(p) *p++ = ',';
17285         if(n < 0) return buf;
17286     } while(s = p);
17287     return NULL;
17288 }
17289
17290 int
17291 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17292 {
17293   char buf[MSG_SIZ];
17294   int len = strlen(name);
17295   int val;
17296
17297   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17298     (*p) += len + 1;
17299     sscanf(*p, "%d", &val);
17300     *loc = (val != 0);
17301     while (**p && **p != ' ')
17302       (*p)++;
17303     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17304     SendToProgram(buf, cps);
17305     return TRUE;
17306   }
17307   return FALSE;
17308 }
17309
17310 int
17311 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17312 {
17313   char buf[MSG_SIZ];
17314   int len = strlen(name);
17315   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17316     (*p) += len + 1;
17317     sscanf(*p, "%d", loc);
17318     while (**p && **p != ' ') (*p)++;
17319     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17320     SendToProgram(buf, cps);
17321     return TRUE;
17322   }
17323   return FALSE;
17324 }
17325
17326 int
17327 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17328 {
17329   char buf[MSG_SIZ];
17330   int len = strlen(name);
17331   if (strncmp((*p), name, len) == 0
17332       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17333     (*p) += len + 2;
17334     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
17335     FREE(*loc); *loc = malloc(len);
17336     strncpy(*loc, *p, len);
17337     sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17338     while (**p && **p != '\"') (*p)++;
17339     if (**p == '\"') (*p)++;
17340     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17341     SendToProgram(buf, cps);
17342     return TRUE;
17343   }
17344   return FALSE;
17345 }
17346
17347 int
17348 ParseOption (Option *opt, ChessProgramState *cps)
17349 // [HGM] options: process the string that defines an engine option, and determine
17350 // name, type, default value, and allowed value range
17351 {
17352         char *p, *q, buf[MSG_SIZ];
17353         int n, min = (-1)<<31, max = 1<<31, def;
17354
17355         opt->target = &opt->value;   // OK for spin/slider and checkbox
17356         if(p = strstr(opt->name, " -spin ")) {
17357             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17358             if(max < min) max = min; // enforce consistency
17359             if(def < min) def = min;
17360             if(def > max) def = max;
17361             opt->value = def;
17362             opt->min = min;
17363             opt->max = max;
17364             opt->type = Spin;
17365         } else if((p = strstr(opt->name, " -slider "))) {
17366             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17367             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17368             if(max < min) max = min; // enforce consistency
17369             if(def < min) def = min;
17370             if(def > max) def = max;
17371             opt->value = def;
17372             opt->min = min;
17373             opt->max = max;
17374             opt->type = Spin; // Slider;
17375         } else if((p = strstr(opt->name, " -string "))) {
17376             opt->textValue = p+9;
17377             opt->type = TextBox;
17378             opt->target = &opt->textValue;
17379         } else if((p = strstr(opt->name, " -file "))) {
17380             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17381             opt->target = opt->textValue = p+7;
17382             opt->type = FileName; // FileName;
17383             opt->target = &opt->textValue;
17384         } else if((p = strstr(opt->name, " -path "))) {
17385             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17386             opt->target = opt->textValue = p+7;
17387             opt->type = PathName; // PathName;
17388             opt->target = &opt->textValue;
17389         } else if(p = strstr(opt->name, " -check ")) {
17390             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17391             opt->value = (def != 0);
17392             opt->type = CheckBox;
17393         } else if(p = strstr(opt->name, " -combo ")) {
17394             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17395             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17396             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17397             opt->value = n = 0;
17398             while(q = StrStr(q, " /// ")) {
17399                 n++; *q = 0;    // count choices, and null-terminate each of them
17400                 q += 5;
17401                 if(*q == '*') { // remember default, which is marked with * prefix
17402                     q++;
17403                     opt->value = n;
17404                 }
17405                 cps->comboList[cps->comboCnt++] = q;
17406             }
17407             cps->comboList[cps->comboCnt++] = NULL;
17408             opt->max = n + 1;
17409             opt->type = ComboBox;
17410         } else if(p = strstr(opt->name, " -button")) {
17411             opt->type = Button;
17412         } else if(p = strstr(opt->name, " -save")) {
17413             opt->type = SaveButton;
17414         } else return FALSE;
17415         *p = 0; // terminate option name
17416         *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17417         if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17418         // now look if the command-line options define a setting for this engine option.
17419         if(cps->optionSettings && cps->optionSettings[0])
17420             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17421         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17422           snprintf(buf, MSG_SIZ, "option %s", p);
17423                 if(p = strstr(buf, ",")) *p = 0;
17424                 if(q = strchr(buf, '=')) switch(opt->type) {
17425                     case ComboBox:
17426                         for(n=0; n<opt->max; n++)
17427                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17428                         break;
17429                     case TextBox:
17430                     case FileName:
17431                     case PathName:
17432                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17433                         break;
17434                     case Spin:
17435                     case CheckBox:
17436                         opt->value = atoi(q+1);
17437                     default:
17438                         break;
17439                 }
17440                 strcat(buf, "\n");
17441                 SendToProgram(buf, cps);
17442         }
17443         return TRUE;
17444 }
17445
17446 void
17447 FeatureDone (ChessProgramState *cps, int val)
17448 {
17449   DelayedEventCallback cb = GetDelayedEvent();
17450   if ((cb == InitBackEnd3 && cps == &first) ||
17451       (cb == SettingsMenuIfReady && cps == &second) ||
17452       (cb == LoadEngine) || (cb == StartSecond) ||
17453       (cb == TwoMachinesEventIfReady)) {
17454     CancelDelayedEvent();
17455     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17456   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17457   cps->initDone = val;
17458   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17459 }
17460
17461 /* Parse feature command from engine */
17462 void
17463 ParseFeatures (char *args, ChessProgramState *cps)
17464 {
17465   char *p = args;
17466   char *q = NULL;
17467   int val;
17468   char buf[MSG_SIZ];
17469
17470   for (;;) {
17471     while (*p == ' ') p++;
17472     if (*p == NULLCHAR) return;
17473
17474     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17475     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17476     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17477     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17478     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17479     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17480     if (BoolFeature(&p, "reuse", &val, cps)) {
17481       /* Engine can disable reuse, but can't enable it if user said no */
17482       if (!val) cps->reuse = FALSE;
17483       continue;
17484     }
17485     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17486     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17487       if (gameMode == TwoMachinesPlay) {
17488         DisplayTwoMachinesTitle();
17489       } else {
17490         DisplayTitle("");
17491       }
17492       continue;
17493     }
17494     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17495     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17496     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17497     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17498     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17499     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17500     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17501     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17502     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17503     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17504     if (IntFeature(&p, "done", &val, cps)) {
17505       FeatureDone(cps, val);
17506       continue;
17507     }
17508     /* Added by Tord: */
17509     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17510     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17511     /* End of additions by Tord */
17512
17513     /* [HGM] added features: */
17514     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17515     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17516     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17517     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17518     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17519     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17520     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17521     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17522         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17523         if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17524         FREE(cps->option[cps->nrOptions].name);
17525         cps->option[cps->nrOptions].name = q; q = NULL;
17526         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17527           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17528             SendToProgram(buf, cps);
17529             continue;
17530         }
17531         if(cps->nrOptions >= MAX_OPTIONS) {
17532             cps->nrOptions--;
17533             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17534             DisplayError(buf, 0);
17535         }
17536         continue;
17537     }
17538     /* End of additions by HGM */
17539
17540     /* unknown feature: complain and skip */
17541     q = p;
17542     while (*q && *q != '=') q++;
17543     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17544     SendToProgram(buf, cps);
17545     p = q;
17546     if (*p == '=') {
17547       p++;
17548       if (*p == '\"') {
17549         p++;
17550         while (*p && *p != '\"') p++;
17551         if (*p == '\"') p++;
17552       } else {
17553         while (*p && *p != ' ') p++;
17554       }
17555     }
17556   }
17557
17558 }
17559
17560 void
17561 PeriodicUpdatesEvent (int newState)
17562 {
17563     if (newState == appData.periodicUpdates)
17564       return;
17565
17566     appData.periodicUpdates=newState;
17567
17568     /* Display type changes, so update it now */
17569 //    DisplayAnalysis();
17570
17571     /* Get the ball rolling again... */
17572     if (newState) {
17573         AnalysisPeriodicEvent(1);
17574         StartAnalysisClock();
17575     }
17576 }
17577
17578 void
17579 PonderNextMoveEvent (int newState)
17580 {
17581     if (newState == appData.ponderNextMove) return;
17582     if (gameMode == EditPosition) EditPositionDone(TRUE);
17583     if (newState) {
17584         SendToProgram("hard\n", &first);
17585         if (gameMode == TwoMachinesPlay) {
17586             SendToProgram("hard\n", &second);
17587         }
17588     } else {
17589         SendToProgram("easy\n", &first);
17590         thinkOutput[0] = NULLCHAR;
17591         if (gameMode == TwoMachinesPlay) {
17592             SendToProgram("easy\n", &second);
17593         }
17594     }
17595     appData.ponderNextMove = newState;
17596 }
17597
17598 void
17599 NewSettingEvent (int option, int *feature, char *command, int value)
17600 {
17601     char buf[MSG_SIZ];
17602
17603     if (gameMode == EditPosition) EditPositionDone(TRUE);
17604     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17605     if(feature == NULL || *feature) SendToProgram(buf, &first);
17606     if (gameMode == TwoMachinesPlay) {
17607         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17608     }
17609 }
17610
17611 void
17612 ShowThinkingEvent ()
17613 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17614 {
17615     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17616     int newState = appData.showThinking
17617         // [HGM] thinking: other features now need thinking output as well
17618         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17619
17620     if (oldState == newState) return;
17621     oldState = newState;
17622     if (gameMode == EditPosition) EditPositionDone(TRUE);
17623     if (oldState) {
17624         SendToProgram("post\n", &first);
17625         if (gameMode == TwoMachinesPlay) {
17626             SendToProgram("post\n", &second);
17627         }
17628     } else {
17629         SendToProgram("nopost\n", &first);
17630         thinkOutput[0] = NULLCHAR;
17631         if (gameMode == TwoMachinesPlay) {
17632             SendToProgram("nopost\n", &second);
17633         }
17634     }
17635 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17636 }
17637
17638 void
17639 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17640 {
17641   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17642   if (pr == NoProc) return;
17643   AskQuestion(title, question, replyPrefix, pr);
17644 }
17645
17646 void
17647 TypeInEvent (char firstChar)
17648 {
17649     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17650         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17651         gameMode == AnalyzeMode || gameMode == EditGame ||
17652         gameMode == EditPosition || gameMode == IcsExamining ||
17653         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17654         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17655                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17656                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17657         gameMode == Training) PopUpMoveDialog(firstChar);
17658 }
17659
17660 void
17661 TypeInDoneEvent (char *move)
17662 {
17663         Board board;
17664         int n, fromX, fromY, toX, toY;
17665         char promoChar;
17666         ChessMove moveType;
17667
17668         // [HGM] FENedit
17669         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17670                 EditPositionPasteFEN(move);
17671                 return;
17672         }
17673         // [HGM] movenum: allow move number to be typed in any mode
17674         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17675           ToNrEvent(2*n-1);
17676           return;
17677         }
17678         // undocumented kludge: allow command-line option to be typed in!
17679         // (potentially fatal, and does not implement the effect of the option.)
17680         // should only be used for options that are values on which future decisions will be made,
17681         // and definitely not on options that would be used during initialization.
17682         if(strstr(move, "!!! -") == move) {
17683             ParseArgsFromString(move+4);
17684             return;
17685         }
17686
17687       if (gameMode != EditGame && currentMove != forwardMostMove &&
17688         gameMode != Training) {
17689         DisplayMoveError(_("Displayed move is not current"));
17690       } else {
17691         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17692           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17693         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17694         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17695           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17696           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17697         } else {
17698           DisplayMoveError(_("Could not parse move"));
17699         }
17700       }
17701 }
17702
17703 void
17704 DisplayMove (int moveNumber)
17705 {
17706     char message[MSG_SIZ];
17707     char res[MSG_SIZ];
17708     char cpThinkOutput[MSG_SIZ];
17709
17710     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17711
17712     if (moveNumber == forwardMostMove - 1 ||
17713         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17714
17715         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17716
17717         if (strchr(cpThinkOutput, '\n')) {
17718             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17719         }
17720     } else {
17721         *cpThinkOutput = NULLCHAR;
17722     }
17723
17724     /* [AS] Hide thinking from human user */
17725     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17726         *cpThinkOutput = NULLCHAR;
17727         if( thinkOutput[0] != NULLCHAR ) {
17728             int i;
17729
17730             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17731                 cpThinkOutput[i] = '.';
17732             }
17733             cpThinkOutput[i] = NULLCHAR;
17734             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17735         }
17736     }
17737
17738     if (moveNumber == forwardMostMove - 1 &&
17739         gameInfo.resultDetails != NULL) {
17740         if (gameInfo.resultDetails[0] == NULLCHAR) {
17741           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17742         } else {
17743           snprintf(res, MSG_SIZ, " {%s} %s",
17744                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17745         }
17746     } else {
17747         res[0] = NULLCHAR;
17748     }
17749
17750     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17751         DisplayMessage(res, cpThinkOutput);
17752     } else {
17753       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17754                 WhiteOnMove(moveNumber) ? " " : ".. ",
17755                 parseList[moveNumber], res);
17756         DisplayMessage(message, cpThinkOutput);
17757     }
17758 }
17759
17760 void
17761 DisplayComment (int moveNumber, char *text)
17762 {
17763     char title[MSG_SIZ];
17764
17765     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17766       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17767     } else {
17768       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17769               WhiteOnMove(moveNumber) ? " " : ".. ",
17770               parseList[moveNumber]);
17771     }
17772     if (text != NULL && (appData.autoDisplayComment || commentUp))
17773         CommentPopUp(title, text);
17774 }
17775
17776 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17777  * might be busy thinking or pondering.  It can be omitted if your
17778  * gnuchess is configured to stop thinking immediately on any user
17779  * input.  However, that gnuchess feature depends on the FIONREAD
17780  * ioctl, which does not work properly on some flavors of Unix.
17781  */
17782 void
17783 Attention (ChessProgramState *cps)
17784 {
17785 #if ATTENTION
17786     if (!cps->useSigint) return;
17787     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17788     switch (gameMode) {
17789       case MachinePlaysWhite:
17790       case MachinePlaysBlack:
17791       case TwoMachinesPlay:
17792       case IcsPlayingWhite:
17793       case IcsPlayingBlack:
17794       case AnalyzeMode:
17795       case AnalyzeFile:
17796         /* Skip if we know it isn't thinking */
17797         if (!cps->maybeThinking) return;
17798         if (appData.debugMode)
17799           fprintf(debugFP, "Interrupting %s\n", cps->which);
17800         InterruptChildProcess(cps->pr);
17801         cps->maybeThinking = FALSE;
17802         break;
17803       default:
17804         break;
17805     }
17806 #endif /*ATTENTION*/
17807 }
17808
17809 int
17810 CheckFlags ()
17811 {
17812     if (whiteTimeRemaining <= 0) {
17813         if (!whiteFlag) {
17814             whiteFlag = TRUE;
17815             if (appData.icsActive) {
17816                 if (appData.autoCallFlag &&
17817                     gameMode == IcsPlayingBlack && !blackFlag) {
17818                   SendToICS(ics_prefix);
17819                   SendToICS("flag\n");
17820                 }
17821             } else {
17822                 if (blackFlag) {
17823                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17824                 } else {
17825                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17826                     if (appData.autoCallFlag) {
17827                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17828                         return TRUE;
17829                     }
17830                 }
17831             }
17832         }
17833     }
17834     if (blackTimeRemaining <= 0) {
17835         if (!blackFlag) {
17836             blackFlag = TRUE;
17837             if (appData.icsActive) {
17838                 if (appData.autoCallFlag &&
17839                     gameMode == IcsPlayingWhite && !whiteFlag) {
17840                   SendToICS(ics_prefix);
17841                   SendToICS("flag\n");
17842                 }
17843             } else {
17844                 if (whiteFlag) {
17845                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17846                 } else {
17847                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17848                     if (appData.autoCallFlag) {
17849                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17850                         return TRUE;
17851                     }
17852                 }
17853             }
17854         }
17855     }
17856     return FALSE;
17857 }
17858
17859 void
17860 CheckTimeControl ()
17861 {
17862     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17863         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17864
17865     /*
17866      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17867      */
17868     if ( !WhiteOnMove(forwardMostMove) ) {
17869         /* White made time control */
17870         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17871         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17872         /* [HGM] time odds: correct new time quota for time odds! */
17873                                             / WhitePlayer()->timeOdds;
17874         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17875     } else {
17876         lastBlack -= blackTimeRemaining;
17877         /* Black made time control */
17878         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17879                                             / WhitePlayer()->other->timeOdds;
17880         lastWhite = whiteTimeRemaining;
17881     }
17882 }
17883
17884 void
17885 DisplayBothClocks ()
17886 {
17887     int wom = gameMode == EditPosition ?
17888       !blackPlaysFirst : WhiteOnMove(currentMove);
17889     DisplayWhiteClock(whiteTimeRemaining, wom);
17890     DisplayBlackClock(blackTimeRemaining, !wom);
17891 }
17892
17893
17894 /* Timekeeping seems to be a portability nightmare.  I think everyone
17895    has ftime(), but I'm really not sure, so I'm including some ifdefs
17896    to use other calls if you don't.  Clocks will be less accurate if
17897    you have neither ftime nor gettimeofday.
17898 */
17899
17900 /* VS 2008 requires the #include outside of the function */
17901 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17902 #include <sys/timeb.h>
17903 #endif
17904
17905 /* Get the current time as a TimeMark */
17906 void
17907 GetTimeMark (TimeMark *tm)
17908 {
17909 #if HAVE_GETTIMEOFDAY
17910
17911     struct timeval timeVal;
17912     struct timezone timeZone;
17913
17914     gettimeofday(&timeVal, &timeZone);
17915     tm->sec = (long) timeVal.tv_sec;
17916     tm->ms = (int) (timeVal.tv_usec / 1000L);
17917
17918 #else /*!HAVE_GETTIMEOFDAY*/
17919 #if HAVE_FTIME
17920
17921 // include <sys/timeb.h> / moved to just above start of function
17922     struct timeb timeB;
17923
17924     ftime(&timeB);
17925     tm->sec = (long) timeB.time;
17926     tm->ms = (int) timeB.millitm;
17927
17928 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17929     tm->sec = (long) time(NULL);
17930     tm->ms = 0;
17931 #endif
17932 #endif
17933 }
17934
17935 /* Return the difference in milliseconds between two
17936    time marks.  We assume the difference will fit in a long!
17937 */
17938 long
17939 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17940 {
17941     return 1000L*(tm2->sec - tm1->sec) +
17942            (long) (tm2->ms - tm1->ms);
17943 }
17944
17945
17946 /*
17947  * Code to manage the game clocks.
17948  *
17949  * In tournament play, black starts the clock and then white makes a move.
17950  * We give the human user a slight advantage if he is playing white---the
17951  * clocks don't run until he makes his first move, so it takes zero time.
17952  * Also, we don't account for network lag, so we could get out of sync
17953  * with GNU Chess's clock -- but then, referees are always right.
17954  */
17955
17956 static TimeMark tickStartTM;
17957 static long intendedTickLength;
17958
17959 long
17960 NextTickLength (long timeRemaining)
17961 {
17962     long nominalTickLength, nextTickLength;
17963
17964     if (timeRemaining > 0L && timeRemaining <= 10000L)
17965       nominalTickLength = 100L;
17966     else
17967       nominalTickLength = 1000L;
17968     nextTickLength = timeRemaining % nominalTickLength;
17969     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17970
17971     return nextTickLength;
17972 }
17973
17974 /* Adjust clock one minute up or down */
17975 void
17976 AdjustClock (Boolean which, int dir)
17977 {
17978     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17979     if(which) blackTimeRemaining += 60000*dir;
17980     else      whiteTimeRemaining += 60000*dir;
17981     DisplayBothClocks();
17982     adjustedClock = TRUE;
17983 }
17984
17985 /* Stop clocks and reset to a fresh time control */
17986 void
17987 ResetClocks ()
17988 {
17989     (void) StopClockTimer();
17990     if (appData.icsActive) {
17991         whiteTimeRemaining = blackTimeRemaining = 0;
17992     } else if (searchTime) {
17993         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17994         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17995     } else { /* [HGM] correct new time quote for time odds */
17996         whiteTC = blackTC = fullTimeControlString;
17997         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17998         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17999     }
18000     if (whiteFlag || blackFlag) {
18001         DisplayTitle("");
18002         whiteFlag = blackFlag = FALSE;
18003     }
18004     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
18005     DisplayBothClocks();
18006     adjustedClock = FALSE;
18007 }
18008
18009 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
18010
18011 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
18012
18013 /* Decrement running clock by amount of time that has passed */
18014 void
18015 DecrementClocks ()
18016 {
18017     long tRemaining;
18018     long lastTickLength, fudge;
18019     TimeMark now;
18020
18021     if (!appData.clockMode) return;
18022     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
18023
18024     GetTimeMark(&now);
18025
18026     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18027
18028     /* Fudge if we woke up a little too soon */
18029     fudge = intendedTickLength - lastTickLength;
18030     if (fudge < 0 || fudge > FUDGE) fudge = 0;
18031
18032     if (WhiteOnMove(forwardMostMove)) {
18033         if(whiteNPS >= 0) lastTickLength = 0;
18034          tRemaining = whiteTimeRemaining -= lastTickLength;
18035         if( tRemaining < 0 && !appData.icsActive) {
18036             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
18037             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
18038                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
18039                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
18040             }
18041         }
18042         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
18043         DisplayWhiteClock(whiteTimeRemaining - fudge,
18044                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18045         timeSuffix = 0;
18046     } else {
18047         if(blackNPS >= 0) lastTickLength = 0;
18048          tRemaining = blackTimeRemaining -= lastTickLength;
18049         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
18050             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
18051             if(suddenDeath) {
18052                 blackStartMove = forwardMostMove;
18053                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
18054             }
18055         }
18056         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
18057         DisplayBlackClock(blackTimeRemaining - fudge,
18058                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18059         timeSuffix = 0;
18060     }
18061     if (CheckFlags()) return;
18062
18063     if(twoBoards) { // count down secondary board's clocks as well
18064         activePartnerTime -= lastTickLength;
18065         partnerUp = 1;
18066         if(activePartner == 'W')
18067             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
18068         else
18069             DisplayBlackClock(activePartnerTime, TRUE);
18070         partnerUp = 0;
18071     }
18072
18073     tickStartTM = now;
18074     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
18075     StartClockTimer(intendedTickLength);
18076
18077     /* if the time remaining has fallen below the alarm threshold, sound the
18078      * alarm. if the alarm has sounded and (due to a takeback or time control
18079      * with increment) the time remaining has increased to a level above the
18080      * threshold, reset the alarm so it can sound again.
18081      */
18082
18083     if (appData.icsActive && appData.icsAlarm) {
18084
18085         /* make sure we are dealing with the user's clock */
18086         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
18087                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
18088            )) return;
18089
18090         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
18091             alarmSounded = FALSE;
18092         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
18093             PlayAlarmSound();
18094             alarmSounded = TRUE;
18095         }
18096     }
18097 }
18098
18099
18100 /* A player has just moved, so stop the previously running
18101    clock and (if in clock mode) start the other one.
18102    We redisplay both clocks in case we're in ICS mode, because
18103    ICS gives us an update to both clocks after every move.
18104    Note that this routine is called *after* forwardMostMove
18105    is updated, so the last fractional tick must be subtracted
18106    from the color that is *not* on move now.
18107 */
18108 void
18109 SwitchClocks (int newMoveNr)
18110 {
18111     long lastTickLength;
18112     TimeMark now;
18113     int flagged = FALSE;
18114
18115     GetTimeMark(&now);
18116
18117     if (StopClockTimer() && appData.clockMode) {
18118         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18119         if (!WhiteOnMove(forwardMostMove)) {
18120             if(blackNPS >= 0) lastTickLength = 0;
18121             blackTimeRemaining -= lastTickLength;
18122            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18123 //         if(pvInfoList[forwardMostMove].time == -1)
18124                  pvInfoList[forwardMostMove].time =               // use GUI time
18125                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18126         } else {
18127            if(whiteNPS >= 0) lastTickLength = 0;
18128            whiteTimeRemaining -= lastTickLength;
18129            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18130 //         if(pvInfoList[forwardMostMove].time == -1)
18131                  pvInfoList[forwardMostMove].time =
18132                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18133         }
18134         flagged = CheckFlags();
18135     }
18136     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18137     CheckTimeControl();
18138
18139     if (flagged || !appData.clockMode) return;
18140
18141     switch (gameMode) {
18142       case MachinePlaysBlack:
18143       case MachinePlaysWhite:
18144       case BeginningOfGame:
18145         if (pausing) return;
18146         break;
18147
18148       case EditGame:
18149       case PlayFromGameFile:
18150       case IcsExamining:
18151         return;
18152
18153       default:
18154         break;
18155     }
18156
18157     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18158         if(WhiteOnMove(forwardMostMove))
18159              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18160         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18161     }
18162
18163     tickStartTM = now;
18164     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18165       whiteTimeRemaining : blackTimeRemaining);
18166     StartClockTimer(intendedTickLength);
18167 }
18168
18169
18170 /* Stop both clocks */
18171 void
18172 StopClocks ()
18173 {
18174     long lastTickLength;
18175     TimeMark now;
18176
18177     if (!StopClockTimer()) return;
18178     if (!appData.clockMode) return;
18179
18180     GetTimeMark(&now);
18181
18182     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18183     if (WhiteOnMove(forwardMostMove)) {
18184         if(whiteNPS >= 0) lastTickLength = 0;
18185         whiteTimeRemaining -= lastTickLength;
18186         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18187     } else {
18188         if(blackNPS >= 0) lastTickLength = 0;
18189         blackTimeRemaining -= lastTickLength;
18190         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18191     }
18192     CheckFlags();
18193 }
18194
18195 /* Start clock of player on move.  Time may have been reset, so
18196    if clock is already running, stop and restart it. */
18197 void
18198 StartClocks ()
18199 {
18200     (void) StopClockTimer(); /* in case it was running already */
18201     DisplayBothClocks();
18202     if (CheckFlags()) return;
18203
18204     if (!appData.clockMode) return;
18205     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18206
18207     GetTimeMark(&tickStartTM);
18208     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18209       whiteTimeRemaining : blackTimeRemaining);
18210
18211    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18212     whiteNPS = blackNPS = -1;
18213     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18214        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18215         whiteNPS = first.nps;
18216     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18217        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18218         blackNPS = first.nps;
18219     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18220         whiteNPS = second.nps;
18221     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18222         blackNPS = second.nps;
18223     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18224
18225     StartClockTimer(intendedTickLength);
18226 }
18227
18228 char *
18229 TimeString (long ms)
18230 {
18231     long second, minute, hour, day;
18232     char *sign = "";
18233     static char buf[40], moveTime[8];
18234
18235     if (ms > 0 && ms <= 9900) {
18236       /* convert milliseconds to tenths, rounding up */
18237       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18238
18239       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18240       return buf;
18241     }
18242
18243     /* convert milliseconds to seconds, rounding up */
18244     /* use floating point to avoid strangeness of integer division
18245        with negative dividends on many machines */
18246     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18247
18248     if (second < 0) {
18249         sign = "-";
18250         second = -second;
18251     }
18252
18253     day = second / (60 * 60 * 24);
18254     second = second % (60 * 60 * 24);
18255     hour = second / (60 * 60);
18256     second = second % (60 * 60);
18257     minute = second / 60;
18258     second = second % 60;
18259
18260     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18261     else *moveTime = NULLCHAR;
18262
18263     if (day > 0)
18264       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18265               sign, day, hour, minute, second, moveTime);
18266     else if (hour > 0)
18267       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18268     else
18269       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18270
18271     return buf;
18272 }
18273
18274
18275 /*
18276  * This is necessary because some C libraries aren't ANSI C compliant yet.
18277  */
18278 char *
18279 StrStr (char *string, char *match)
18280 {
18281     int i, length;
18282
18283     length = strlen(match);
18284
18285     for (i = strlen(string) - length; i >= 0; i--, string++)
18286       if (!strncmp(match, string, length))
18287         return string;
18288
18289     return NULL;
18290 }
18291
18292 char *
18293 StrCaseStr (char *string, char *match)
18294 {
18295     int i, j, length;
18296
18297     length = strlen(match);
18298
18299     for (i = strlen(string) - length; i >= 0; i--, string++) {
18300         for (j = 0; j < length; j++) {
18301             if (ToLower(match[j]) != ToLower(string[j]))
18302               break;
18303         }
18304         if (j == length) return string;
18305     }
18306
18307     return NULL;
18308 }
18309
18310 #ifndef _amigados
18311 int
18312 StrCaseCmp (char *s1, char *s2)
18313 {
18314     char c1, c2;
18315
18316     for (;;) {
18317         c1 = ToLower(*s1++);
18318         c2 = ToLower(*s2++);
18319         if (c1 > c2) return 1;
18320         if (c1 < c2) return -1;
18321         if (c1 == NULLCHAR) return 0;
18322     }
18323 }
18324
18325
18326 int
18327 ToLower (int c)
18328 {
18329     return isupper(c) ? tolower(c) : c;
18330 }
18331
18332
18333 int
18334 ToUpper (int c)
18335 {
18336     return islower(c) ? toupper(c) : c;
18337 }
18338 #endif /* !_amigados    */
18339
18340 char *
18341 StrSave (char *s)
18342 {
18343   char *ret;
18344
18345   if ((ret = (char *) malloc(strlen(s) + 1)))
18346     {
18347       safeStrCpy(ret, s, strlen(s)+1);
18348     }
18349   return ret;
18350 }
18351
18352 char *
18353 StrSavePtr (char *s, char **savePtr)
18354 {
18355     if (*savePtr) {
18356         free(*savePtr);
18357     }
18358     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18359       safeStrCpy(*savePtr, s, strlen(s)+1);
18360     }
18361     return(*savePtr);
18362 }
18363
18364 char *
18365 PGNDate ()
18366 {
18367     time_t clock;
18368     struct tm *tm;
18369     char buf[MSG_SIZ];
18370
18371     clock = time((time_t *)NULL);
18372     tm = localtime(&clock);
18373     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18374             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18375     return StrSave(buf);
18376 }
18377
18378
18379 char *
18380 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18381 {
18382     int i, j, fromX, fromY, toX, toY;
18383     int whiteToPlay, haveRights = nrCastlingRights;
18384     char buf[MSG_SIZ];
18385     char *p, *q;
18386     int emptycount;
18387     ChessSquare piece;
18388
18389     whiteToPlay = (gameMode == EditPosition) ?
18390       !blackPlaysFirst : (move % 2 == 0);
18391     p = buf;
18392
18393     /* Piece placement data */
18394     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18395         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18396         emptycount = 0;
18397         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18398             if (boards[move][i][j] == EmptySquare) {
18399                 emptycount++;
18400             } else { ChessSquare piece = boards[move][i][j];
18401                 if (emptycount > 0) {
18402                     if(emptycount<10) /* [HGM] can be >= 10 */
18403                         *p++ = '0' + emptycount;
18404                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18405                     emptycount = 0;
18406                 }
18407                 if(PieceToChar(piece) == '+') {
18408                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18409                     *p++ = '+';
18410                     piece = (ChessSquare)(CHUDEMOTED(piece));
18411                 }
18412                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18413                 if(*p = PieceSuffix(piece)) p++;
18414                 if(p[-1] == '~') {
18415                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18416                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18417                     *p++ = '~';
18418                 }
18419             }
18420         }
18421         if (emptycount > 0) {
18422             if(emptycount<10) /* [HGM] can be >= 10 */
18423                 *p++ = '0' + emptycount;
18424             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18425             emptycount = 0;
18426         }
18427         *p++ = '/';
18428     }
18429     *(p - 1) = ' ';
18430
18431     /* [HGM] print Crazyhouse or Shogi holdings */
18432     if( gameInfo.holdingsWidth ) {
18433         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18434         q = p;
18435         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18436             piece = boards[move][i][BOARD_WIDTH-1];
18437             if( piece != EmptySquare )
18438               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18439                   *p++ = PieceToChar(piece);
18440         }
18441         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18442             piece = boards[move][handSize-i-1][0];
18443             if( piece != EmptySquare )
18444               for(j=0; j<(int) boards[move][handSize-i-1][1]; j++)
18445                   *p++ = PieceToChar(piece);
18446         }
18447
18448         if( q == p ) *p++ = '-';
18449         *p++ = ']';
18450         *p++ = ' ';
18451     }
18452
18453     /* Active color */
18454     *p++ = whiteToPlay ? 'w' : 'b';
18455     *p++ = ' ';
18456
18457   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18458     haveRights = 0; q = p;
18459     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18460       piece = boards[move][0][i];
18461       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18462         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18463       }
18464     }
18465     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18466       piece = boards[move][BOARD_HEIGHT-1][i];
18467       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18468         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18469       }
18470     }
18471     if(p == q) *p++ = '-';
18472     *p++ = ' ';
18473   }
18474
18475   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18476     while(*p++ = *q++)
18477                       ;
18478     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18479   } else {
18480   if(haveRights) {
18481      int handW=0, handB=0;
18482      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18483         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18484         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18485      }
18486      q = p;
18487      if(appData.fischerCastling) {
18488         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18489            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18490                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18491         } else {
18492        /* [HGM] write directly from rights */
18493            if(boards[move][CASTLING][2] != NoRights &&
18494               boards[move][CASTLING][0] != NoRights   )
18495                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18496            if(boards[move][CASTLING][2] != NoRights &&
18497               boards[move][CASTLING][1] != NoRights   )
18498                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18499         }
18500         if(handB) {
18501            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18502                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18503         } else {
18504            if(boards[move][CASTLING][5] != NoRights &&
18505               boards[move][CASTLING][3] != NoRights   )
18506                 *p++ = boards[move][CASTLING][3] + AAA;
18507            if(boards[move][CASTLING][5] != NoRights &&
18508               boards[move][CASTLING][4] != NoRights   )
18509                 *p++ = boards[move][CASTLING][4] + AAA;
18510         }
18511      } else {
18512
18513         /* [HGM] write true castling rights */
18514         if( nrCastlingRights == 6 ) {
18515             int q, k=0;
18516             if(boards[move][CASTLING][0] != NoRights &&
18517                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18518             q = (boards[move][CASTLING][1] != NoRights &&
18519                  boards[move][CASTLING][2] != NoRights  );
18520             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18521                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18522                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18523                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18524             }
18525             if(q) *p++ = 'Q';
18526             k = 0;
18527             if(boards[move][CASTLING][3] != NoRights &&
18528                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18529             q = (boards[move][CASTLING][4] != NoRights &&
18530                  boards[move][CASTLING][5] != NoRights  );
18531             if(handB) {
18532                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18533                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18534                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18535             }
18536             if(q) *p++ = 'q';
18537         }
18538      }
18539      if (q == p) *p++ = '-'; /* No castling rights */
18540      *p++ = ' ';
18541   }
18542
18543   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18544      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18545      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18546     /* En passant target square */
18547     if (move > backwardMostMove) {
18548         fromX = moveList[move - 1][0] - AAA;
18549         fromY = moveList[move - 1][1] - ONE;
18550         toX = moveList[move - 1][2] - AAA;
18551         toY = moveList[move - 1][3] - ONE;
18552         if ((whiteToPlay ? toY < fromY - 1 : toY > fromY + 1) &&
18553             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) ) {
18554             /* 2-square pawn move just happened */
18555             *p++ = (3*toX + 5*fromX + 4)/8 + AAA;
18556             *p++ = (3*toY + 5*fromY + 4)/8 + ONE;
18557             if(gameInfo.variant == VariantBerolina) {
18558                 *p++ = toX + AAA;
18559                 *p++ = toY + ONE;
18560             }
18561         } else {
18562             *p++ = '-';
18563         }
18564     } else if(move == backwardMostMove) {
18565         // [HGM] perhaps we should always do it like this, and forget the above?
18566         if((signed char)boards[move][EP_STATUS] >= 0) {
18567             *p++ = boards[move][EP_STATUS] + AAA;
18568             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18569         } else {
18570             *p++ = '-';
18571         }
18572     } else {
18573         *p++ = '-';
18574     }
18575     *p++ = ' ';
18576   }
18577   }
18578
18579     i = boards[move][CHECK_COUNT];
18580     if(i) {
18581         sprintf(p, "%d+%d ", i&255, i>>8);
18582         while(*p) p++;
18583     }
18584
18585     if(moveCounts)
18586     {   int i = 0, j=move;
18587
18588         /* [HGM] find reversible plies */
18589         if (appData.debugMode) { int k;
18590             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18591             for(k=backwardMostMove; k<=forwardMostMove; k++)
18592                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18593
18594         }
18595
18596         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18597         if( j == backwardMostMove ) i += initialRulePlies;
18598         sprintf(p, "%d ", i);
18599         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18600
18601         /* Fullmove number */
18602         sprintf(p, "%d", (move / 2) + 1);
18603     } else *--p = NULLCHAR;
18604
18605     return StrSave(buf);
18606 }
18607
18608 Boolean
18609 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18610 {
18611     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18612     char *p, c;
18613     int emptycount, virgin[BOARD_FILES];
18614     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18615
18616     p = fen;
18617
18618     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18619
18620     /* Piece placement data */
18621     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18622         j = 0;
18623         for (;;) {
18624             if (*p == '/' || *p == ' ' || *p == '[' ) {
18625                 if(j > w) w = j;
18626                 emptycount = gameInfo.boardWidth - j;
18627                 while (emptycount--)
18628                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18629                 if (*p == '/') p++;
18630                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18631                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18632                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18633                     }
18634                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18635                 }
18636                 break;
18637 #if(BOARD_FILES >= 10)*0
18638             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18639                 p++; emptycount=10;
18640                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18641                 while (emptycount--)
18642                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18643 #endif
18644             } else if (*p == '*') {
18645                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18646             } else if (isdigit(*p)) {
18647                 emptycount = *p++ - '0';
18648                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18649                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18650                 while (emptycount--)
18651                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18652             } else if (*p == '<') {
18653                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18654                 else if (i != 0 || !shuffle) return FALSE;
18655                 p++;
18656             } else if (shuffle && *p == '>') {
18657                 p++; // for now ignore closing shuffle range, and assume rank-end
18658             } else if (*p == '?') {
18659                 if (j >= gameInfo.boardWidth) return FALSE;
18660                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18661                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18662             } else if (*p == '+' || isalpha(*p)) {
18663                 char *q, *s = SUFFIXES;
18664                 if (j >= gameInfo.boardWidth) return FALSE;
18665                 if(*p=='+') {
18666                     char c = *++p;
18667                     if(q = strchr(s, p[1])) p++;
18668                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18669                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18670                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18671                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18672                 } else {
18673                     char c = *p++;
18674                     if(q = strchr(s, *p)) p++;
18675                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18676                 }
18677
18678                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18679                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18680                     piece = (ChessSquare) (PROMOTED(piece));
18681                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18682                     p++;
18683                 }
18684                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18685                 if(piece == king) wKingRank = i;
18686                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18687             } else {
18688                 return FALSE;
18689             }
18690         }
18691     }
18692     while (*p == '/' || *p == ' ') p++;
18693
18694     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18695
18696     /* [HGM] by default clear Crazyhouse holdings, if present */
18697     if(gameInfo.holdingsWidth) {
18698        for(i=0; i<handSize; i++) {
18699            board[i][0]             = EmptySquare; /* black holdings */
18700            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18701            board[i][1]             = (ChessSquare) 0; /* black counts */
18702            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18703        }
18704     }
18705
18706     /* [HGM] look for Crazyhouse holdings here */
18707     while(*p==' ') p++;
18708     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18709         int swap=0, wcnt=0, bcnt=0;
18710         if(*p == '[') p++;
18711         if(*p == '<') swap++, p++;
18712         if(*p == '-' ) p++; /* empty holdings */ else {
18713             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18714             /* if we would allow FEN reading to set board size, we would   */
18715             /* have to add holdings and shift the board read so far here   */
18716             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18717                 p++;
18718                 if((int) piece >= (int) BlackPawn ) {
18719                     i = (int)piece - (int)BlackPawn;
18720                     i = PieceToNumber((ChessSquare)i);
18721                     if( i >= gameInfo.holdingsSize ) return FALSE;
18722                     board[handSize-1-i][0] = piece; /* black holdings */
18723                     board[handSize-1-i][1]++;       /* black counts   */
18724                     bcnt++;
18725                 } else {
18726                     i = (int)piece - (int)WhitePawn;
18727                     i = PieceToNumber((ChessSquare)i);
18728                     if( i >= gameInfo.holdingsSize ) return FALSE;
18729                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18730                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18731                     wcnt++;
18732                 }
18733             }
18734             if(subst) { // substitute back-rank question marks by holdings pieces
18735                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18736                     int k, m, n = bcnt + 1;
18737                     if(board[0][j] == ClearBoard) {
18738                         if(!wcnt) return FALSE;
18739                         n = rand() % wcnt;
18740                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18741                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18742                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18743                             break;
18744                         }
18745                     }
18746                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18747                         if(!bcnt) return FALSE;
18748                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18749                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[handSize-1-k][1]) < 0) {
18750                             board[BOARD_HEIGHT-1][j] = board[handSize-1-k][0]; bcnt--;
18751                             if(--board[handSize-1-k][1] == 0) board[handSize-1-k][0] = EmptySquare;
18752                             break;
18753                         }
18754                     }
18755                 }
18756                 subst = 0;
18757             }
18758         }
18759         if(*p == ']') p++;
18760     }
18761
18762     if(subst) return FALSE; // substitution requested, but no holdings
18763
18764     while(*p == ' ') p++;
18765
18766     /* Active color */
18767     c = *p++;
18768     if(appData.colorNickNames) {
18769       if( c == appData.colorNickNames[0] ) c = 'w'; else
18770       if( c == appData.colorNickNames[1] ) c = 'b';
18771     }
18772     switch (c) {
18773       case 'w':
18774         *blackPlaysFirst = FALSE;
18775         break;
18776       case 'b':
18777         *blackPlaysFirst = TRUE;
18778         break;
18779       default:
18780         return FALSE;
18781     }
18782
18783     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18784     /* return the extra info in global variiables             */
18785
18786     while(*p==' ') p++;
18787
18788     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18789         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18790         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18791     }
18792
18793     /* set defaults in case FEN is incomplete */
18794     board[EP_STATUS] = EP_UNKNOWN;
18795     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18796     for(i=0; i<nrCastlingRights; i++ ) {
18797         board[CASTLING][i] =
18798             appData.fischerCastling ? NoRights : initialRights[i];
18799     }   /* assume possible unless obviously impossible */
18800     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18801     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18802     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18803                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18804     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18805     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18806     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18807                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18808     FENrulePlies = 0;
18809
18810     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18811       char *q = p;
18812       int w=0, b=0;
18813       while(isalpha(*p)) {
18814         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18815         if(islower(*p)) b |= 1 << (*p++ - 'a');
18816       }
18817       if(*p == '-') p++;
18818       if(p != q) {
18819         board[TOUCHED_W] = ~w;
18820         board[TOUCHED_B] = ~b;
18821         while(*p == ' ') p++;
18822       }
18823     } else
18824
18825     if(nrCastlingRights) {
18826       int fischer = 0;
18827       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18828       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18829           /* castling indicator present, so default becomes no castlings */
18830           for(i=0; i<nrCastlingRights; i++ ) {
18831                  board[CASTLING][i] = NoRights;
18832           }
18833       }
18834       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18835              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18836              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18837              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18838         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18839
18840         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18841             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18842             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18843         }
18844         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18845             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18846         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18847                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18848         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18849                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18850         switch(c) {
18851           case'K':
18852               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18853               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18854               board[CASTLING][2] = whiteKingFile;
18855               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18856               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18857               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18858               break;
18859           case'Q':
18860               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18861               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18862               board[CASTLING][2] = whiteKingFile;
18863               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18864               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18865               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18866               break;
18867           case'k':
18868               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18869               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18870               board[CASTLING][5] = blackKingFile;
18871               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18872               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18873               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18874               break;
18875           case'q':
18876               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18877               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18878               board[CASTLING][5] = blackKingFile;
18879               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18880               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18881               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18882           case '-':
18883               break;
18884           default: /* FRC castlings */
18885               if(c >= 'a') { /* black rights */
18886                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18887                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18888                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18889                   if(i == BOARD_RGHT) break;
18890                   board[CASTLING][5] = i;
18891                   c -= AAA;
18892                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18893                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18894                   if(c > i)
18895                       board[CASTLING][3] = c;
18896                   else
18897                       board[CASTLING][4] = c;
18898               } else { /* white rights */
18899                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18900                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18901                     if(board[0][i] == WhiteKing) break;
18902                   if(i == BOARD_RGHT) break;
18903                   board[CASTLING][2] = i;
18904                   c -= AAA - 'a' + 'A';
18905                   if(board[0][c] >= WhiteKing) break;
18906                   if(c > i)
18907                       board[CASTLING][0] = c;
18908                   else
18909                       board[CASTLING][1] = c;
18910               }
18911         }
18912       }
18913       for(i=0; i<nrCastlingRights; i++)
18914         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18915       if(gameInfo.variant == VariantSChess)
18916         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18917       if(fischer && shuffle) appData.fischerCastling = TRUE;
18918     if (appData.debugMode) {
18919         fprintf(debugFP, "FEN castling rights:");
18920         for(i=0; i<nrCastlingRights; i++)
18921         fprintf(debugFP, " %d", board[CASTLING][i]);
18922         fprintf(debugFP, "\n");
18923     }
18924
18925       while(*p==' ') p++;
18926     }
18927
18928     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18929
18930     /* read e.p. field in games that know e.p. capture */
18931     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18932        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18933        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18934       if(*p=='-') {
18935         p++; board[EP_STATUS] = EP_NONE;
18936       } else {
18937          int d, r, c = *p - AAA;
18938
18939          if(c >= BOARD_LEFT && c < BOARD_RGHT) {
18940              p++;
18941              board[EP_STATUS] = board[EP_FILE] = c; r = 0;
18942              if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18943              d = (r < BOARD_HEIGHT << 1 ? 1 : -1); // assume double-push (P next to e.p. square nearer center)
18944              if(board[r+d][c] == EmptySquare) d *= 2; // but if no Pawn there, triple push
18945              board[LAST_TO] = 256*(r + d) + c;
18946              c = *p++ - AAA;
18947              if(c >= BOARD_LEFT && c < BOARD_RGHT) { // mover explicitly mentioned
18948                  if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18949                  board[LAST_TO] = 256*r + c;
18950                  if(!(board[EP_RANK]-r & 1)) board[EP_RANK] |= 128;
18951              }
18952          }
18953       }
18954     }
18955
18956     while(*p == ' ') p++;
18957
18958     board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18959     if(sscanf(p, "%d+%d", &i, &j) == 2) {
18960         board[CHECK_COUNT] = i + 256*j;
18961         while(*p && *p != ' ') p++;
18962     }
18963
18964     c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18965     if(c > 0) {
18966         FENrulePlies = i; /* 50-move ply counter */
18967         /* (The move number is still ignored)    */
18968         if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18969     }
18970
18971     return TRUE;
18972 }
18973
18974 void
18975 EditPositionPasteFEN (char *fen)
18976 {
18977   if (fen != NULL) {
18978     Board initial_position;
18979
18980     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18981       DisplayError(_("Bad FEN position in clipboard"), 0);
18982       return ;
18983     } else {
18984       int savedBlackPlaysFirst = blackPlaysFirst;
18985       EditPositionEvent();
18986       blackPlaysFirst = savedBlackPlaysFirst;
18987       CopyBoard(boards[0], initial_position);
18988       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18989       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18990       DisplayBothClocks();
18991       DrawPosition(FALSE, boards[currentMove]);
18992     }
18993   }
18994 }
18995
18996 static char cseq[12] = "\\   ";
18997
18998 Boolean
18999 set_cont_sequence (char *new_seq)
19000 {
19001     int len;
19002     Boolean ret;
19003
19004     // handle bad attempts to set the sequence
19005         if (!new_seq)
19006                 return 0; // acceptable error - no debug
19007
19008     len = strlen(new_seq);
19009     ret = (len > 0) && (len < sizeof(cseq));
19010     if (ret)
19011       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
19012     else if (appData.debugMode)
19013       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
19014     return ret;
19015 }
19016
19017 /*
19018     reformat a source message so words don't cross the width boundary.  internal
19019     newlines are not removed.  returns the wrapped size (no null character unless
19020     included in source message).  If dest is NULL, only calculate the size required
19021     for the dest buffer.  lp argument indicats line position upon entry, and it's
19022     passed back upon exit.
19023 */
19024 int
19025 wrap (char *dest, char *src, int count, int width, int *lp)
19026 {
19027     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
19028
19029     cseq_len = strlen(cseq);
19030     old_line = line = *lp;
19031     ansi = len = clen = 0;
19032
19033     for (i=0; i < count; i++)
19034     {
19035         if (src[i] == '\033')
19036             ansi = 1;
19037
19038         // if we hit the width, back up
19039         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
19040         {
19041             // store i & len in case the word is too long
19042             old_i = i, old_len = len;
19043
19044             // find the end of the last word
19045             while (i && src[i] != ' ' && src[i] != '\n')
19046             {
19047                 i--;
19048                 len--;
19049             }
19050
19051             // word too long?  restore i & len before splitting it
19052             if ((old_i-i+clen) >= width)
19053             {
19054                 i = old_i;
19055                 len = old_len;
19056             }
19057
19058             // extra space?
19059             if (i && src[i-1] == ' ')
19060                 len--;
19061
19062             if (src[i] != ' ' && src[i] != '\n')
19063             {
19064                 i--;
19065                 if (len)
19066                     len--;
19067             }
19068
19069             // now append the newline and continuation sequence
19070             if (dest)
19071                 dest[len] = '\n';
19072             len++;
19073             if (dest)
19074                 strncpy(dest+len, cseq, cseq_len);
19075             len += cseq_len;
19076             line = cseq_len;
19077             clen = cseq_len;
19078             continue;
19079         }
19080
19081         if (dest)
19082             dest[len] = src[i];
19083         len++;
19084         if (!ansi)
19085             line++;
19086         if (src[i] == '\n')
19087             line = 0;
19088         if (src[i] == 'm')
19089             ansi = 0;
19090     }
19091     if (dest && appData.debugMode)
19092     {
19093         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
19094             count, width, line, len, *lp);
19095         show_bytes(debugFP, src, count);
19096         fprintf(debugFP, "\ndest: ");
19097         show_bytes(debugFP, dest, len);
19098         fprintf(debugFP, "\n");
19099     }
19100     *lp = dest ? line : old_line;
19101
19102     return len;
19103 }
19104
19105 // [HGM] vari: routines for shelving variations
19106 Boolean modeRestore = FALSE;
19107
19108 void
19109 PushInner (int firstMove, int lastMove)
19110 {
19111         int i, j, nrMoves = lastMove - firstMove;
19112
19113         // push current tail of game on stack
19114         savedResult[storedGames] = gameInfo.result;
19115         savedDetails[storedGames] = gameInfo.resultDetails;
19116         gameInfo.resultDetails = NULL;
19117         savedFirst[storedGames] = firstMove;
19118         savedLast [storedGames] = lastMove;
19119         savedFramePtr[storedGames] = framePtr;
19120         framePtr -= nrMoves; // reserve space for the boards
19121         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19122             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19123             for(j=0; j<MOVE_LEN; j++)
19124                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19125             for(j=0; j<2*MOVE_LEN; j++)
19126                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19127             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19128             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19129             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19130             pvInfoList[firstMove+i-1].depth = 0;
19131             commentList[framePtr+i] = commentList[firstMove+i];
19132             commentList[firstMove+i] = NULL;
19133         }
19134
19135         storedGames++;
19136         forwardMostMove = firstMove; // truncate game so we can start variation
19137 }
19138
19139 void
19140 PushTail (int firstMove, int lastMove)
19141 {
19142         if(appData.icsActive) { // only in local mode
19143                 forwardMostMove = currentMove; // mimic old ICS behavior
19144                 return;
19145         }
19146         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19147
19148         PushInner(firstMove, lastMove);
19149         if(storedGames == 1) GreyRevert(FALSE);
19150         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19151 }
19152
19153 void
19154 PopInner (Boolean annotate)
19155 {
19156         int i, j, nrMoves;
19157         char buf[8000], moveBuf[20];
19158
19159         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19160         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19161         nrMoves = savedLast[storedGames] - currentMove;
19162         if(annotate) {
19163                 int cnt = 10;
19164                 if(!WhiteOnMove(currentMove))
19165                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19166                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19167                 for(i=currentMove; i<forwardMostMove; i++) {
19168                         if(WhiteOnMove(i))
19169                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19170                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19171                         strcat(buf, moveBuf);
19172                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19173                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19174                 }
19175                 strcat(buf, ")");
19176         }
19177         for(i=1; i<=nrMoves; i++) { // copy last variation back
19178             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19179             for(j=0; j<MOVE_LEN; j++)
19180                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19181             for(j=0; j<2*MOVE_LEN; j++)
19182                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19183             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19184             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19185             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19186             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19187             commentList[currentMove+i] = commentList[framePtr+i];
19188             commentList[framePtr+i] = NULL;
19189         }
19190         if(annotate) AppendComment(currentMove+1, buf, FALSE);
19191         framePtr = savedFramePtr[storedGames];
19192         gameInfo.result = savedResult[storedGames];
19193         if(gameInfo.resultDetails != NULL) {
19194             free(gameInfo.resultDetails);
19195       }
19196         gameInfo.resultDetails = savedDetails[storedGames];
19197         forwardMostMove = currentMove + nrMoves;
19198 }
19199
19200 Boolean
19201 PopTail (Boolean annotate)
19202 {
19203         if(appData.icsActive) return FALSE; // only in local mode
19204         if(!storedGames) return FALSE; // sanity
19205         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19206
19207         PopInner(annotate);
19208         if(currentMove < forwardMostMove) ForwardEvent(); else
19209         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19210
19211         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19212         return TRUE;
19213 }
19214
19215 void
19216 CleanupTail ()
19217 {       // remove all shelved variations
19218         int i;
19219         for(i=0; i<storedGames; i++) {
19220             if(savedDetails[i])
19221                 free(savedDetails[i]);
19222             savedDetails[i] = NULL;
19223         }
19224         for(i=framePtr; i<MAX_MOVES; i++) {
19225                 if(commentList[i]) free(commentList[i]);
19226                 commentList[i] = NULL;
19227         }
19228         framePtr = MAX_MOVES-1;
19229         storedGames = 0;
19230 }
19231
19232 void
19233 LoadVariation (int index, char *text)
19234 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19235         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19236         int level = 0, move;
19237
19238         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19239         // first find outermost bracketing variation
19240         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19241             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19242                 if(*p == '{') wait = '}'; else
19243                 if(*p == '[') wait = ']'; else
19244                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19245                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19246             }
19247             if(*p == wait) wait = NULLCHAR; // closing ]} found
19248             p++;
19249         }
19250         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19251         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19252         end[1] = NULLCHAR; // clip off comment beyond variation
19253         ToNrEvent(currentMove-1);
19254         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19255         // kludge: use ParsePV() to append variation to game
19256         move = currentMove;
19257         ParsePV(start, TRUE, TRUE);
19258         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19259         ClearPremoveHighlights();
19260         CommentPopDown();
19261         ToNrEvent(currentMove+1);
19262 }
19263
19264 int transparency[2];
19265
19266 void
19267 LoadTheme ()
19268 {
19269 #define BUF_SIZ (2*MSG_SIZ)
19270     char *p, *q, buf[BUF_SIZ];
19271     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19272         snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19273         ParseArgsFromString(buf);
19274         ActivateTheme(TRUE); // also redo colors
19275         return;
19276     }
19277     p = nickName;
19278     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19279     {
19280         int len;
19281         q = appData.themeNames;
19282         snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19283       if(appData.useBitmaps) {
19284         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19285                 Shorten(appData.liteBackTextureFile));
19286         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19287                 Shorten(appData.darkBackTextureFile),
19288                 appData.liteBackTextureMode,
19289                 appData.darkBackTextureMode );
19290       } else {
19291         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19292       }
19293       if(!appData.useBitmaps || transparency[0]) {
19294         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19295       }
19296       if(!appData.useBitmaps || transparency[1]) {
19297         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19298       }
19299       if(appData.useBorder) {
19300         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19301                 appData.border);
19302       } else {
19303         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19304       }
19305       if(appData.useFont) {
19306         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19307                 appData.renderPiecesWithFont,
19308                 appData.fontToPieceTable,
19309                 Col2Text(9),    // appData.fontBackColorWhite
19310                 Col2Text(10) ); // appData.fontForeColorBlack
19311       } else {
19312         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19313         if(appData.pieceDirectory[0]) {
19314           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19315           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19316             snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19317         }
19318         if(!appData.pieceDirectory[0] || !appData.trueColors)
19319           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19320                 Col2Text(0),   // whitePieceColor
19321                 Col2Text(1) ); // blackPieceColor
19322       }
19323       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19324                 Col2Text(4),   // highlightSquareColor
19325                 Col2Text(5) ); // premoveHighlightColor
19326         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19327         if(insert != q) insert[-1] = NULLCHAR;
19328         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19329         if(q)   free(q);
19330     }
19331     ActivateTheme(FALSE);
19332 }