Fix Seirawan gating at Rook square in PGN castling moves
[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, tryNr;
917
918 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
919 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
920 char *insert, *wbOptions, *currentEngine[2]; // point in ChessProgramNames were we should insert new engine
921 static char newEngineCommand[MSG_SIZ];
922
923 void
924 FloatToFront(char **list, char *engineLine)
925 {
926     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
927     int i=0;
928     if(appData.recentEngines <= 0) return;
929     TidyProgramName(engineLine, "localhost", tidy+1);
930     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
931     strncpy(buf+1, *list, MSG_SIZ-50);
932     if(p = strstr(buf, tidy)) { // tidy name appears in list
933         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
934         while(*p++ = *++q); // squeeze out
935     }
936     strcat(tidy, buf+1); // put list behind tidy name
937     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
938     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
939     ASSIGN(*list, tidy+1);
940 }
941
942 void
943 SaveEngineList ()
944 {
945         FILE *f;
946         if(*engineListFile && (f = fopen(engineListFile, "w"))) {
947           fprintf(f, "-firstChessProgramNames {%s}\n", firstChessProgramNames);
948           fclose(f);
949         }
950 }
951
952 void
953 AddToEngineList (int i)
954 {
955     if(addToList) {
956         int len;
957         char quote, buf[MSG_SIZ];
958         char *q = firstChessProgramNames, *p = newEngineCommand;
959         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
960         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
961         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s",
962                         quote, p, quote, appData.directory[i],
963                         useNick ? " -fn \"" : "",
964                         useNick ? nickName : "",
965                         useNick ? "\"" : "",
966                         v1 ? " -firstProtocolVersion 1" : "",
967                         hasBook ? "" : " -fNoOwnBookUCI",
968                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
969                         storeVariant ? " -variant " : "",
970                         storeVariant ? VariantName(gameInfo.variant) : "");
971         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " %s", wbOptions);
972         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 2);
973         if(insert != q) insert[-1] = NULLCHAR;
974         snprintf(firstChessProgramNames, len, "%s\n%s\n%s", q, buf, insert);
975         if(q)   free(q);
976         SaveEngineList();
977         FloatToFront(&appData.recentEngineList, buf);
978         ASSIGN(currentEngine[i], buf);
979     }
980 }
981
982 void
983 LoadEngine ()
984 {
985     int i;
986     if(WaitForEngine(savCps, LoadEngine)) return;
987     if(tryNr == 1 && !isUCI) { SendToProgram("uci\n", savCps); tryNr = 2; ScheduleDelayedEvent(LoadEngine, FEATURE_TIMEOUT); return; }
988     if(tryNr) v1 |= (tryNr == 2), tryNr = 0, AddToEngineList(0); // deferred to after protocol determination
989     CommonEngineInit(); // recalculate time odds
990     if(gameInfo.variant != StringToVariant(appData.variant)) {
991         // we changed variant when loading the engine; this forces us to reset
992         Reset(TRUE, savCps != &first);
993         oldMode = BeginningOfGame; // to prevent restoring old mode
994     }
995     InitChessProgram(savCps, FALSE);
996     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
997     DisplayMessage("", "");
998     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
999     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
1000     ThawUI();
1001     SetGNUMode();
1002     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
1003 }
1004
1005 void
1006 ReplaceEngine (ChessProgramState *cps, int n)
1007 {
1008     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
1009     keepInfo = 1;
1010     if(oldMode != BeginningOfGame) EditGameEvent();
1011     keepInfo = 0;
1012     UnloadEngine(cps);
1013     appData.noChessProgram = FALSE;
1014     appData.clockMode = TRUE;
1015     InitEngine(cps, n);
1016     UpdateLogos(TRUE);
1017     if(n && !tryNr) return; // only startup first engine immediately; second can wait (unless autodetect)
1018     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
1019     LoadEngine();
1020 }
1021
1022 static char resetOptions[] =
1023         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
1024         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
1025         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
1026         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
1027
1028 void
1029 Load (ChessProgramState *cps, int i)
1030 {
1031     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
1032     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
1033         ASSIGN(currentEngine[i], engineLine);
1034         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
1035         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
1036         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
1037         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
1038         appData.firstProtocolVersion = PROTOVER;
1039         ParseArgsFromString(buf);
1040         SwapEngines(i);
1041         ReplaceEngine(cps, i);
1042         FloatToFront(&appData.recentEngineList, engineLine);
1043         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1044         return;
1045     }
1046     p = engineName;
1047     while(q = strchr(p, SLASH)) p = q+1;
1048     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1049     if(engineDir[0] != NULLCHAR) {
1050         ASSIGN(appData.directory[i], engineDir); p = engineName;
1051     } else if(p != engineName) { // derive directory from engine path, when not given
1052         p[-1] = 0;
1053         ASSIGN(appData.directory[i], engineName);
1054         p[-1] = SLASH;
1055         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1056     } else { ASSIGN(appData.directory[i], "."); }
1057     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1058     if(params[0]) {
1059         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1060         snprintf(command, MSG_SIZ, "%s %s", p, params);
1061         p = command;
1062     }
1063     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1064     ASSIGN(appData.chessProgram[i], p);
1065     tryNr = 3; // requests adding to list without auto-detect
1066     if(isUCI == 3) tryNr = 1, isUCI = 0; // auto-detect
1067     appData.isUCI[i] = isUCI;
1068     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1069     appData.hasOwnBookUCI[i] = hasBook;
1070     if(!nickName[0]) useNick = FALSE;
1071     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1072     safeStrCpy(newEngineCommand, p, MSG_SIZ);
1073     ReplaceEngine(cps, i);
1074 }
1075
1076 void
1077 InitTimeControls ()
1078 {
1079     int matched, min, sec;
1080     /*
1081      * Parse timeControl resource
1082      */
1083     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1084                           appData.movesPerSession)) {
1085         char buf[MSG_SIZ];
1086         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1087         DisplayFatalError(buf, 0, 2);
1088     }
1089
1090     /*
1091      * Parse searchTime resource
1092      */
1093     if (*appData.searchTime != NULLCHAR) {
1094         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1095         if (matched == 1) {
1096             searchTime = min * 60;
1097         } else if (matched == 2) {
1098             searchTime = min * 60 + sec;
1099         } else {
1100             char buf[MSG_SIZ];
1101             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1102             DisplayFatalError(buf, 0, 2);
1103         }
1104     }
1105 }
1106
1107 void
1108 InitBackEnd1 ()
1109 {
1110
1111     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1112     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1113
1114     GetTimeMark(&programStartTime);
1115     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1116     appData.seedBase = random() + (random()<<15);
1117     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1118
1119     ClearProgramStats();
1120     programStats.ok_to_send = 1;
1121     programStats.seen_stat = 0;
1122
1123     /*
1124      * Initialize game list
1125      */
1126     ListNew(&gameList);
1127
1128
1129     /*
1130      * Internet chess server status
1131      */
1132     if (appData.icsActive) {
1133         appData.matchMode = FALSE;
1134         appData.matchGames = 0;
1135 #if ZIPPY
1136         appData.noChessProgram = !appData.zippyPlay;
1137 #else
1138         appData.zippyPlay = FALSE;
1139         appData.zippyTalk = FALSE;
1140         appData.noChessProgram = TRUE;
1141 #endif
1142         if (*appData.icsHelper != NULLCHAR) {
1143             appData.useTelnet = TRUE;
1144             appData.telnetProgram = appData.icsHelper;
1145         }
1146     } else {
1147         appData.zippyTalk = appData.zippyPlay = FALSE;
1148     }
1149
1150     /* [AS] Initialize pv info list [HGM] and game state */
1151     {
1152         int i, j;
1153
1154         for( i=0; i<=framePtr; i++ ) {
1155             pvInfoList[i].depth = -1;
1156             boards[i][EP_STATUS] = EP_NONE;
1157             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1158         }
1159     }
1160
1161     InitTimeControls();
1162
1163     /* [AS] Adjudication threshold */
1164     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1165
1166     InitEngine(&first, 0);
1167     InitEngine(&second, 1);
1168     CommonEngineInit();
1169
1170     pairing.which = "pairing"; // pairing engine
1171     pairing.pr = NoProc;
1172     pairing.isr = NULL;
1173     pairing.program = appData.pairingEngine;
1174     pairing.host = "localhost";
1175     pairing.dir = ".";
1176
1177     if (appData.icsActive) {
1178         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1179     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1180         appData.clockMode = FALSE;
1181         first.sendTime = second.sendTime = 0;
1182     }
1183
1184 #if ZIPPY
1185     /* Override some settings from environment variables, for backward
1186        compatibility.  Unfortunately it's not feasible to have the env
1187        vars just set defaults, at least in xboard.  Ugh.
1188     */
1189     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1190       ZippyInit();
1191     }
1192 #endif
1193
1194     if (!appData.icsActive) {
1195       char buf[MSG_SIZ];
1196       int len;
1197
1198       /* Check for variants that are supported only in ICS mode,
1199          or not at all.  Some that are accepted here nevertheless
1200          have bugs; see comments below.
1201       */
1202       VariantClass variant = StringToVariant(appData.variant);
1203       switch (variant) {
1204       case VariantBughouse:     /* need four players and two boards */
1205       case VariantKriegspiel:   /* need to hide pieces and move details */
1206         /* case VariantFischeRandom: (Fabien: moved below) */
1207         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1208         if( (len >= MSG_SIZ) && appData.debugMode )
1209           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1210
1211         DisplayFatalError(buf, 0, 2);
1212         return;
1213
1214       case VariantUnknown:
1215       case VariantLoadable:
1216       case Variant29:
1217       case Variant30:
1218       case Variant31:
1219       case Variant32:
1220       case Variant33:
1221       case Variant34:
1222       case Variant35:
1223       case Variant36:
1224       default:
1225         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1226         if( (len >= MSG_SIZ) && appData.debugMode )
1227           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1228
1229         DisplayFatalError(buf, 0, 2);
1230         return;
1231
1232       case VariantNormal:     /* definitely works! */
1233         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1234           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1235           return;
1236         }
1237       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1238       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1239       case VariantGothic:     /* [HGM] should work */
1240       case VariantCapablanca: /* [HGM] should work */
1241       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1242       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1243       case VariantChu:        /* [HGM] experimental */
1244       case VariantKnightmate: /* [HGM] should work */
1245       case VariantCylinder:   /* [HGM] untested */
1246       case VariantFalcon:     /* [HGM] untested */
1247       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1248                                  offboard interposition not understood */
1249       case VariantWildCastle: /* pieces not automatically shuffled */
1250       case VariantNoCastle:   /* pieces not automatically shuffled */
1251       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1252       case VariantLosers:     /* should work except for win condition,
1253                                  and doesn't know captures are mandatory */
1254       case VariantSuicide:    /* should work except for win condition,
1255                                  and doesn't know captures are mandatory */
1256       case VariantGiveaway:   /* should work except for win condition,
1257                                  and doesn't know captures are mandatory */
1258       case VariantTwoKings:   /* should work */
1259       case VariantAtomic:     /* should work except for win condition */
1260       case Variant3Check:     /* should work except for win condition */
1261       case VariantShatranj:   /* should work except for all win conditions */
1262       case VariantMakruk:     /* should work except for draw countdown */
1263       case VariantASEAN :     /* should work except for draw countdown */
1264       case VariantBerolina:   /* might work if TestLegality is off */
1265       case VariantCapaRandom: /* should work */
1266       case VariantJanus:      /* should work */
1267       case VariantSuper:      /* experimental */
1268       case VariantGreat:      /* experimental, requires legality testing to be off */
1269       case VariantSChess:     /* S-Chess, should work */
1270       case VariantGrand:      /* should work */
1271       case VariantSpartan:    /* should work */
1272       case VariantLion:       /* should work */
1273       case VariantChuChess:   /* should work */
1274         break;
1275       }
1276     }
1277
1278 }
1279
1280 int
1281 NextIntegerFromString (char ** str, long * value)
1282 {
1283     int result = -1;
1284     char * s = *str;
1285
1286     while( *s == ' ' || *s == '\t' ) {
1287         s++;
1288     }
1289
1290     *value = 0;
1291
1292     if( *s >= '0' && *s <= '9' ) {
1293         while( *s >= '0' && *s <= '9' ) {
1294             *value = *value * 10 + (*s - '0');
1295             s++;
1296         }
1297
1298         result = 0;
1299     }
1300
1301     *str = s;
1302
1303     return result;
1304 }
1305
1306 int
1307 NextTimeControlFromString (char ** str, long * value)
1308 {
1309     long temp;
1310     int result = NextIntegerFromString( str, &temp );
1311
1312     if( result == 0 ) {
1313         *value = temp * 60; /* Minutes */
1314         if( **str == ':' ) {
1315             (*str)++;
1316             result = NextIntegerFromString( str, &temp );
1317             *value += temp; /* Seconds */
1318         }
1319     }
1320
1321     return result;
1322 }
1323
1324 int
1325 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1326 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1327     int result = -1, type = 0; long temp, temp2;
1328
1329     if(**str != ':') return -1; // old params remain in force!
1330     (*str)++;
1331     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1332     if( NextIntegerFromString( str, &temp ) ) return -1;
1333     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1334
1335     if(**str != '/') {
1336         /* time only: incremental or sudden-death time control */
1337         if(**str == '+') { /* increment follows; read it */
1338             (*str)++;
1339             if(**str == '!') type = *(*str)++; // Bronstein TC
1340             if(result = NextIntegerFromString( str, &temp2)) return -1;
1341             *inc = temp2 * 1000;
1342             if(**str == '.') { // read fraction of increment
1343                 char *start = ++(*str);
1344                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1345                 temp2 *= 1000;
1346                 while(start++ < *str) temp2 /= 10;
1347                 *inc += temp2;
1348             }
1349         } else *inc = 0;
1350         *moves = 0; *tc = temp * 1000; *incType = type;
1351         return 0;
1352     }
1353
1354     (*str)++; /* classical time control */
1355     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1356
1357     if(result == 0) {
1358         *moves = temp;
1359         *tc    = temp2 * 1000;
1360         *inc   = 0;
1361         *incType = type;
1362     }
1363     return result;
1364 }
1365
1366 int
1367 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1368 {   /* [HGM] get time to add from the multi-session time-control string */
1369     int incType, moves=1; /* kludge to force reading of first session */
1370     long time, increment;
1371     char *s = tcString;
1372
1373     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1374     do {
1375         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1376         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1377         if(movenr == -1) return time;    /* last move before new session     */
1378         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1379         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1380         if(!moves) return increment;     /* current session is incremental   */
1381         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1382     } while(movenr >= -1);               /* try again for next session       */
1383
1384     return 0; // no new time quota on this move
1385 }
1386
1387 int
1388 ParseTimeControl (char *tc, float ti, int mps)
1389 {
1390   long tc1;
1391   long tc2;
1392   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1393   int min, sec=0;
1394
1395   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1396   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1397       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1398   if(ti > 0) {
1399
1400     if(mps)
1401       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1402     else
1403       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1404   } else {
1405     if(mps)
1406       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1407     else
1408       snprintf(buf, MSG_SIZ, ":%s", mytc);
1409   }
1410   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1411
1412   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1413     return FALSE;
1414   }
1415
1416   if( *tc == '/' ) {
1417     /* Parse second time control */
1418     tc++;
1419
1420     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1421       return FALSE;
1422     }
1423
1424     if( tc2 == 0 ) {
1425       return FALSE;
1426     }
1427
1428     timeControl_2 = tc2 * 1000;
1429   }
1430   else {
1431     timeControl_2 = 0;
1432   }
1433
1434   if( tc1 == 0 ) {
1435     return FALSE;
1436   }
1437
1438   timeControl = tc1 * 1000;
1439
1440   if (ti >= 0) {
1441     timeIncrement = ti * 1000;  /* convert to ms */
1442     movesPerSession = 0;
1443   } else {
1444     timeIncrement = 0;
1445     movesPerSession = mps;
1446   }
1447   return TRUE;
1448 }
1449
1450 void
1451 InitBackEnd2 ()
1452 {
1453     if (appData.debugMode) {
1454 #    ifdef __GIT_VERSION
1455       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1456 #    else
1457       fprintf(debugFP, "Version: %s\n", programVersion);
1458 #    endif
1459     }
1460     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1461
1462     set_cont_sequence(appData.wrapContSeq);
1463     if (appData.matchGames > 0) {
1464         appData.matchMode = TRUE;
1465     } else if (appData.matchMode) {
1466         appData.matchGames = 1;
1467     }
1468     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1469         appData.matchGames = appData.sameColorGames;
1470     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1471         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1472         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1473     }
1474     Reset(TRUE, FALSE);
1475     if (appData.noChessProgram || first.protocolVersion == 1) {
1476       InitBackEnd3();
1477     } else {
1478       /* kludge: allow timeout for initial "feature" commands */
1479       FreezeUI();
1480       DisplayMessage("", _("Starting chess program"));
1481       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1482     }
1483 }
1484
1485 int
1486 CalculateIndex (int index, int gameNr)
1487 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1488     int res;
1489     if(index > 0) return index; // fixed nmber
1490     if(index == 0) return 1;
1491     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1492     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1493     return res;
1494 }
1495
1496 int
1497 LoadGameOrPosition (int gameNr)
1498 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1499     if (*appData.loadGameFile != NULLCHAR) {
1500         if (!LoadGameFromFile(appData.loadGameFile,
1501                 CalculateIndex(appData.loadGameIndex, gameNr),
1502                               appData.loadGameFile, FALSE)) {
1503             DisplayFatalError(_("Bad game file"), 0, 1);
1504             return 0;
1505         }
1506     } else if (*appData.loadPositionFile != NULLCHAR) {
1507         if (!LoadPositionFromFile(appData.loadPositionFile,
1508                 CalculateIndex(appData.loadPositionIndex, gameNr),
1509                                   appData.loadPositionFile)) {
1510             DisplayFatalError(_("Bad position file"), 0, 1);
1511             return 0;
1512         }
1513     }
1514     return 1;
1515 }
1516
1517 void
1518 ReserveGame (int gameNr, char resChar)
1519 {
1520     FILE *tf = fopen(appData.tourneyFile, "r+");
1521     char *p, *q, c, buf[MSG_SIZ];
1522     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1523     safeStrCpy(buf, lastMsg, MSG_SIZ);
1524     DisplayMessage(_("Pick new game"), "");
1525     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1526     ParseArgsFromFile(tf);
1527     p = q = appData.results;
1528     if(appData.debugMode) {
1529       char *r = appData.participants;
1530       fprintf(debugFP, "results = '%s'\n", p);
1531       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1532       fprintf(debugFP, "\n");
1533     }
1534     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1535     nextGame = q - p;
1536     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1537     safeStrCpy(q, p, strlen(p) + 2);
1538     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1539     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1540     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1541         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1542         q[nextGame] = '*';
1543     }
1544     fseek(tf, -(strlen(p)+4), SEEK_END);
1545     c = fgetc(tf);
1546     if(c != '"') // depending on DOS or Unix line endings we can be one off
1547          fseek(tf, -(strlen(p)+2), SEEK_END);
1548     else fseek(tf, -(strlen(p)+3), SEEK_END);
1549     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1550     DisplayMessage(buf, "");
1551     free(p); appData.results = q;
1552     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1553        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1554       int round = appData.defaultMatchGames * appData.tourneyType;
1555       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1556          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1557         UnloadEngine(&first);  // next game belongs to other pairing;
1558         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1559     }
1560     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1561 }
1562
1563 void
1564 MatchEvent (int mode)
1565 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1566         int dummy;
1567         if(matchMode) { // already in match mode: switch it off
1568             abortMatch = TRUE;
1569             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1570             return;
1571         }
1572 //      if(gameMode != BeginningOfGame) {
1573 //          DisplayError(_("You can only start a match from the initial position."), 0);
1574 //          return;
1575 //      }
1576         abortMatch = FALSE;
1577         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1578         /* Set up machine vs. machine match */
1579         nextGame = 0;
1580         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1581         if(appData.tourneyFile[0]) {
1582             ReserveGame(-1, 0);
1583             if(nextGame > appData.matchGames) {
1584                 char buf[MSG_SIZ];
1585                 if(strchr(appData.results, '*') == NULL) {
1586                     FILE *f;
1587                     appData.tourneyCycles++;
1588                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1589                         fclose(f);
1590                         NextTourneyGame(-1, &dummy);
1591                         ReserveGame(-1, 0);
1592                         if(nextGame <= appData.matchGames) {
1593                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1594                             matchMode = mode;
1595                             ScheduleDelayedEvent(NextMatchGame, 10000);
1596                             return;
1597                         }
1598                     }
1599                 }
1600                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1601                 DisplayError(buf, 0);
1602                 appData.tourneyFile[0] = 0;
1603                 return;
1604             }
1605         } else
1606         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1607             DisplayFatalError(_("Can't have a match with no chess programs"),
1608                               0, 2);
1609             return;
1610         }
1611         matchMode = mode;
1612         matchGame = roundNr = 1;
1613         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1614         NextMatchGame();
1615 }
1616
1617 void
1618 InitBackEnd3 P((void))
1619 {
1620     GameMode initialMode;
1621     char buf[MSG_SIZ];
1622     int err, len;
1623
1624     ParseFeatures(appData.features[0], &first);
1625     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1626        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1627         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1628        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1629        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1630         char c, *q = first.variants, *p = strchr(q, ',');
1631         if(p) *p = NULLCHAR;
1632         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1633             int w, h, s;
1634             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1635                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1636             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1637             Reset(TRUE, FALSE);         // and re-initialize
1638         }
1639         if(p) *p = ',';
1640     }
1641
1642     InitChessProgram(&first, startedFromSetupPosition);
1643
1644     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1645         free(programVersion);
1646         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1647         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1648         FloatToFront(&appData.recentEngineList, currentEngine[0] ? currentEngine[0] : appData.firstChessProgram);
1649     }
1650
1651     if (appData.icsActive) {
1652 #ifdef WIN32
1653         /* [DM] Make a console window if needed [HGM] merged ifs */
1654         ConsoleCreate();
1655 #endif
1656         err = establish();
1657         if (err != 0)
1658           {
1659             if (*appData.icsCommPort != NULLCHAR)
1660               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1661                              appData.icsCommPort);
1662             else
1663               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1664                         appData.icsHost, appData.icsPort);
1665
1666             if( (len >= MSG_SIZ) && appData.debugMode )
1667               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1668
1669             DisplayFatalError(buf, err, 1);
1670             return;
1671         }
1672         SetICSMode();
1673         telnetISR =
1674           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1675         fromUserISR =
1676           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1677         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1678             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1679     } else if (appData.noChessProgram) {
1680         SetNCPMode();
1681     } else {
1682         SetGNUMode();
1683     }
1684
1685     if (*appData.cmailGameName != NULLCHAR) {
1686         SetCmailMode();
1687         OpenLoopback(&cmailPR);
1688         cmailISR =
1689           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1690     }
1691
1692     ThawUI();
1693     DisplayMessage("", "");
1694     if (StrCaseCmp(appData.initialMode, "") == 0) {
1695       initialMode = BeginningOfGame;
1696       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1697         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1698         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1699         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1700         ModeHighlight();
1701       }
1702     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1703       initialMode = TwoMachinesPlay;
1704     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1705       initialMode = AnalyzeFile;
1706     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1707       initialMode = AnalyzeMode;
1708     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1709       initialMode = MachinePlaysWhite;
1710     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1711       initialMode = MachinePlaysBlack;
1712     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1713       initialMode = EditGame;
1714     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1715       initialMode = EditPosition;
1716     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1717       initialMode = Training;
1718     } else {
1719       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1720       if( (len >= MSG_SIZ) && appData.debugMode )
1721         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1722
1723       DisplayFatalError(buf, 0, 2);
1724       return;
1725     }
1726
1727     if (appData.matchMode) {
1728         if(appData.tourneyFile[0]) { // start tourney from command line
1729             FILE *f;
1730             if(f = fopen(appData.tourneyFile, "r")) {
1731                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1732                 fclose(f);
1733                 appData.clockMode = TRUE;
1734                 SetGNUMode();
1735             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1736         }
1737         MatchEvent(TRUE);
1738     } else if (*appData.cmailGameName != NULLCHAR) {
1739         /* Set up cmail mode */
1740         ReloadCmailMsgEvent(TRUE);
1741     } else {
1742         /* Set up other modes */
1743         if (initialMode == AnalyzeFile) {
1744           if (*appData.loadGameFile == NULLCHAR) {
1745             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1746             return;
1747           }
1748         }
1749         if (*appData.loadGameFile != NULLCHAR) {
1750             (void) LoadGameFromFile(appData.loadGameFile,
1751                                     appData.loadGameIndex,
1752                                     appData.loadGameFile, TRUE);
1753         } else if (*appData.loadPositionFile != NULLCHAR) {
1754             (void) LoadPositionFromFile(appData.loadPositionFile,
1755                                         appData.loadPositionIndex,
1756                                         appData.loadPositionFile);
1757             /* [HGM] try to make self-starting even after FEN load */
1758             /* to allow automatic setup of fairy variants with wtm */
1759             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1760                 gameMode = BeginningOfGame;
1761                 setboardSpoiledMachineBlack = 1;
1762             }
1763             /* [HGM] loadPos: make that every new game uses the setup */
1764             /* from file as long as we do not switch variant          */
1765             if(!blackPlaysFirst) {
1766                 startedFromPositionFile = TRUE;
1767                 CopyBoard(filePosition, boards[0]);
1768                 CopyBoard(initialPosition, boards[0]);
1769             }
1770         } else if(*appData.fen != NULLCHAR) {
1771             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1772                 startedFromPositionFile = TRUE;
1773                 Reset(TRUE, TRUE);
1774             }
1775         }
1776         if (initialMode == AnalyzeMode) {
1777           if (appData.noChessProgram) {
1778             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1779             return;
1780           }
1781           if (appData.icsActive) {
1782             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1783             return;
1784           }
1785           AnalyzeModeEvent();
1786         } else if (initialMode == AnalyzeFile) {
1787           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1788           ShowThinkingEvent();
1789           AnalyzeFileEvent();
1790           AnalysisPeriodicEvent(1);
1791         } else if (initialMode == MachinePlaysWhite) {
1792           if (appData.noChessProgram) {
1793             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1794                               0, 2);
1795             return;
1796           }
1797           if (appData.icsActive) {
1798             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1799                               0, 2);
1800             return;
1801           }
1802           MachineWhiteEvent();
1803         } else if (initialMode == MachinePlaysBlack) {
1804           if (appData.noChessProgram) {
1805             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1806                               0, 2);
1807             return;
1808           }
1809           if (appData.icsActive) {
1810             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1811                               0, 2);
1812             return;
1813           }
1814           MachineBlackEvent();
1815         } else if (initialMode == TwoMachinesPlay) {
1816           if (appData.noChessProgram) {
1817             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1818                               0, 2);
1819             return;
1820           }
1821           if (appData.icsActive) {
1822             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1823                               0, 2);
1824             return;
1825           }
1826           TwoMachinesEvent();
1827         } else if (initialMode == EditGame) {
1828           EditGameEvent();
1829         } else if (initialMode == EditPosition) {
1830           EditPositionEvent();
1831         } else if (initialMode == Training) {
1832           if (*appData.loadGameFile == NULLCHAR) {
1833             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1834             return;
1835           }
1836           TrainingEvent();
1837         }
1838     }
1839 }
1840
1841 void
1842 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1843 {
1844     DisplayBook(current+1);
1845
1846     MoveHistorySet( movelist, first, last, current, pvInfoList );
1847
1848     EvalGraphSet( first, last, current, pvInfoList );
1849
1850     MakeEngineOutputTitle();
1851 }
1852
1853 /*
1854  * Establish will establish a contact to a remote host.port.
1855  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1856  *  used to talk to the host.
1857  * Returns 0 if okay, error code if not.
1858  */
1859 int
1860 establish ()
1861 {
1862     char buf[MSG_SIZ];
1863
1864     if (*appData.icsCommPort != NULLCHAR) {
1865         /* Talk to the host through a serial comm port */
1866         return OpenCommPort(appData.icsCommPort, &icsPR);
1867
1868     } else if (*appData.gateway != NULLCHAR) {
1869         if (*appData.remoteShell == NULLCHAR) {
1870             /* Use the rcmd protocol to run telnet program on a gateway host */
1871             snprintf(buf, sizeof(buf), "%s %s %s",
1872                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1873             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1874
1875         } else {
1876             /* Use the rsh program to run telnet program on a gateway host */
1877             if (*appData.remoteUser == NULLCHAR) {
1878                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1879                         appData.gateway, appData.telnetProgram,
1880                         appData.icsHost, appData.icsPort);
1881             } else {
1882                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1883                         appData.remoteShell, appData.gateway,
1884                         appData.remoteUser, appData.telnetProgram,
1885                         appData.icsHost, appData.icsPort);
1886             }
1887             return StartChildProcess(buf, "", &icsPR);
1888
1889         }
1890     } else if (appData.useTelnet) {
1891         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1892
1893     } else {
1894         /* TCP socket interface differs somewhat between
1895            Unix and NT; handle details in the front end.
1896            */
1897         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1898     }
1899 }
1900
1901 void
1902 EscapeExpand (char *p, char *q)
1903 {       // [HGM] initstring: routine to shape up string arguments
1904         while(*p++ = *q++) if(p[-1] == '\\')
1905             switch(*q++) {
1906                 case 'n': p[-1] = '\n'; break;
1907                 case 'r': p[-1] = '\r'; break;
1908                 case 't': p[-1] = '\t'; break;
1909                 case '\\': p[-1] = '\\'; break;
1910                 case 0: *p = 0; return;
1911                 default: p[-1] = q[-1]; break;
1912             }
1913 }
1914
1915 void
1916 show_bytes (FILE *fp, char *buf, int count)
1917 {
1918     while (count--) {
1919         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1920             fprintf(fp, "\\%03o", *buf & 0xff);
1921         } else {
1922             putc(*buf, fp);
1923         }
1924         buf++;
1925     }
1926     fflush(fp);
1927 }
1928
1929 /* Returns an errno value */
1930 int
1931 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1932 {
1933     char buf[8192], *p, *q, *buflim;
1934     int left, newcount, outcount;
1935
1936     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1937         *appData.gateway != NULLCHAR) {
1938         if (appData.debugMode) {
1939             fprintf(debugFP, ">ICS: ");
1940             show_bytes(debugFP, message, count);
1941             fprintf(debugFP, "\n");
1942         }
1943         return OutputToProcess(pr, message, count, outError);
1944     }
1945
1946     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1947     p = message;
1948     q = buf;
1949     left = count;
1950     newcount = 0;
1951     while (left) {
1952         if (q >= buflim) {
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             q = buf;
1961             newcount = 0;
1962         }
1963         if (*p == '\n') {
1964             *q++ = '\r';
1965             newcount++;
1966         } else if (((unsigned char) *p) == TN_IAC) {
1967             *q++ = (char) TN_IAC;
1968             newcount ++;
1969         }
1970         *q++ = *p++;
1971         newcount++;
1972         left--;
1973     }
1974     if (appData.debugMode) {
1975         fprintf(debugFP, ">ICS: ");
1976         show_bytes(debugFP, buf, newcount);
1977         fprintf(debugFP, "\n");
1978     }
1979     outcount = OutputToProcess(pr, buf, newcount, outError);
1980     if (outcount < newcount) return -1; /* to be sure */
1981     return count;
1982 }
1983
1984 void
1985 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1986 {
1987     int outError, outCount;
1988     static int gotEof = 0;
1989     static FILE *ini;
1990
1991     /* Pass data read from player on to ICS */
1992     if (count > 0) {
1993         gotEof = 0;
1994         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1995         if (outCount < count) {
1996             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1997         }
1998         if(have_sent_ICS_logon == 2) {
1999           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
2000             fprintf(ini, "%s", message);
2001             have_sent_ICS_logon = 3;
2002           } else
2003             have_sent_ICS_logon = 1;
2004         } else if(have_sent_ICS_logon == 3) {
2005             fprintf(ini, "%s", message);
2006             fclose(ini);
2007           have_sent_ICS_logon = 1;
2008         }
2009     } else if (count < 0) {
2010         RemoveInputSource(isr);
2011         DisplayFatalError(_("Error reading from keyboard"), error, 1);
2012     } else if (gotEof++ > 0) {
2013         RemoveInputSource(isr);
2014         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
2015     }
2016 }
2017
2018 void
2019 KeepAlive ()
2020 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
2021     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2022     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2023     SendToICS("date\n");
2024     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2025 }
2026
2027 /* added routine for printf style output to ics */
2028 void
2029 ics_printf (char *format, ...)
2030 {
2031     char buffer[MSG_SIZ];
2032     va_list args;
2033
2034     va_start(args, format);
2035     vsnprintf(buffer, sizeof(buffer), format, args);
2036     buffer[sizeof(buffer)-1] = '\0';
2037     SendToICS(buffer);
2038     va_end(args);
2039 }
2040
2041 void
2042 SendToICS (char *s)
2043 {
2044     int count, outCount, outError;
2045
2046     if (icsPR == NoProc) return;
2047
2048     count = strlen(s);
2049     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2050     if (outCount < count) {
2051         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2052     }
2053 }
2054
2055 /* This is used for sending logon scripts to the ICS. Sending
2056    without a delay causes problems when using timestamp on ICC
2057    (at least on my machine). */
2058 void
2059 SendToICSDelayed (char *s, long msdelay)
2060 {
2061     int count, outCount, outError;
2062
2063     if (icsPR == NoProc) return;
2064
2065     count = strlen(s);
2066     if (appData.debugMode) {
2067         fprintf(debugFP, ">ICS: ");
2068         show_bytes(debugFP, s, count);
2069         fprintf(debugFP, "\n");
2070     }
2071     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2072                                       msdelay);
2073     if (outCount < count) {
2074         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2075     }
2076 }
2077
2078
2079 /* Remove all highlighting escape sequences in s
2080    Also deletes any suffix starting with '('
2081    */
2082 char *
2083 StripHighlightAndTitle (char *s)
2084 {
2085     static char retbuf[MSG_SIZ];
2086     char *p = retbuf;
2087
2088     while (*s != NULLCHAR) {
2089         while (*s == '\033') {
2090             while (*s != NULLCHAR && !isalpha(*s)) s++;
2091             if (*s != NULLCHAR) s++;
2092         }
2093         while (*s != NULLCHAR && *s != '\033') {
2094             if (*s == '(' || *s == '[') {
2095                 *p = NULLCHAR;
2096                 return retbuf;
2097             }
2098             *p++ = *s++;
2099         }
2100     }
2101     *p = NULLCHAR;
2102     return retbuf;
2103 }
2104
2105 /* Remove all highlighting escape sequences in s */
2106 char *
2107 StripHighlight (char *s)
2108 {
2109     static char retbuf[MSG_SIZ];
2110     char *p = retbuf;
2111
2112     while (*s != NULLCHAR) {
2113         while (*s == '\033') {
2114             while (*s != NULLCHAR && !isalpha(*s)) s++;
2115             if (*s != NULLCHAR) s++;
2116         }
2117         while (*s != NULLCHAR && *s != '\033') {
2118             *p++ = *s++;
2119         }
2120     }
2121     *p = NULLCHAR;
2122     return retbuf;
2123 }
2124
2125 char engineVariant[MSG_SIZ];
2126 char *variantNames[] = VARIANT_NAMES;
2127 char *
2128 VariantName (VariantClass v)
2129 {
2130     if(v == VariantUnknown || *engineVariant) return engineVariant;
2131     return variantNames[v];
2132 }
2133
2134
2135 /* Identify a variant from the strings the chess servers use or the
2136    PGN Variant tag names we use. */
2137 VariantClass
2138 StringToVariant (char *e)
2139 {
2140     char *p;
2141     int wnum = -1;
2142     VariantClass v = VariantNormal;
2143     int i, found = FALSE;
2144     char buf[MSG_SIZ], c;
2145     int len;
2146
2147     if (!e) return v;
2148
2149     /* [HGM] skip over optional board-size prefixes */
2150     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2151         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2152         while( *e++ != '_');
2153     }
2154
2155     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2156         v = VariantNormal;
2157         found = TRUE;
2158     } else
2159     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2160       if (p = StrCaseStr(e, variantNames[i])) {
2161         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2162         v = (VariantClass) i;
2163         found = TRUE;
2164         break;
2165       }
2166     }
2167
2168     if (!found) {
2169       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2170           || StrCaseStr(e, "wild/fr")
2171           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2172         v = VariantFischeRandom;
2173       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2174                  (i = 1, p = StrCaseStr(e, "w"))) {
2175         p += i;
2176         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2177         if (isdigit(*p)) {
2178           wnum = atoi(p);
2179         } else {
2180           wnum = -1;
2181         }
2182         switch (wnum) {
2183         case 0: /* FICS only, actually */
2184         case 1:
2185           /* Castling legal even if K starts on d-file */
2186           v = VariantWildCastle;
2187           break;
2188         case 2:
2189         case 3:
2190         case 4:
2191           /* Castling illegal even if K & R happen to start in
2192              normal positions. */
2193           v = VariantNoCastle;
2194           break;
2195         case 5:
2196         case 7:
2197         case 8:
2198         case 10:
2199         case 11:
2200         case 12:
2201         case 13:
2202         case 14:
2203         case 15:
2204         case 18:
2205         case 19:
2206           /* Castling legal iff K & R start in normal positions */
2207           v = VariantNormal;
2208           break;
2209         case 6:
2210         case 20:
2211         case 21:
2212           /* Special wilds for position setup; unclear what to do here */
2213           v = VariantLoadable;
2214           break;
2215         case 9:
2216           /* Bizarre ICC game */
2217           v = VariantTwoKings;
2218           break;
2219         case 16:
2220           v = VariantKriegspiel;
2221           break;
2222         case 17:
2223           v = VariantLosers;
2224           break;
2225         case 22:
2226           v = VariantFischeRandom;
2227           break;
2228         case 23:
2229           v = VariantCrazyhouse;
2230           break;
2231         case 24:
2232           v = VariantBughouse;
2233           break;
2234         case 25:
2235           v = Variant3Check;
2236           break;
2237         case 26:
2238           /* Not quite the same as FICS suicide! */
2239           v = VariantGiveaway;
2240           break;
2241         case 27:
2242           v = VariantAtomic;
2243           break;
2244         case 28:
2245           v = VariantShatranj;
2246           break;
2247
2248         /* Temporary names for future ICC types.  The name *will* change in
2249            the next xboard/WinBoard release after ICC defines it. */
2250         case 29:
2251           v = Variant29;
2252           break;
2253         case 30:
2254           v = Variant30;
2255           break;
2256         case 31:
2257           v = Variant31;
2258           break;
2259         case 32:
2260           v = Variant32;
2261           break;
2262         case 33:
2263           v = Variant33;
2264           break;
2265         case 34:
2266           v = Variant34;
2267           break;
2268         case 35:
2269           v = Variant35;
2270           break;
2271         case 36:
2272           v = Variant36;
2273           break;
2274         case 37:
2275           v = VariantShogi;
2276           break;
2277         case 38:
2278           v = VariantXiangqi;
2279           break;
2280         case 39:
2281           v = VariantCourier;
2282           break;
2283         case 40:
2284           v = VariantGothic;
2285           break;
2286         case 41:
2287           v = VariantCapablanca;
2288           break;
2289         case 42:
2290           v = VariantKnightmate;
2291           break;
2292         case 43:
2293           v = VariantFairy;
2294           break;
2295         case 44:
2296           v = VariantCylinder;
2297           break;
2298         case 45:
2299           v = VariantFalcon;
2300           break;
2301         case 46:
2302           v = VariantCapaRandom;
2303           break;
2304         case 47:
2305           v = VariantBerolina;
2306           break;
2307         case 48:
2308           v = VariantJanus;
2309           break;
2310         case 49:
2311           v = VariantSuper;
2312           break;
2313         case 50:
2314           v = VariantGreat;
2315           break;
2316         case -1:
2317           /* Found "wild" or "w" in the string but no number;
2318              must assume it's normal chess. */
2319           v = VariantNormal;
2320           break;
2321         default:
2322           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2323           if( (len >= MSG_SIZ) && appData.debugMode )
2324             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2325
2326           DisplayError(buf, 0);
2327           v = VariantUnknown;
2328           break;
2329         }
2330       }
2331     }
2332     if (appData.debugMode) {
2333       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2334               e, wnum, VariantName(v));
2335     }
2336     return v;
2337 }
2338
2339 static int leftover_start = 0, leftover_len = 0;
2340 char star_match[STAR_MATCH_N][MSG_SIZ];
2341
2342 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2343    advance *index beyond it, and set leftover_start to the new value of
2344    *index; else return FALSE.  If pattern contains the character '*', it
2345    matches any sequence of characters not containing '\r', '\n', or the
2346    character following the '*' (if any), and the matched sequence(s) are
2347    copied into star_match.
2348    */
2349 int
2350 looking_at ( char *buf, int *index, char *pattern)
2351 {
2352     char *bufp = &buf[*index], *patternp = pattern;
2353     int star_count = 0;
2354     char *matchp = star_match[0];
2355
2356     for (;;) {
2357         if (*patternp == NULLCHAR) {
2358             *index = leftover_start = bufp - buf;
2359             *matchp = NULLCHAR;
2360             return TRUE;
2361         }
2362         if (*bufp == NULLCHAR) return FALSE;
2363         if (*patternp == '*') {
2364             if (*bufp == *(patternp + 1)) {
2365                 *matchp = NULLCHAR;
2366                 matchp = star_match[++star_count];
2367                 patternp += 2;
2368                 bufp++;
2369                 continue;
2370             } else if (*bufp == '\n' || *bufp == '\r') {
2371                 patternp++;
2372                 if (*patternp == NULLCHAR)
2373                   continue;
2374                 else
2375                   return FALSE;
2376             } else {
2377                 *matchp++ = *bufp++;
2378                 continue;
2379             }
2380         }
2381         if (*patternp != *bufp) return FALSE;
2382         patternp++;
2383         bufp++;
2384     }
2385 }
2386
2387 void
2388 SendToPlayer (char *data, int length)
2389 {
2390     int error, outCount;
2391     outCount = OutputToProcess(NoProc, data, length, &error);
2392     if (outCount < length) {
2393         DisplayFatalError(_("Error writing to display"), error, 1);
2394     }
2395 }
2396
2397 void
2398 PackHolding (char packed[], char *holding)
2399 {
2400     char *p = holding;
2401     char *q = packed;
2402     int runlength = 0;
2403     int curr = 9999;
2404     do {
2405         if (*p == curr) {
2406             runlength++;
2407         } else {
2408             switch (runlength) {
2409               case 0:
2410                 break;
2411               case 1:
2412                 *q++ = curr;
2413                 break;
2414               case 2:
2415                 *q++ = curr;
2416                 *q++ = curr;
2417                 break;
2418               default:
2419                 sprintf(q, "%d", runlength);
2420                 while (*q) q++;
2421                 *q++ = curr;
2422                 break;
2423             }
2424             runlength = 1;
2425             curr = *p;
2426         }
2427     } while (*p++);
2428     *q = NULLCHAR;
2429 }
2430
2431 /* Telnet protocol requests from the front end */
2432 void
2433 TelnetRequest (unsigned char ddww, unsigned char option)
2434 {
2435     unsigned char msg[3];
2436     int outCount, outError;
2437
2438     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2439
2440     if (appData.debugMode) {
2441         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2442         switch (ddww) {
2443           case TN_DO:
2444             ddwwStr = "DO";
2445             break;
2446           case TN_DONT:
2447             ddwwStr = "DONT";
2448             break;
2449           case TN_WILL:
2450             ddwwStr = "WILL";
2451             break;
2452           case TN_WONT:
2453             ddwwStr = "WONT";
2454             break;
2455           default:
2456             ddwwStr = buf1;
2457             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2458             break;
2459         }
2460         switch (option) {
2461           case TN_ECHO:
2462             optionStr = "ECHO";
2463             break;
2464           default:
2465             optionStr = buf2;
2466             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2467             break;
2468         }
2469         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2470     }
2471     msg[0] = TN_IAC;
2472     msg[1] = ddww;
2473     msg[2] = option;
2474     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2475     if (outCount < 3) {
2476         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2477     }
2478 }
2479
2480 void
2481 DoEcho ()
2482 {
2483     if (!appData.icsActive) return;
2484     TelnetRequest(TN_DO, TN_ECHO);
2485 }
2486
2487 void
2488 DontEcho ()
2489 {
2490     if (!appData.icsActive) return;
2491     TelnetRequest(TN_DONT, TN_ECHO);
2492 }
2493
2494 void
2495 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2496 {
2497     /* put the holdings sent to us by the server on the board holdings area */
2498     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2499     char p;
2500     ChessSquare piece;
2501
2502     if(gameInfo.holdingsWidth < 2)  return;
2503     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2504         return; // prevent overwriting by pre-board holdings
2505
2506     if( (int)lowestPiece >= BlackPawn ) {
2507         holdingsColumn = 0;
2508         countsColumn = 1;
2509         holdingsStartRow = handSize-1;
2510         direction = -1;
2511     } else {
2512         holdingsColumn = BOARD_WIDTH-1;
2513         countsColumn = BOARD_WIDTH-2;
2514         holdingsStartRow = 0;
2515         direction = 1;
2516     }
2517
2518     for(i=0; i<BOARD_RANKS-1; i++) { /* clear holdings */
2519         board[i][holdingsColumn] = EmptySquare;
2520         board[i][countsColumn]   = (ChessSquare) 0;
2521     }
2522     while( (p=*holdings++) != NULLCHAR ) {
2523         piece = CharToPiece( ToUpper(p) );
2524         if(piece == EmptySquare) continue;
2525         /*j = (int) piece - (int) WhitePawn;*/
2526         j = PieceToNumber(piece);
2527         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2528         if(j < 0) continue;               /* should not happen */
2529         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2530         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2531         board[holdingsStartRow+j*direction][countsColumn]++;
2532     }
2533 }
2534
2535
2536 void
2537 VariantSwitch (Board board, VariantClass newVariant)
2538 {
2539    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2540    static Board oldBoard;
2541
2542    startedFromPositionFile = FALSE;
2543    if(gameInfo.variant == newVariant) return;
2544
2545    /* [HGM] This routine is called each time an assignment is made to
2546     * gameInfo.variant during a game, to make sure the board sizes
2547     * are set to match the new variant. If that means adding or deleting
2548     * holdings, we shift the playing board accordingly
2549     * This kludge is needed because in ICS observe mode, we get boards
2550     * of an ongoing game without knowing the variant, and learn about the
2551     * latter only later. This can be because of the move list we requested,
2552     * in which case the game history is refilled from the beginning anyway,
2553     * but also when receiving holdings of a crazyhouse game. In the latter
2554     * case we want to add those holdings to the already received position.
2555     */
2556
2557
2558    if (appData.debugMode) {
2559      fprintf(debugFP, "Switch board from %s to %s\n",
2560              VariantName(gameInfo.variant), VariantName(newVariant));
2561      setbuf(debugFP, NULL);
2562    }
2563    shuffleOpenings = 0;       /* [HGM] shuffle */
2564    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2565    switch(newVariant)
2566      {
2567      case VariantShogi:
2568        newWidth = 9;  newHeight = 9;
2569        gameInfo.holdingsSize = 7;
2570      case VariantBughouse:
2571      case VariantCrazyhouse:
2572        newHoldingsWidth = 2; break;
2573      case VariantGreat:
2574        newWidth = 10;
2575      case VariantSuper:
2576        newHoldingsWidth = 2;
2577        gameInfo.holdingsSize = 8;
2578        break;
2579      case VariantGothic:
2580      case VariantCapablanca:
2581      case VariantCapaRandom:
2582        newWidth = 10;
2583      default:
2584        newHoldingsWidth = gameInfo.holdingsSize = 0;
2585      };
2586
2587    if(newWidth  != gameInfo.boardWidth  ||
2588       newHeight != gameInfo.boardHeight ||
2589       newHoldingsWidth != gameInfo.holdingsWidth ) {
2590
2591      /* shift position to new playing area, if needed */
2592      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2593        for(i=0; i<BOARD_HEIGHT; i++)
2594          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2595            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2596              board[i][j];
2597        for(i=0; i<newHeight; i++) {
2598          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2599          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2600        }
2601      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2602        for(i=0; i<BOARD_HEIGHT; i++)
2603          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2604            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2605              board[i][j];
2606      }
2607      board[HOLDINGS_SET] = 0;
2608      gameInfo.boardWidth  = newWidth;
2609      gameInfo.boardHeight = newHeight;
2610      gameInfo.holdingsWidth = newHoldingsWidth;
2611      gameInfo.variant = newVariant;
2612      InitDrawingSizes(-2, 0);
2613    } else gameInfo.variant = newVariant;
2614    CopyBoard(oldBoard, board);   // remember correctly formatted board
2615      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2616    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2617 }
2618
2619 static int loggedOn = FALSE;
2620
2621 /*-- Game start info cache: --*/
2622 int gs_gamenum;
2623 char gs_kind[MSG_SIZ];
2624 static char player1Name[128] = "";
2625 static char player2Name[128] = "";
2626 static char cont_seq[] = "\n\\   ";
2627 static int player1Rating = -1;
2628 static int player2Rating = -1;
2629 /*----------------------------*/
2630
2631 ColorClass curColor = ColorNormal;
2632 int suppressKibitz = 0;
2633
2634 // [HGM] seekgraph
2635 Boolean soughtPending = FALSE;
2636 Boolean seekGraphUp;
2637 #define MAX_SEEK_ADS 200
2638 #define SQUARE 0x80
2639 char *seekAdList[MAX_SEEK_ADS];
2640 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2641 float tcList[MAX_SEEK_ADS];
2642 char colorList[MAX_SEEK_ADS];
2643 int nrOfSeekAds = 0;
2644 int minRating = 1010, maxRating = 2800;
2645 int hMargin = 10, vMargin = 20, h, w;
2646 extern int squareSize, lineGap;
2647
2648 void
2649 PlotSeekAd (int i)
2650 {
2651         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2652         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2653         if(r < minRating+100 && r >=0 ) r = minRating+100;
2654         if(r > maxRating) r = maxRating;
2655         if(tc < 1.f) tc = 1.f;
2656         if(tc > 95.f) tc = 95.f;
2657         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2658         y = ((double)r - minRating)/(maxRating - minRating)
2659             * (h-vMargin-squareSize/8-1) + vMargin;
2660         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2661         if(strstr(seekAdList[i], " u ")) color = 1;
2662         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2663            !strstr(seekAdList[i], "bullet") &&
2664            !strstr(seekAdList[i], "blitz") &&
2665            !strstr(seekAdList[i], "standard") ) color = 2;
2666         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2667         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2668 }
2669
2670 void
2671 PlotSingleSeekAd (int i)
2672 {
2673         PlotSeekAd(i);
2674 }
2675
2676 void
2677 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2678 {
2679         char buf[MSG_SIZ], *ext = "";
2680         VariantClass v = StringToVariant(type);
2681         if(strstr(type, "wild")) {
2682             ext = type + 4; // append wild number
2683             if(v == VariantFischeRandom) type = "chess960"; else
2684             if(v == VariantLoadable) type = "setup"; else
2685             type = VariantName(v);
2686         }
2687         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2688         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2689             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2690             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2691             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2692             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2693             seekNrList[nrOfSeekAds] = nr;
2694             zList[nrOfSeekAds] = 0;
2695             seekAdList[nrOfSeekAds++] = StrSave(buf);
2696             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2697         }
2698 }
2699
2700 void
2701 EraseSeekDot (int i)
2702 {
2703     int x = xList[i], y = yList[i], d=squareSize/4, k;
2704     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2705     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2706     // now replot every dot that overlapped
2707     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2708         int xx = xList[k], yy = yList[k];
2709         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2710             DrawSeekDot(xx, yy, colorList[k]);
2711     }
2712 }
2713
2714 void
2715 RemoveSeekAd (int nr)
2716 {
2717         int i;
2718         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2719             EraseSeekDot(i);
2720             if(seekAdList[i]) free(seekAdList[i]);
2721             seekAdList[i] = seekAdList[--nrOfSeekAds];
2722             seekNrList[i] = seekNrList[nrOfSeekAds];
2723             ratingList[i] = ratingList[nrOfSeekAds];
2724             colorList[i]  = colorList[nrOfSeekAds];
2725             tcList[i] = tcList[nrOfSeekAds];
2726             xList[i]  = xList[nrOfSeekAds];
2727             yList[i]  = yList[nrOfSeekAds];
2728             zList[i]  = zList[nrOfSeekAds];
2729             seekAdList[nrOfSeekAds] = NULL;
2730             break;
2731         }
2732 }
2733
2734 Boolean
2735 MatchSoughtLine (char *line)
2736 {
2737     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2738     int nr, base, inc, u=0; char dummy;
2739
2740     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2741        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2742        (u=1) &&
2743        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2744         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2745         // match: compact and save the line
2746         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2747         return TRUE;
2748     }
2749     return FALSE;
2750 }
2751
2752 int
2753 DrawSeekGraph ()
2754 {
2755     int i;
2756     if(!seekGraphUp) return FALSE;
2757     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2758     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2759
2760     DrawSeekBackground(0, 0, w, h);
2761     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2762     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2763     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2764         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2765         yy = h-1-yy;
2766         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2767         if(i%500 == 0) {
2768             char buf[MSG_SIZ];
2769             snprintf(buf, MSG_SIZ, "%d", i);
2770             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2771         }
2772     }
2773     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2774     for(i=1; i<100; i+=(i<10?1:5)) {
2775         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2776         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2777         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2778             char buf[MSG_SIZ];
2779             snprintf(buf, MSG_SIZ, "%d", i);
2780             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2781         }
2782     }
2783     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2784     return TRUE;
2785 }
2786
2787 int
2788 SeekGraphClick (ClickType click, int x, int y, int moving)
2789 {
2790     static int lastDown = 0, displayed = 0, lastSecond;
2791     if(y < 0) return FALSE;
2792     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2793         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2794         if(!seekGraphUp) return FALSE;
2795         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2796         DrawPosition(TRUE, NULL);
2797         return TRUE;
2798     }
2799     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2800         if(click == Release || moving) return FALSE;
2801         nrOfSeekAds = 0;
2802         soughtPending = TRUE;
2803         SendToICS(ics_prefix);
2804         SendToICS("sought\n"); // should this be "sought all"?
2805     } else { // issue challenge based on clicked ad
2806         int dist = 10000; int i, closest = 0, second = 0;
2807         for(i=0; i<nrOfSeekAds; i++) {
2808             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2809             if(d < dist) { dist = d; closest = i; }
2810             second += (d - zList[i] < 120); // count in-range ads
2811             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2812         }
2813         if(dist < 120) {
2814             char buf[MSG_SIZ];
2815             second = (second > 1);
2816             if(displayed != closest || second != lastSecond) {
2817                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2818                 lastSecond = second; displayed = closest;
2819             }
2820             if(click == Press) {
2821                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2822                 lastDown = closest;
2823                 return TRUE;
2824             } // on press 'hit', only show info
2825             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2826             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2827             SendToICS(ics_prefix);
2828             SendToICS(buf);
2829             return TRUE; // let incoming board of started game pop down the graph
2830         } else if(click == Release) { // release 'miss' is ignored
2831             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2832             if(moving == 2) { // right up-click
2833                 nrOfSeekAds = 0; // refresh graph
2834                 soughtPending = TRUE;
2835                 SendToICS(ics_prefix);
2836                 SendToICS("sought\n"); // should this be "sought all"?
2837             }
2838             return TRUE;
2839         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2840         // press miss or release hit 'pop down' seek graph
2841         seekGraphUp = FALSE;
2842         DrawPosition(TRUE, NULL);
2843     }
2844     return TRUE;
2845 }
2846
2847 void
2848 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2849 {
2850 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2851 #define STARTED_NONE 0
2852 #define STARTED_MOVES 1
2853 #define STARTED_BOARD 2
2854 #define STARTED_OBSERVE 3
2855 #define STARTED_HOLDINGS 4
2856 #define STARTED_CHATTER 5
2857 #define STARTED_COMMENT 6
2858 #define STARTED_MOVES_NOHIDE 7
2859
2860     static int started = STARTED_NONE;
2861     static char parse[20000];
2862     static int parse_pos = 0;
2863     static char buf[BUF_SIZE + 1];
2864     static int firstTime = TRUE, intfSet = FALSE;
2865     static ColorClass prevColor = ColorNormal;
2866     static int savingComment = FALSE;
2867     static int cmatch = 0; // continuation sequence match
2868     char *bp;
2869     char str[MSG_SIZ];
2870     int i, oldi;
2871     int buf_len;
2872     int next_out;
2873     int tkind;
2874     int backup;    /* [DM] For zippy color lines */
2875     char *p;
2876     char talker[MSG_SIZ]; // [HGM] chat
2877     int channel, collective=0;
2878
2879     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2880
2881     if (appData.debugMode) {
2882       if (!error) {
2883         fprintf(debugFP, "<ICS: ");
2884         show_bytes(debugFP, data, count);
2885         fprintf(debugFP, "\n");
2886       }
2887     }
2888
2889     if (appData.debugMode) { int f = forwardMostMove;
2890         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2891                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2892                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2893     }
2894     if (count > 0) {
2895         /* If last read ended with a partial line that we couldn't parse,
2896            prepend it to the new read and try again. */
2897         if (leftover_len > 0) {
2898             for (i=0; i<leftover_len; i++)
2899               buf[i] = buf[leftover_start + i];
2900         }
2901
2902     /* copy new characters into the buffer */
2903     bp = buf + leftover_len;
2904     buf_len=leftover_len;
2905     for (i=0; i<count; i++)
2906     {
2907         // ignore these
2908         if (data[i] == '\r')
2909             continue;
2910
2911         // join lines split by ICS?
2912         if (!appData.noJoin)
2913         {
2914             /*
2915                 Joining just consists of finding matches against the
2916                 continuation sequence, and discarding that sequence
2917                 if found instead of copying it.  So, until a match
2918                 fails, there's nothing to do since it might be the
2919                 complete sequence, and thus, something we don't want
2920                 copied.
2921             */
2922             if (data[i] == cont_seq[cmatch])
2923             {
2924                 cmatch++;
2925                 if (cmatch == strlen(cont_seq))
2926                 {
2927                     cmatch = 0; // complete match.  just reset the counter
2928
2929                     /*
2930                         it's possible for the ICS to not include the space
2931                         at the end of the last word, making our [correct]
2932                         join operation fuse two separate words.  the server
2933                         does this when the space occurs at the width setting.
2934                     */
2935                     if (!buf_len || buf[buf_len-1] != ' ')
2936                     {
2937                         *bp++ = ' ';
2938                         buf_len++;
2939                     }
2940                 }
2941                 continue;
2942             }
2943             else if (cmatch)
2944             {
2945                 /*
2946                     match failed, so we have to copy what matched before
2947                     falling through and copying this character.  In reality,
2948                     this will only ever be just the newline character, but
2949                     it doesn't hurt to be precise.
2950                 */
2951                 strncpy(bp, cont_seq, cmatch);
2952                 bp += cmatch;
2953                 buf_len += cmatch;
2954                 cmatch = 0;
2955             }
2956         }
2957
2958         // copy this char
2959         *bp++ = data[i];
2960         buf_len++;
2961     }
2962
2963         buf[buf_len] = NULLCHAR;
2964 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2965         next_out = 0;
2966         leftover_start = 0;
2967
2968         i = 0;
2969         while (i < buf_len) {
2970             /* Deal with part of the TELNET option negotiation
2971                protocol.  We refuse to do anything beyond the
2972                defaults, except that we allow the WILL ECHO option,
2973                which ICS uses to turn off password echoing when we are
2974                directly connected to it.  We reject this option
2975                if localLineEditing mode is on (always on in xboard)
2976                and we are talking to port 23, which might be a real
2977                telnet server that will try to keep WILL ECHO on permanently.
2978              */
2979             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2980                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2981                 unsigned char option;
2982                 oldi = i;
2983                 switch ((unsigned char) buf[++i]) {
2984                   case TN_WILL:
2985                     if (appData.debugMode)
2986                       fprintf(debugFP, "\n<WILL ");
2987                     switch (option = (unsigned char) buf[++i]) {
2988                       case TN_ECHO:
2989                         if (appData.debugMode)
2990                           fprintf(debugFP, "ECHO ");
2991                         /* Reply only if this is a change, according
2992                            to the protocol rules. */
2993                         if (remoteEchoOption) break;
2994                         if (appData.localLineEditing &&
2995                             atoi(appData.icsPort) == TN_PORT) {
2996                             TelnetRequest(TN_DONT, TN_ECHO);
2997                         } else {
2998                             EchoOff();
2999                             TelnetRequest(TN_DO, TN_ECHO);
3000                             remoteEchoOption = TRUE;
3001                         }
3002                         break;
3003                       default:
3004                         if (appData.debugMode)
3005                           fprintf(debugFP, "%d ", option);
3006                         /* Whatever this is, we don't want it. */
3007                         TelnetRequest(TN_DONT, option);
3008                         break;
3009                     }
3010                     break;
3011                   case TN_WONT:
3012                     if (appData.debugMode)
3013                       fprintf(debugFP, "\n<WONT ");
3014                     switch (option = (unsigned char) buf[++i]) {
3015                       case TN_ECHO:
3016                         if (appData.debugMode)
3017                           fprintf(debugFP, "ECHO ");
3018                         /* Reply only if this is a change, according
3019                            to the protocol rules. */
3020                         if (!remoteEchoOption) break;
3021                         EchoOn();
3022                         TelnetRequest(TN_DONT, TN_ECHO);
3023                         remoteEchoOption = FALSE;
3024                         break;
3025                       default:
3026                         if (appData.debugMode)
3027                           fprintf(debugFP, "%d ", (unsigned char) option);
3028                         /* Whatever this is, it must already be turned
3029                            off, because we never agree to turn on
3030                            anything non-default, so according to the
3031                            protocol rules, we don't reply. */
3032                         break;
3033                     }
3034                     break;
3035                   case TN_DO:
3036                     if (appData.debugMode)
3037                       fprintf(debugFP, "\n<DO ");
3038                     switch (option = (unsigned char) buf[++i]) {
3039                       default:
3040                         /* Whatever this is, we refuse to do it. */
3041                         if (appData.debugMode)
3042                           fprintf(debugFP, "%d ", option);
3043                         TelnetRequest(TN_WONT, option);
3044                         break;
3045                     }
3046                     break;
3047                   case TN_DONT:
3048                     if (appData.debugMode)
3049                       fprintf(debugFP, "\n<DONT ");
3050                     switch (option = (unsigned char) buf[++i]) {
3051                       default:
3052                         if (appData.debugMode)
3053                           fprintf(debugFP, "%d ", option);
3054                         /* Whatever this is, we are already not doing
3055                            it, because we never agree to do anything
3056                            non-default, so according to the protocol
3057                            rules, we don't reply. */
3058                         break;
3059                     }
3060                     break;
3061                   case TN_IAC:
3062                     if (appData.debugMode)
3063                       fprintf(debugFP, "\n<IAC ");
3064                     /* Doubled IAC; pass it through */
3065                     i--;
3066                     break;
3067                   default:
3068                     if (appData.debugMode)
3069                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3070                     /* Drop all other telnet commands on the floor */
3071                     break;
3072                 }
3073                 if (oldi > next_out)
3074                   SendToPlayer(&buf[next_out], oldi - next_out);
3075                 if (++i > next_out)
3076                   next_out = i;
3077                 continue;
3078             }
3079
3080             /* OK, this at least will *usually* work */
3081             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3082                 loggedOn = TRUE;
3083             }
3084
3085             if (loggedOn && !intfSet) {
3086                 if (ics_type == ICS_ICC) {
3087                   snprintf(str, MSG_SIZ,
3088                           "/set-quietly interface %s\n/set-quietly style 12\n",
3089                           programVersion);
3090                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3091                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3092                 } else if (ics_type == ICS_CHESSNET) {
3093                   snprintf(str, MSG_SIZ, "/style 12\n");
3094                 } else {
3095                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3096                   strcat(str, programVersion);
3097                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3098                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3099                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3100 #ifdef WIN32
3101                   strcat(str, "$iset nohighlight 1\n");
3102 #endif
3103                   strcat(str, "$iset lock 1\n$style 12\n");
3104                 }
3105                 SendToICS(str);
3106                 NotifyFrontendLogin();
3107                 intfSet = TRUE;
3108             }
3109
3110             if (started == STARTED_COMMENT) {
3111                 /* Accumulate characters in comment */
3112                 parse[parse_pos++] = buf[i];
3113                 if (buf[i] == '\n') {
3114                     parse[parse_pos] = NULLCHAR;
3115                     if(chattingPartner>=0) {
3116                         char mess[MSG_SIZ];
3117                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3118                         OutputChatMessage(chattingPartner, mess);
3119                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3120                             int p;
3121                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3122                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3123                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3124                                 OutputChatMessage(p, mess);
3125                                 break;
3126                             }
3127                         }
3128                         chattingPartner = -1;
3129                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3130                         collective = 0;
3131                     } else
3132                     if(!suppressKibitz) // [HGM] kibitz
3133                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3134                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3135                         int nrDigit = 0, nrAlph = 0, j;
3136                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3137                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3138                         parse[parse_pos] = NULLCHAR;
3139                         // try to be smart: if it does not look like search info, it should go to
3140                         // ICS interaction window after all, not to engine-output window.
3141                         for(j=0; j<parse_pos; j++) { // count letters and digits
3142                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3143                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3144                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3145                         }
3146                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3147                             int depth=0; float score;
3148                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3149                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3150                                 pvInfoList[forwardMostMove-1].depth = depth;
3151                                 pvInfoList[forwardMostMove-1].score = 100*score;
3152                             }
3153                             OutputKibitz(suppressKibitz, parse);
3154                         } else {
3155                             char tmp[MSG_SIZ];
3156                             if(gameMode == IcsObserving) // restore original ICS messages
3157                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3158                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3159                             else
3160                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3161                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3162                             SendToPlayer(tmp, strlen(tmp));
3163                         }
3164                         next_out = i+1; // [HGM] suppress printing in ICS window
3165                     }
3166                     started = STARTED_NONE;
3167                 } else {
3168                     /* Don't match patterns against characters in comment */
3169                     i++;
3170                     continue;
3171                 }
3172             }
3173             if (started == STARTED_CHATTER) {
3174                 if (buf[i] != '\n') {
3175                     /* Don't match patterns against characters in chatter */
3176                     i++;
3177                     continue;
3178                 }
3179                 started = STARTED_NONE;
3180                 if(suppressKibitz) next_out = i+1;
3181             }
3182
3183             /* Kludge to deal with rcmd protocol */
3184             if (firstTime && looking_at(buf, &i, "\001*")) {
3185                 DisplayFatalError(&buf[1], 0, 1);
3186                 continue;
3187             } else {
3188                 firstTime = FALSE;
3189             }
3190
3191             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3192                 ics_type = ICS_ICC;
3193                 ics_prefix = "/";
3194                 if (appData.debugMode)
3195                   fprintf(debugFP, "ics_type %d\n", ics_type);
3196                 continue;
3197             }
3198             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3199                 ics_type = ICS_FICS;
3200                 ics_prefix = "$";
3201                 if (appData.debugMode)
3202                   fprintf(debugFP, "ics_type %d\n", ics_type);
3203                 continue;
3204             }
3205             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3206                 ics_type = ICS_CHESSNET;
3207                 ics_prefix = "/";
3208                 if (appData.debugMode)
3209                   fprintf(debugFP, "ics_type %d\n", ics_type);
3210                 continue;
3211             }
3212
3213             if (!loggedOn &&
3214                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3215                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3216                  looking_at(buf, &i, "will be \"*\""))) {
3217               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3218               continue;
3219             }
3220
3221             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3222               char buf[MSG_SIZ];
3223               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3224               DisplayIcsInteractionTitle(buf);
3225               have_set_title = TRUE;
3226             }
3227
3228             /* skip finger notes */
3229             if (started == STARTED_NONE &&
3230                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3231                  (buf[i] == '1' && buf[i+1] == '0')) &&
3232                 buf[i+2] == ':' && buf[i+3] == ' ') {
3233               started = STARTED_CHATTER;
3234               i += 3;
3235               continue;
3236             }
3237
3238             oldi = i;
3239             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3240             if(appData.seekGraph) {
3241                 if(soughtPending && MatchSoughtLine(buf+i)) {
3242                     i = strstr(buf+i, "rated") - buf;
3243                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3244                     next_out = leftover_start = i;
3245                     started = STARTED_CHATTER;
3246                     suppressKibitz = TRUE;
3247                     continue;
3248                 }
3249                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3250                         && looking_at(buf, &i, "* ads displayed")) {
3251                     soughtPending = FALSE;
3252                     seekGraphUp = TRUE;
3253                     DrawSeekGraph();
3254                     continue;
3255                 }
3256                 if(appData.autoRefresh) {
3257                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3258                         int s = (ics_type == ICS_ICC); // ICC format differs
3259                         if(seekGraphUp)
3260                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3261                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3262                         looking_at(buf, &i, "*% "); // eat prompt
3263                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3264                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3265                         next_out = i; // suppress
3266                         continue;
3267                     }
3268                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3269                         char *p = star_match[0];
3270                         while(*p) {
3271                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3272                             while(*p && *p++ != ' '); // next
3273                         }
3274                         looking_at(buf, &i, "*% "); // eat prompt
3275                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3276                         next_out = i;
3277                         continue;
3278                     }
3279                 }
3280             }
3281
3282             /* skip formula vars */
3283             if (started == STARTED_NONE &&
3284                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3285               started = STARTED_CHATTER;
3286               i += 3;
3287               continue;
3288             }
3289
3290             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3291             if (appData.autoKibitz && started == STARTED_NONE &&
3292                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3293                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3294                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3295                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3296                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3297                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3298                         suppressKibitz = TRUE;
3299                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3300                         next_out = i;
3301                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3302                                 && (gameMode == IcsPlayingWhite)) ||
3303                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3304                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3305                             started = STARTED_CHATTER; // own kibitz we simply discard
3306                         else {
3307                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3308                             parse_pos = 0; parse[0] = NULLCHAR;
3309                             savingComment = TRUE;
3310                             suppressKibitz = gameMode != IcsObserving ? 2 :
3311                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3312                         }
3313                         continue;
3314                 } else
3315                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3316                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3317                          && atoi(star_match[0])) {
3318                     // suppress the acknowledgements of our own autoKibitz
3319                     char *p;
3320                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3321                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3322                     SendToPlayer(star_match[0], strlen(star_match[0]));
3323                     if(looking_at(buf, &i, "*% ")) // eat prompt
3324                         suppressKibitz = FALSE;
3325                     next_out = i;
3326                     continue;
3327                 }
3328             } // [HGM] kibitz: end of patch
3329
3330             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3331
3332             // [HGM] chat: intercept tells by users for which we have an open chat window
3333             channel = -1;
3334             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3335                                            looking_at(buf, &i, "* whispers:") ||
3336                                            looking_at(buf, &i, "* kibitzes:") ||
3337                                            looking_at(buf, &i, "* shouts:") ||
3338                                            looking_at(buf, &i, "* c-shouts:") ||
3339                                            looking_at(buf, &i, "--> * ") ||
3340                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3341                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3342                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3343                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3344                 int p;
3345                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3346                 chattingPartner = -1; collective = 0;
3347
3348                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3349                 for(p=0; p<MAX_CHAT; p++) {
3350                     collective = 1;
3351                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3352                     talker[0] = '['; strcat(talker, "] ");
3353                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3354                     chattingPartner = p; break;
3355                     }
3356                 } else
3357                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3358                 for(p=0; p<MAX_CHAT; p++) {
3359                     collective = 1;
3360                     if(!strcmp("kibitzes", chatPartner[p])) {
3361                         talker[0] = '['; strcat(talker, "] ");
3362                         chattingPartner = p; break;
3363                     }
3364                 } else
3365                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3366                 for(p=0; p<MAX_CHAT; p++) {
3367                     collective = 1;
3368                     if(!strcmp("whispers", chatPartner[p])) {
3369                         talker[0] = '['; strcat(talker, "] ");
3370                         chattingPartner = p; break;
3371                     }
3372                 } else
3373                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3374                   if(buf[i-8] == '-' && buf[i-3] == 't')
3375                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3376                     collective = 1;
3377                     if(!strcmp("c-shouts", chatPartner[p])) {
3378                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3379                         chattingPartner = p; break;
3380                     }
3381                   }
3382                   if(chattingPartner < 0)
3383                   for(p=0; p<MAX_CHAT; p++) {
3384                     collective = 1;
3385                     if(!strcmp("shouts", chatPartner[p])) {
3386                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3387                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3388                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3389                         chattingPartner = p; break;
3390                     }
3391                   }
3392                 }
3393                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3394                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3395                     talker[0] = 0;
3396                     Colorize(ColorTell, FALSE);
3397                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3398                     collective |= 2;
3399                     chattingPartner = p; break;
3400                 }
3401                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3402                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3403                     started = STARTED_COMMENT;
3404                     parse_pos = 0; parse[0] = NULLCHAR;
3405                     savingComment = 3 + chattingPartner; // counts as TRUE
3406                     if(collective == 3) i = oldi; else {
3407                         suppressKibitz = TRUE;
3408                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3409                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3410                         continue;
3411                     }
3412                 }
3413             } // [HGM] chat: end of patch
3414
3415           backup = i;
3416             if (appData.zippyTalk || appData.zippyPlay) {
3417                 /* [DM] Backup address for color zippy lines */
3418 #if ZIPPY
3419                if (loggedOn == TRUE)
3420                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3421                           (appData.zippyPlay && ZippyMatch(buf, &backup)))
3422                        ;
3423 #endif
3424             } // [DM] 'else { ' deleted
3425                 if (
3426                     /* Regular tells and says */
3427                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3428                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3429                     looking_at(buf, &i, "* says: ") ||
3430                     /* Don't color "message" or "messages" output */
3431                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3432                     looking_at(buf, &i, "*. * at *:*: ") ||
3433                     looking_at(buf, &i, "--* (*:*): ") ||
3434                     /* Message notifications (same color as tells) */
3435                     looking_at(buf, &i, "* has left a message ") ||
3436                     looking_at(buf, &i, "* just sent you a message:\n") ||
3437                     /* Whispers and kibitzes */
3438                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3439                     looking_at(buf, &i, "* kibitzes: ") ||
3440                     /* Channel tells */
3441                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3442
3443                   if (tkind == 1 && strchr(star_match[0], ':')) {
3444                       /* Avoid "tells you:" spoofs in channels */
3445                      tkind = 3;
3446                   }
3447                   if (star_match[0][0] == NULLCHAR ||
3448                       strchr(star_match[0], ' ') ||
3449                       (tkind == 3 && strchr(star_match[1], ' '))) {
3450                     /* Reject bogus matches */
3451                     i = oldi;
3452                   } else {
3453                     if (appData.colorize) {
3454                       if (oldi > next_out) {
3455                         SendToPlayer(&buf[next_out], oldi - next_out);
3456                         next_out = oldi;
3457                       }
3458                       switch (tkind) {
3459                       case 1:
3460                         Colorize(ColorTell, FALSE);
3461                         curColor = ColorTell;
3462                         break;
3463                       case 2:
3464                         Colorize(ColorKibitz, FALSE);
3465                         curColor = ColorKibitz;
3466                         break;
3467                       case 3:
3468                         p = strrchr(star_match[1], '(');
3469                         if (p == NULL) {
3470                           p = star_match[1];
3471                         } else {
3472                           p++;
3473                         }
3474                         if (atoi(p) == 1) {
3475                           Colorize(ColorChannel1, FALSE);
3476                           curColor = ColorChannel1;
3477                         } else {
3478                           Colorize(ColorChannel, FALSE);
3479                           curColor = ColorChannel;
3480                         }
3481                         break;
3482                       case 5:
3483                         curColor = ColorNormal;
3484                         break;
3485                       }
3486                     }
3487                     if (started == STARTED_NONE && appData.autoComment &&
3488                         (gameMode == IcsObserving ||
3489                          gameMode == IcsPlayingWhite ||
3490                          gameMode == IcsPlayingBlack)) {
3491                       parse_pos = i - oldi;
3492                       memcpy(parse, &buf[oldi], parse_pos);
3493                       parse[parse_pos] = NULLCHAR;
3494                       started = STARTED_COMMENT;
3495                       savingComment = TRUE;
3496                     } else if(collective != 3) {
3497                       started = STARTED_CHATTER;
3498                       savingComment = FALSE;
3499                     }
3500                     loggedOn = TRUE;
3501                     continue;
3502                   }
3503                 }
3504
3505                 if (looking_at(buf, &i, "* s-shouts: ") ||
3506                     looking_at(buf, &i, "* c-shouts: ")) {
3507                     if (appData.colorize) {
3508                         if (oldi > next_out) {
3509                             SendToPlayer(&buf[next_out], oldi - next_out);
3510                             next_out = oldi;
3511                         }
3512                         Colorize(ColorSShout, FALSE);
3513                         curColor = ColorSShout;
3514                     }
3515                     loggedOn = TRUE;
3516                     started = STARTED_CHATTER;
3517                     continue;
3518                 }
3519
3520                 if (looking_at(buf, &i, "--->")) {
3521                     loggedOn = TRUE;
3522                     continue;
3523                 }
3524
3525                 if (looking_at(buf, &i, "* shouts: ") ||
3526                     looking_at(buf, &i, "--> ")) {
3527                     if (appData.colorize) {
3528                         if (oldi > next_out) {
3529                             SendToPlayer(&buf[next_out], oldi - next_out);
3530                             next_out = oldi;
3531                         }
3532                         Colorize(ColorShout, FALSE);
3533                         curColor = ColorShout;
3534                     }
3535                     loggedOn = TRUE;
3536                     started = STARTED_CHATTER;
3537                     continue;
3538                 }
3539
3540                 if (looking_at( buf, &i, "Challenge:")) {
3541                     if (appData.colorize) {
3542                         if (oldi > next_out) {
3543                             SendToPlayer(&buf[next_out], oldi - next_out);
3544                             next_out = oldi;
3545                         }
3546                         Colorize(ColorChallenge, FALSE);
3547                         curColor = ColorChallenge;
3548                     }
3549                     loggedOn = TRUE;
3550                     continue;
3551                 }
3552
3553                 if (looking_at(buf, &i, "* offers you") ||
3554                     looking_at(buf, &i, "* offers to be") ||
3555                     looking_at(buf, &i, "* would like to") ||
3556                     looking_at(buf, &i, "* requests to") ||
3557                     looking_at(buf, &i, "Your opponent offers") ||
3558                     looking_at(buf, &i, "Your opponent requests")) {
3559
3560                     if (appData.colorize) {
3561                         if (oldi > next_out) {
3562                             SendToPlayer(&buf[next_out], oldi - next_out);
3563                             next_out = oldi;
3564                         }
3565                         Colorize(ColorRequest, FALSE);
3566                         curColor = ColorRequest;
3567                     }
3568                     continue;
3569                 }
3570
3571                 if (looking_at(buf, &i, "* (*) seeking")) {
3572                     if (appData.colorize) {
3573                         if (oldi > next_out) {
3574                             SendToPlayer(&buf[next_out], oldi - next_out);
3575                             next_out = oldi;
3576                         }
3577                         Colorize(ColorSeek, FALSE);
3578                         curColor = ColorSeek;
3579                     }
3580                     continue;
3581             }
3582
3583           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3584
3585             if (looking_at(buf, &i, "\\   ")) {
3586                 if (prevColor != ColorNormal) {
3587                     if (oldi > next_out) {
3588                         SendToPlayer(&buf[next_out], oldi - next_out);
3589                         next_out = oldi;
3590                     }
3591                     Colorize(prevColor, TRUE);
3592                     curColor = prevColor;
3593                 }
3594                 if (savingComment) {
3595                     parse_pos = i - oldi;
3596                     memcpy(parse, &buf[oldi], parse_pos);
3597                     parse[parse_pos] = NULLCHAR;
3598                     started = STARTED_COMMENT;
3599                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3600                         chattingPartner = savingComment - 3; // kludge to remember the box
3601                 } else {
3602                     started = STARTED_CHATTER;
3603                 }
3604                 continue;
3605             }
3606
3607             if (looking_at(buf, &i, "Black Strength :") ||
3608                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3609                 looking_at(buf, &i, "<10>") ||
3610                 looking_at(buf, &i, "#@#")) {
3611                 /* Wrong board style */
3612                 loggedOn = TRUE;
3613                 SendToICS(ics_prefix);
3614                 SendToICS("set style 12\n");
3615                 SendToICS(ics_prefix);
3616                 SendToICS("refresh\n");
3617                 continue;
3618             }
3619
3620             if (looking_at(buf, &i, "login:")) {
3621               if (!have_sent_ICS_logon) {
3622                 if(ICSInitScript())
3623                   have_sent_ICS_logon = 1;
3624                 else // no init script was found
3625                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3626               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3627                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3628               }
3629                 continue;
3630             }
3631
3632             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3633                 (looking_at(buf, &i, "\n<12> ") ||
3634                  looking_at(buf, &i, "<12> "))) {
3635                 loggedOn = TRUE;
3636                 if (oldi > next_out) {
3637                     SendToPlayer(&buf[next_out], oldi - next_out);
3638                 }
3639                 next_out = i;
3640                 started = STARTED_BOARD;
3641                 parse_pos = 0;
3642                 continue;
3643             }
3644
3645             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3646                 looking_at(buf, &i, "<b1> ")) {
3647                 if (oldi > next_out) {
3648                     SendToPlayer(&buf[next_out], oldi - next_out);
3649                 }
3650                 next_out = i;
3651                 started = STARTED_HOLDINGS;
3652                 parse_pos = 0;
3653                 continue;
3654             }
3655
3656             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3657                 loggedOn = TRUE;
3658                 /* Header for a move list -- first line */
3659
3660                 switch (ics_getting_history) {
3661                   case H_FALSE:
3662                     switch (gameMode) {
3663                       case IcsIdle:
3664                       case BeginningOfGame:
3665                         /* User typed "moves" or "oldmoves" while we
3666                            were idle.  Pretend we asked for these
3667                            moves and soak them up so user can step
3668                            through them and/or save them.
3669                            */
3670                         Reset(FALSE, TRUE);
3671                         gameMode = IcsObserving;
3672                         ModeHighlight();
3673                         ics_gamenum = -1;
3674                         ics_getting_history = H_GOT_UNREQ_HEADER;
3675                         break;
3676                       case EditGame: /*?*/
3677                       case EditPosition: /*?*/
3678                         /* Should above feature work in these modes too? */
3679                         /* For now it doesn't */
3680                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3681                         break;
3682                       default:
3683                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3684                         break;
3685                     }
3686                     break;
3687                   case H_REQUESTED:
3688                     /* Is this the right one? */
3689                     if (gameInfo.white && gameInfo.black &&
3690                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3691                         strcmp(gameInfo.black, star_match[2]) == 0) {
3692                         /* All is well */
3693                         ics_getting_history = H_GOT_REQ_HEADER;
3694                     }
3695                     break;
3696                   case H_GOT_REQ_HEADER:
3697                   case H_GOT_UNREQ_HEADER:
3698                   case H_GOT_UNWANTED_HEADER:
3699                   case H_GETTING_MOVES:
3700                     /* Should not happen */
3701                     DisplayError(_("Error gathering move list: two headers"), 0);
3702                     ics_getting_history = H_FALSE;
3703                     break;
3704                 }
3705
3706                 /* Save player ratings into gameInfo if needed */
3707                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3708                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3709                     (gameInfo.whiteRating == -1 ||
3710                      gameInfo.blackRating == -1)) {
3711
3712                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3713                     gameInfo.blackRating = string_to_rating(star_match[3]);
3714                     if (appData.debugMode)
3715                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3716                               gameInfo.whiteRating, gameInfo.blackRating);
3717                 }
3718                 continue;
3719             }
3720
3721             if (looking_at(buf, &i,
3722               "* * match, initial time: * minute*, increment: * second")) {
3723                 /* Header for a move list -- second line */
3724                 /* Initial board will follow if this is a wild game */
3725                 if (gameInfo.event != NULL) free(gameInfo.event);
3726                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3727                 gameInfo.event = StrSave(str);
3728                 /* [HGM] we switched variant. Translate boards if needed. */
3729                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3730                 continue;
3731             }
3732
3733             if (looking_at(buf, &i, "Move  ")) {
3734                 /* Beginning of a move list */
3735                 switch (ics_getting_history) {
3736                   case H_FALSE:
3737                     /* Normally should not happen */
3738                     /* Maybe user hit reset while we were parsing */
3739                     break;
3740                   case H_REQUESTED:
3741                     /* Happens if we are ignoring a move list that is not
3742                      * the one we just requested.  Common if the user
3743                      * tries to observe two games without turning off
3744                      * getMoveList */
3745                     break;
3746                   case H_GETTING_MOVES:
3747                     /* Should not happen */
3748                     DisplayError(_("Error gathering move list: nested"), 0);
3749                     ics_getting_history = H_FALSE;
3750                     break;
3751                   case H_GOT_REQ_HEADER:
3752                     ics_getting_history = H_GETTING_MOVES;
3753                     started = STARTED_MOVES;
3754                     parse_pos = 0;
3755                     if (oldi > next_out) {
3756                         SendToPlayer(&buf[next_out], oldi - next_out);
3757                     }
3758                     break;
3759                   case H_GOT_UNREQ_HEADER:
3760                     ics_getting_history = H_GETTING_MOVES;
3761                     started = STARTED_MOVES_NOHIDE;
3762                     parse_pos = 0;
3763                     break;
3764                   case H_GOT_UNWANTED_HEADER:
3765                     ics_getting_history = H_FALSE;
3766                     break;
3767                 }
3768                 continue;
3769             }
3770
3771             if (looking_at(buf, &i, "% ") ||
3772                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3773                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3774                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3775                     soughtPending = FALSE;
3776                     seekGraphUp = TRUE;
3777                     DrawSeekGraph();
3778                 }
3779                 if(suppressKibitz) next_out = i;
3780                 savingComment = FALSE;
3781                 suppressKibitz = 0;
3782                 switch (started) {
3783                   case STARTED_MOVES:
3784                   case STARTED_MOVES_NOHIDE:
3785                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3786                     parse[parse_pos + i - oldi] = NULLCHAR;
3787                     ParseGameHistory(parse);
3788 #if ZIPPY
3789                     if (appData.zippyPlay && first.initDone) {
3790                         FeedMovesToProgram(&first, forwardMostMove);
3791                         if (gameMode == IcsPlayingWhite) {
3792                             if (WhiteOnMove(forwardMostMove)) {
3793                                 if (first.sendTime) {
3794                                   if (first.useColors) {
3795                                     SendToProgram("black\n", &first);
3796                                   }
3797                                   SendTimeRemaining(&first, TRUE);
3798                                 }
3799                                 if (first.useColors) {
3800                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3801                                 }
3802                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3803                                 first.maybeThinking = TRUE;
3804                             } else {
3805                                 if (first.usePlayother) {
3806                                   if (first.sendTime) {
3807                                     SendTimeRemaining(&first, TRUE);
3808                                   }
3809                                   SendToProgram("playother\n", &first);
3810                                   firstMove = FALSE;
3811                                 } else {
3812                                   firstMove = TRUE;
3813                                 }
3814                             }
3815                         } else if (gameMode == IcsPlayingBlack) {
3816                             if (!WhiteOnMove(forwardMostMove)) {
3817                                 if (first.sendTime) {
3818                                   if (first.useColors) {
3819                                     SendToProgram("white\n", &first);
3820                                   }
3821                                   SendTimeRemaining(&first, FALSE);
3822                                 }
3823                                 if (first.useColors) {
3824                                   SendToProgram("black\n", &first);
3825                                 }
3826                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3827                                 first.maybeThinking = TRUE;
3828                             } else {
3829                                 if (first.usePlayother) {
3830                                   if (first.sendTime) {
3831                                     SendTimeRemaining(&first, FALSE);
3832                                   }
3833                                   SendToProgram("playother\n", &first);
3834                                   firstMove = FALSE;
3835                                 } else {
3836                                   firstMove = TRUE;
3837                                 }
3838                             }
3839                         }
3840                     }
3841 #endif
3842                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3843                         /* Moves came from oldmoves or moves command
3844                            while we weren't doing anything else.
3845                            */
3846                         currentMove = forwardMostMove;
3847                         ClearHighlights();/*!!could figure this out*/
3848                         flipView = appData.flipView;
3849                         DrawPosition(TRUE, boards[currentMove]);
3850                         DisplayBothClocks();
3851                         snprintf(str, MSG_SIZ, "%s %s %s",
3852                                 gameInfo.white, _("vs."),  gameInfo.black);
3853                         DisplayTitle(str);
3854                         gameMode = IcsIdle;
3855                     } else {
3856                         /* Moves were history of an active game */
3857                         if (gameInfo.resultDetails != NULL) {
3858                             free(gameInfo.resultDetails);
3859                             gameInfo.resultDetails = NULL;
3860                         }
3861                     }
3862                     HistorySet(parseList, backwardMostMove,
3863                                forwardMostMove, currentMove-1);
3864                     DisplayMove(currentMove - 1);
3865                     if (started == STARTED_MOVES) next_out = i;
3866                     started = STARTED_NONE;
3867                     ics_getting_history = H_FALSE;
3868                     break;
3869
3870                   case STARTED_OBSERVE:
3871                     started = STARTED_NONE;
3872                     SendToICS(ics_prefix);
3873                     SendToICS("refresh\n");
3874                     break;
3875
3876                   default:
3877                     break;
3878                 }
3879                 if(bookHit) { // [HGM] book: simulate book reply
3880                     static char bookMove[MSG_SIZ]; // a bit generous?
3881
3882                     programStats.nodes = programStats.depth = programStats.time =
3883                     programStats.score = programStats.got_only_move = 0;
3884                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3885
3886                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3887                     strcat(bookMove, bookHit);
3888                     HandleMachineMove(bookMove, &first);
3889                 }
3890                 continue;
3891             }
3892
3893             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3894                  started == STARTED_HOLDINGS ||
3895                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3896                 /* Accumulate characters in move list or board */
3897                 parse[parse_pos++] = buf[i];
3898             }
3899
3900             /* Start of game messages.  Mostly we detect start of game
3901                when the first board image arrives.  On some versions
3902                of the ICS, though, we need to do a "refresh" after starting
3903                to observe in order to get the current board right away. */
3904             if (looking_at(buf, &i, "Adding game * to observation list")) {
3905                 started = STARTED_OBSERVE;
3906                 continue;
3907             }
3908
3909             /* Handle auto-observe */
3910             if (appData.autoObserve &&
3911                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3912                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3913                 char *player;
3914                 /* Choose the player that was highlighted, if any. */
3915                 if (star_match[0][0] == '\033' ||
3916                     star_match[1][0] != '\033') {
3917                     player = star_match[0];
3918                 } else {
3919                     player = star_match[2];
3920                 }
3921                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3922                         ics_prefix, StripHighlightAndTitle(player));
3923                 SendToICS(str);
3924
3925                 /* Save ratings from notify string */
3926                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3927                 player1Rating = string_to_rating(star_match[1]);
3928                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3929                 player2Rating = string_to_rating(star_match[3]);
3930
3931                 if (appData.debugMode)
3932                   fprintf(debugFP,
3933                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3934                           player1Name, player1Rating,
3935                           player2Name, player2Rating);
3936
3937                 continue;
3938             }
3939
3940             /* Deal with automatic examine mode after a game,
3941                and with IcsObserving -> IcsExamining transition */
3942             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3943                 looking_at(buf, &i, "has made you an examiner of game *")) {
3944
3945                 int gamenum = atoi(star_match[0]);
3946                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3947                     gamenum == ics_gamenum) {
3948                     /* We were already playing or observing this game;
3949                        no need to refetch history */
3950                     gameMode = IcsExamining;
3951                     if (pausing) {
3952                         pauseExamForwardMostMove = forwardMostMove;
3953                     } else if (currentMove < forwardMostMove) {
3954                         ForwardInner(forwardMostMove);
3955                     }
3956                 } else {
3957                     /* I don't think this case really can happen */
3958                     SendToICS(ics_prefix);
3959                     SendToICS("refresh\n");
3960                 }
3961                 continue;
3962             }
3963
3964             /* Error messages */
3965 //          if (ics_user_moved) {
3966             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3967                 if (looking_at(buf, &i, "Illegal move") ||
3968                     looking_at(buf, &i, "Not a legal move") ||
3969                     looking_at(buf, &i, "Your king is in check") ||
3970                     looking_at(buf, &i, "It isn't your turn") ||
3971                     looking_at(buf, &i, "It is not your move")) {
3972                     /* Illegal move */
3973                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3974                         currentMove = forwardMostMove-1;
3975                         DisplayMove(currentMove - 1); /* before DMError */
3976                         DrawPosition(FALSE, boards[currentMove]);
3977                         SwitchClocks(forwardMostMove-1); // [HGM] race
3978                         DisplayBothClocks();
3979                     }
3980                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3981                     ics_user_moved = 0;
3982                     continue;
3983                 }
3984             }
3985
3986             if (looking_at(buf, &i, "still have time") ||
3987                 looking_at(buf, &i, "not out of time") ||
3988                 looking_at(buf, &i, "either player is out of time") ||
3989                 looking_at(buf, &i, "has timeseal; checking")) {
3990                 /* We must have called his flag a little too soon */
3991                 whiteFlag = blackFlag = FALSE;
3992                 continue;
3993             }
3994
3995             if (looking_at(buf, &i, "added * seconds to") ||
3996                 looking_at(buf, &i, "seconds were added to")) {
3997                 /* Update the clocks */
3998                 SendToICS(ics_prefix);
3999                 SendToICS("refresh\n");
4000                 continue;
4001             }
4002
4003             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
4004                 ics_clock_paused = TRUE;
4005                 StopClocks();
4006                 continue;
4007             }
4008
4009             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
4010                 ics_clock_paused = FALSE;
4011                 StartClocks();
4012                 continue;
4013             }
4014
4015             /* Grab player ratings from the Creating: message.
4016                Note we have to check for the special case when
4017                the ICS inserts things like [white] or [black]. */
4018             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
4019                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
4020                 /* star_matches:
4021                    0    player 1 name (not necessarily white)
4022                    1    player 1 rating
4023                    2    empty, white, or black (IGNORED)
4024                    3    player 2 name (not necessarily black)
4025                    4    player 2 rating
4026
4027                    The names/ratings are sorted out when the game
4028                    actually starts (below).
4029                 */
4030                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4031                 player1Rating = string_to_rating(star_match[1]);
4032                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4033                 player2Rating = string_to_rating(star_match[4]);
4034
4035                 if (appData.debugMode)
4036                   fprintf(debugFP,
4037                           "Ratings from 'Creating:' %s %d, %s %d\n",
4038                           player1Name, player1Rating,
4039                           player2Name, player2Rating);
4040
4041                 continue;
4042             }
4043
4044             /* Improved generic start/end-of-game messages */
4045             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4046                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4047                 /* If tkind == 0: */
4048                 /* star_match[0] is the game number */
4049                 /*           [1] is the white player's name */
4050                 /*           [2] is the black player's name */
4051                 /* For end-of-game: */
4052                 /*           [3] is the reason for the game end */
4053                 /*           [4] is a PGN end game-token, preceded by " " */
4054                 /* For start-of-game: */
4055                 /*           [3] begins with "Creating" or "Continuing" */
4056                 /*           [4] is " *" or empty (don't care). */
4057                 int gamenum = atoi(star_match[0]);
4058                 char *whitename, *blackname, *why, *endtoken;
4059                 ChessMove endtype = EndOfFile;
4060
4061                 if (tkind == 0) {
4062                   whitename = star_match[1];
4063                   blackname = star_match[2];
4064                   why = star_match[3];
4065                   endtoken = star_match[4];
4066                 } else {
4067                   whitename = star_match[1];
4068                   blackname = star_match[3];
4069                   why = star_match[5];
4070                   endtoken = star_match[6];
4071                 }
4072
4073                 /* Game start messages */
4074                 if (strncmp(why, "Creating ", 9) == 0 ||
4075                     strncmp(why, "Continuing ", 11) == 0) {
4076                     gs_gamenum = gamenum;
4077                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4078                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4079                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4080 #if ZIPPY
4081                     if (appData.zippyPlay) {
4082                         ZippyGameStart(whitename, blackname);
4083                     }
4084 #endif /*ZIPPY*/
4085                     partnerBoardValid = FALSE; // [HGM] bughouse
4086                     continue;
4087                 }
4088
4089                 /* Game end messages */
4090                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4091                     ics_gamenum != gamenum) {
4092                     continue;
4093                 }
4094                 while (endtoken[0] == ' ') endtoken++;
4095                 switch (endtoken[0]) {
4096                   case '*':
4097                   default:
4098                     endtype = GameUnfinished;
4099                     break;
4100                   case '0':
4101                     endtype = BlackWins;
4102                     break;
4103                   case '1':
4104                     if (endtoken[1] == '/')
4105                       endtype = GameIsDrawn;
4106                     else
4107                       endtype = WhiteWins;
4108                     break;
4109                 }
4110                 GameEnds(endtype, why, GE_ICS);
4111 #if ZIPPY
4112                 if (appData.zippyPlay && first.initDone) {
4113                     ZippyGameEnd(endtype, why);
4114                     if (first.pr == NoProc) {
4115                       /* Start the next process early so that we'll
4116                          be ready for the next challenge */
4117                       StartChessProgram(&first);
4118                     }
4119                     /* Send "new" early, in case this command takes
4120                        a long time to finish, so that we'll be ready
4121                        for the next challenge. */
4122                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4123                     Reset(TRUE, TRUE);
4124                 }
4125 #endif /*ZIPPY*/
4126                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4127                 continue;
4128             }
4129
4130             if (looking_at(buf, &i, "Removing game * from observation") ||
4131                 looking_at(buf, &i, "no longer observing game *") ||
4132                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4133                 if (gameMode == IcsObserving &&
4134                     atoi(star_match[0]) == ics_gamenum)
4135                   {
4136                       /* icsEngineAnalyze */
4137                       if (appData.icsEngineAnalyze) {
4138                             ExitAnalyzeMode();
4139                             ModeHighlight();
4140                       }
4141                       StopClocks();
4142                       gameMode = IcsIdle;
4143                       ics_gamenum = -1;
4144                       ics_user_moved = FALSE;
4145                   }
4146                 continue;
4147             }
4148
4149             if (looking_at(buf, &i, "no longer examining game *")) {
4150                 if (gameMode == IcsExamining &&
4151                     atoi(star_match[0]) == ics_gamenum)
4152                   {
4153                       gameMode = IcsIdle;
4154                       ics_gamenum = -1;
4155                       ics_user_moved = FALSE;
4156                   }
4157                 continue;
4158             }
4159
4160             /* Advance leftover_start past any newlines we find,
4161                so only partial lines can get reparsed */
4162             if (looking_at(buf, &i, "\n")) {
4163                 prevColor = curColor;
4164                 if (curColor != ColorNormal) {
4165                     if (oldi > next_out) {
4166                         SendToPlayer(&buf[next_out], oldi - next_out);
4167                         next_out = oldi;
4168                     }
4169                     Colorize(ColorNormal, FALSE);
4170                     curColor = ColorNormal;
4171                 }
4172                 if (started == STARTED_BOARD) {
4173                     started = STARTED_NONE;
4174                     parse[parse_pos] = NULLCHAR;
4175                     ParseBoard12(parse);
4176                     ics_user_moved = 0;
4177
4178                     /* Send premove here */
4179                     if (appData.premove) {
4180                       char str[MSG_SIZ];
4181                       if (currentMove == 0 &&
4182                           gameMode == IcsPlayingWhite &&
4183                           appData.premoveWhite) {
4184                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4185                         if (appData.debugMode)
4186                           fprintf(debugFP, "Sending premove:\n");
4187                         SendToICS(str);
4188                       } else if (currentMove == 1 &&
4189                                  gameMode == IcsPlayingBlack &&
4190                                  appData.premoveBlack) {
4191                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4192                         if (appData.debugMode)
4193                           fprintf(debugFP, "Sending premove:\n");
4194                         SendToICS(str);
4195                       } else if (gotPremove) {
4196                         int oldFMM = forwardMostMove;
4197                         gotPremove = 0;
4198                         ClearPremoveHighlights();
4199                         if (appData.debugMode)
4200                           fprintf(debugFP, "Sending premove:\n");
4201                           UserMoveEvent(premoveFromX, premoveFromY,
4202                                         premoveToX, premoveToY,
4203                                         premovePromoChar);
4204                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4205                           if(moveList[oldFMM-1][1] != '@')
4206                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4207                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4208                           else // (drop)
4209                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4210                         }
4211                       }
4212                     }
4213
4214                     /* Usually suppress following prompt */
4215                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4216                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4217                         if (looking_at(buf, &i, "*% ")) {
4218                             savingComment = FALSE;
4219                             suppressKibitz = 0;
4220                         }
4221                     }
4222                     next_out = i;
4223                 } else if (started == STARTED_HOLDINGS) {
4224                     int gamenum;
4225                     char new_piece[MSG_SIZ];
4226                     started = STARTED_NONE;
4227                     parse[parse_pos] = NULLCHAR;
4228                     if (appData.debugMode)
4229                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4230                                                         parse, currentMove);
4231                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4232                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4233                         new_piece[0] = NULLCHAR;
4234                         sscanf(parse, "game %d white [%s black [%s <- %s",
4235                                &gamenum, white_holding, black_holding,
4236                                new_piece);
4237                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4238                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4239                         if (gameInfo.variant == VariantNormal) {
4240                           /* [HGM] We seem to switch variant during a game!
4241                            * Presumably no holdings were displayed, so we have
4242                            * to move the position two files to the right to
4243                            * create room for them!
4244                            */
4245                           VariantClass newVariant;
4246                           switch(gameInfo.boardWidth) { // base guess on board width
4247                                 case 9:  newVariant = VariantShogi; break;
4248                                 case 10: newVariant = VariantGreat; break;
4249                                 default: newVariant = VariantCrazyhouse;
4250                                      if(strchr(white_holding, 'E') || strchr(black_holding, 'E') || 
4251                                         strchr(white_holding, 'H') || strchr(black_holding, 'H')   )
4252                                          newVariant = VariantSChess;
4253                           }
4254                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4255                           /* Get a move list just to see the header, which
4256                              will tell us whether this is really bug or zh */
4257                           if (ics_getting_history == H_FALSE) {
4258                             ics_getting_history = H_REQUESTED;
4259                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4260                             SendToICS(str);
4261                           }
4262                         }
4263                         /* [HGM] copy holdings to board holdings area */
4264                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4265                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4266                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4267 #if ZIPPY
4268                         if (appData.zippyPlay && first.initDone) {
4269                             ZippyHoldings(white_holding, black_holding,
4270                                           new_piece);
4271                         }
4272 #endif /*ZIPPY*/
4273                         if (tinyLayout || smallLayout) {
4274                             char wh[16], bh[16];
4275                             PackHolding(wh, white_holding);
4276                             PackHolding(bh, black_holding);
4277                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4278                                     gameInfo.white, gameInfo.black);
4279                         } else {
4280                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4281                                     gameInfo.white, white_holding, _("vs."),
4282                                     gameInfo.black, black_holding);
4283                         }
4284                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4285                         DrawPosition(FALSE, boards[currentMove]);
4286                         DisplayTitle(str);
4287                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4288                         sscanf(parse, "game %d white [%s black [%s <- %s",
4289                                &gamenum, white_holding, black_holding,
4290                                new_piece);
4291                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4292                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4293                         /* [HGM] copy holdings to partner-board holdings area */
4294                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4295                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4296                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4297                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4298                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4299                       }
4300                     }
4301                     /* Suppress following prompt */
4302                     if (looking_at(buf, &i, "*% ")) {
4303                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4304                         savingComment = FALSE;
4305                         suppressKibitz = 0;
4306                     }
4307                     next_out = i;
4308                 }
4309                 continue;
4310             }
4311
4312             i++;                /* skip unparsed character and loop back */
4313         }
4314
4315         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4316 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4317 //          SendToPlayer(&buf[next_out], i - next_out);
4318             started != STARTED_HOLDINGS && leftover_start > next_out) {
4319             SendToPlayer(&buf[next_out], leftover_start - next_out);
4320             next_out = i;
4321         }
4322
4323         leftover_len = buf_len - leftover_start;
4324         /* if buffer ends with something we couldn't parse,
4325            reparse it after appending the next read */
4326
4327     } else if (count == 0) {
4328         RemoveInputSource(isr);
4329         DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4330     } else {
4331         DisplayFatalError(_("Error reading from ICS"), error, 1);
4332     }
4333 }
4334
4335
4336 /* Board style 12 looks like this:
4337
4338    <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
4339
4340  * The "<12> " is stripped before it gets to this routine.  The two
4341  * trailing 0's (flip state and clock ticking) are later addition, and
4342  * some chess servers may not have them, or may have only the first.
4343  * Additional trailing fields may be added in the future.
4344  */
4345
4346 #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"
4347
4348 #define RELATION_OBSERVING_PLAYED    0
4349 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4350 #define RELATION_PLAYING_MYMOVE      1
4351 #define RELATION_PLAYING_NOTMYMOVE  -1
4352 #define RELATION_EXAMINING           2
4353 #define RELATION_ISOLATED_BOARD     -3
4354 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4355
4356 void
4357 ParseBoard12 (char *string)
4358 {
4359 #if ZIPPY
4360     int i, takeback;
4361     char *bookHit = NULL; // [HGM] book
4362 #endif
4363     GameMode newGameMode;
4364     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4365     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4366     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4367     char to_play, board_chars[200];
4368     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4369     char black[32], white[32];
4370     Board board;
4371     int prevMove = currentMove;
4372     int ticking = 2;
4373     ChessMove moveType;
4374     int fromX, fromY, toX, toY;
4375     char promoChar;
4376     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4377     Boolean weird = FALSE, reqFlag = FALSE;
4378
4379     fromX = fromY = toX = toY = -1;
4380
4381     newGame = FALSE;
4382
4383     if (appData.debugMode)
4384       fprintf(debugFP, "Parsing board: %s\n", string);
4385
4386     move_str[0] = NULLCHAR;
4387     elapsed_time[0] = NULLCHAR;
4388     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4389         int  i = 0, j;
4390         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4391             if(string[i] == ' ') { ranks++; files = 0; }
4392             else files++;
4393             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4394             i++;
4395         }
4396         for(j = 0; j <i; j++) board_chars[j] = string[j];
4397         board_chars[i] = '\0';
4398         string += i + 1;
4399     }
4400     n = sscanf(string, PATTERN, &to_play, &double_push,
4401                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4402                &gamenum, white, black, &relation, &basetime, &increment,
4403                &white_stren, &black_stren, &white_time, &black_time,
4404                &moveNum, str, elapsed_time, move_str, &ics_flip,
4405                &ticking);
4406
4407     if (n < 21) {
4408         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4409         DisplayError(str, 0);
4410         return;
4411     }
4412
4413     /* Convert the move number to internal form */
4414     moveNum = (moveNum - 1) * 2;
4415     if (to_play == 'B') moveNum++;
4416     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4417       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4418                         0, 1);
4419       return;
4420     }
4421
4422     switch (relation) {
4423       case RELATION_OBSERVING_PLAYED:
4424       case RELATION_OBSERVING_STATIC:
4425         if (gamenum == -1) {
4426             /* Old ICC buglet */
4427             relation = RELATION_OBSERVING_STATIC;
4428         }
4429         newGameMode = IcsObserving;
4430         break;
4431       case RELATION_PLAYING_MYMOVE:
4432       case RELATION_PLAYING_NOTMYMOVE:
4433         newGameMode =
4434           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4435             IcsPlayingWhite : IcsPlayingBlack;
4436         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4437         break;
4438       case RELATION_EXAMINING:
4439         newGameMode = IcsExamining;
4440         break;
4441       case RELATION_ISOLATED_BOARD:
4442       default:
4443         /* Just display this board.  If user was doing something else,
4444            we will forget about it until the next board comes. */
4445         newGameMode = IcsIdle;
4446         break;
4447       case RELATION_STARTING_POSITION:
4448         newGameMode = gameMode;
4449         break;
4450     }
4451
4452     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4453         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4454          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4455       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4456       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4457       static int lastBgGame = -1;
4458       char *toSqr;
4459       for (k = 0; k < ranks; k++) {
4460         for (j = 0; j < files; j++)
4461           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4462         if(gameInfo.holdingsWidth > 1) {
4463              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4464              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4465         }
4466       }
4467       CopyBoard(partnerBoard, board);
4468       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4469         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4470         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4471       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4472       if(toSqr = strchr(str, '-')) {
4473         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4474         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4475       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4476       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4477       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4478       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4479       if(twoBoards) {
4480           DisplayWhiteClock(white_time*fac, to_play == 'W');
4481           DisplayBlackClock(black_time*fac, to_play != 'W');
4482           activePartner = to_play;
4483           if(gamenum != lastBgGame) {
4484               char buf[MSG_SIZ];
4485               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4486               DisplayTitle(buf);
4487           }
4488           lastBgGame = gamenum;
4489           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4490                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4491       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4492                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4493       if(!twoBoards) DisplayMessage(partnerStatus, "");
4494         partnerBoardValid = TRUE;
4495       return;
4496     }
4497
4498     if(appData.dualBoard && appData.bgObserve) {
4499         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4500             SendToICS(ics_prefix), SendToICS("pobserve\n");
4501         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4502             char buf[MSG_SIZ];
4503             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4504             SendToICS(buf);
4505         }
4506     }
4507
4508     /* Modify behavior for initial board display on move listing
4509        of wild games.
4510        */
4511     switch (ics_getting_history) {
4512       case H_FALSE:
4513       case H_REQUESTED:
4514         break;
4515       case H_GOT_REQ_HEADER:
4516       case H_GOT_UNREQ_HEADER:
4517         /* This is the initial position of the current game */
4518         gamenum = ics_gamenum;
4519         moveNum = 0;            /* old ICS bug workaround */
4520         if (to_play == 'B') {
4521           startedFromSetupPosition = TRUE;
4522           blackPlaysFirst = TRUE;
4523           moveNum = 1;
4524           if (forwardMostMove == 0) forwardMostMove = 1;
4525           if (backwardMostMove == 0) backwardMostMove = 1;
4526           if (currentMove == 0) currentMove = 1;
4527         }
4528         newGameMode = gameMode;
4529         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4530         break;
4531       case H_GOT_UNWANTED_HEADER:
4532         /* This is an initial board that we don't want */
4533         return;
4534       case H_GETTING_MOVES:
4535         /* Should not happen */
4536         DisplayError(_("Error gathering move list: extra board"), 0);
4537         ics_getting_history = H_FALSE;
4538         return;
4539     }
4540
4541    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4542                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4543                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4544      /* [HGM] We seem to have switched variant unexpectedly
4545       * Try to guess new variant from board size
4546       */
4547           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4548           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4549           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4550           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4551           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4552           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4553           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4554           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4555           /* Get a move list just to see the header, which
4556              will tell us whether this is really bug or zh */
4557           if (ics_getting_history == H_FALSE) {
4558             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4559             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4560             SendToICS(str);
4561           }
4562     }
4563
4564     /* Take action if this is the first board of a new game, or of a
4565        different game than is currently being displayed.  */
4566     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4567         relation == RELATION_ISOLATED_BOARD) {
4568
4569         /* Forget the old game and get the history (if any) of the new one */
4570         if (gameMode != BeginningOfGame) {
4571           Reset(TRUE, TRUE);
4572         }
4573         newGame = TRUE;
4574         if (appData.autoRaiseBoard) BoardToTop();
4575         prevMove = -3;
4576         if (gamenum == -1) {
4577             newGameMode = IcsIdle;
4578         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4579                    appData.getMoveList && !reqFlag) {
4580             /* Need to get game history */
4581             ics_getting_history = H_REQUESTED;
4582             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4583             SendToICS(str);
4584         }
4585
4586         /* Initially flip the board to have black on the bottom if playing
4587            black or if the ICS flip flag is set, but let the user change
4588            it with the Flip View button. */
4589         flipView = appData.autoFlipView ?
4590           (newGameMode == IcsPlayingBlack) || ics_flip :
4591           appData.flipView;
4592
4593         /* Done with values from previous mode; copy in new ones */
4594         gameMode = newGameMode;
4595         ModeHighlight();
4596         ics_gamenum = gamenum;
4597         if (gamenum == gs_gamenum) {
4598             int klen = strlen(gs_kind);
4599             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4600             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4601             gameInfo.event = StrSave(str);
4602         } else {
4603             gameInfo.event = StrSave("ICS game");
4604         }
4605         gameInfo.site = StrSave(appData.icsHost);
4606         gameInfo.date = PGNDate();
4607         gameInfo.round = StrSave("-");
4608         gameInfo.white = StrSave(white);
4609         gameInfo.black = StrSave(black);
4610         timeControl = basetime * 60 * 1000;
4611         timeControl_2 = 0;
4612         timeIncrement = increment * 1000;
4613         movesPerSession = 0;
4614         gameInfo.timeControl = TimeControlTagValue();
4615         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4616   if (appData.debugMode) {
4617     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4618     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4619     setbuf(debugFP, NULL);
4620   }
4621
4622         gameInfo.outOfBook = NULL;
4623
4624         /* Do we have the ratings? */
4625         if (strcmp(player1Name, white) == 0 &&
4626             strcmp(player2Name, black) == 0) {
4627             if (appData.debugMode)
4628               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4629                       player1Rating, player2Rating);
4630             gameInfo.whiteRating = player1Rating;
4631             gameInfo.blackRating = player2Rating;
4632         } else if (strcmp(player2Name, white) == 0 &&
4633                    strcmp(player1Name, black) == 0) {
4634             if (appData.debugMode)
4635               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4636                       player2Rating, player1Rating);
4637             gameInfo.whiteRating = player2Rating;
4638             gameInfo.blackRating = player1Rating;
4639         }
4640         player1Name[0] = player2Name[0] = NULLCHAR;
4641
4642         /* Silence shouts if requested */
4643         if (appData.quietPlay &&
4644             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4645             SendToICS(ics_prefix);
4646             SendToICS("set shout 0\n");
4647         }
4648     }
4649
4650     /* Deal with midgame name changes */
4651     if (!newGame) {
4652         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4653             if (gameInfo.white) free(gameInfo.white);
4654             gameInfo.white = StrSave(white);
4655         }
4656         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4657             if (gameInfo.black) free(gameInfo.black);
4658             gameInfo.black = StrSave(black);
4659         }
4660     }
4661
4662     /* Throw away game result if anything actually changes in examine mode */
4663     if (gameMode == IcsExamining && !newGame) {
4664         gameInfo.result = GameUnfinished;
4665         if (gameInfo.resultDetails != NULL) {
4666             free(gameInfo.resultDetails);
4667             gameInfo.resultDetails = NULL;
4668         }
4669     }
4670
4671     /* In pausing && IcsExamining mode, we ignore boards coming
4672        in if they are in a different variation than we are. */
4673     if (pauseExamInvalid) return;
4674     if (pausing && gameMode == IcsExamining) {
4675         if (moveNum <= pauseExamForwardMostMove) {
4676             pauseExamInvalid = TRUE;
4677             forwardMostMove = pauseExamForwardMostMove;
4678             return;
4679         }
4680     }
4681
4682   if (appData.debugMode) {
4683     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4684   }
4685     /* Parse the board */
4686     for (k = 0; k < ranks; k++) {
4687       for (j = 0; j < files; j++)
4688         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4689       if(gameInfo.holdingsWidth > 1) {
4690            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4691            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4692       }
4693     }
4694     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4695       board[5][BOARD_RGHT+1] = WhiteAngel;
4696       board[6][BOARD_RGHT+1] = WhiteMarshall;
4697       board[1][0] = BlackMarshall;
4698       board[2][0] = BlackAngel;
4699       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4700     }
4701     CopyBoard(boards[moveNum], board);
4702     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4703     if (moveNum == 0) {
4704         startedFromSetupPosition =
4705           !CompareBoards(board, initialPosition);
4706         if(startedFromSetupPosition)
4707             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4708     }
4709
4710     /* [HGM] Set castling rights. Take the outermost Rooks,
4711        to make it also work for FRC opening positions. Note that board12
4712        is really defective for later FRC positions, as it has no way to
4713        indicate which Rook can castle if they are on the same side of King.
4714        For the initial position we grant rights to the outermost Rooks,
4715        and remember thos rights, and we then copy them on positions
4716        later in an FRC game. This means WB might not recognize castlings with
4717        Rooks that have moved back to their original position as illegal,
4718        but in ICS mode that is not its job anyway.
4719     */
4720     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4721     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4722
4723         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4724             if(board[0][i] == WhiteRook) j = i;
4725         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4726         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4727             if(board[0][i] == WhiteRook) j = i;
4728         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4729         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4730             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4731         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4732         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4733             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4734         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4735
4736         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4737         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4738         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4739             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4740         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4741             if(board[BOARD_HEIGHT-1][k] == bKing)
4742                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4743         if(gameInfo.variant == VariantTwoKings) {
4744             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4745             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4746             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4747         }
4748     } else { int r;
4749         r = boards[moveNum][CASTLING][0] = initialRights[0];
4750         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4751         r = boards[moveNum][CASTLING][1] = initialRights[1];
4752         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4753         r = boards[moveNum][CASTLING][3] = initialRights[3];
4754         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4755         r = boards[moveNum][CASTLING][4] = initialRights[4];
4756         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4757         /* wildcastle kludge: always assume King has rights */
4758         r = boards[moveNum][CASTLING][2] = initialRights[2];
4759         r = boards[moveNum][CASTLING][5] = initialRights[5];
4760     }
4761     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4762     boards[moveNum][EP_STATUS] = EP_NONE;
4763     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4764     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4765     if(double_push !=  -1) {
4766         int dir = WhiteOnMove(moveNum) ? 1 : -1, last = BOARD_HEIGHT-1;
4767         boards[moveNum][EP_FILE] = // also set new e.p. variables
4768         boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4769         boards[moveNum][EP_RANK] = (last + 3*dir)/2;
4770         boards[moveNum][LAST_TO] = 128*(last + dir) + boards[moveNum][EP_FILE];
4771     } else boards[moveNum][EP_FILE] = boards[moveNum][EP_RANK] = 100;
4772
4773
4774     if (ics_getting_history == H_GOT_REQ_HEADER ||
4775         ics_getting_history == H_GOT_UNREQ_HEADER) {
4776         /* This was an initial position from a move list, not
4777            the current position */
4778         return;
4779     }
4780
4781     /* Update currentMove and known move number limits */
4782     newMove = newGame || moveNum > forwardMostMove;
4783
4784     if (newGame) {
4785         forwardMostMove = backwardMostMove = currentMove = moveNum;
4786         if (gameMode == IcsExamining && moveNum == 0) {
4787           /* Workaround for ICS limitation: we are not told the wild
4788              type when starting to examine a game.  But if we ask for
4789              the move list, the move list header will tell us */
4790             ics_getting_history = H_REQUESTED;
4791             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4792             SendToICS(str);
4793         }
4794     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4795                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4796 #if ZIPPY
4797         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4798         /* [HGM] applied this also to an engine that is silently watching        */
4799         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4800             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4801             gameInfo.variant == currentlyInitializedVariant) {
4802           takeback = forwardMostMove - moveNum;
4803           for (i = 0; i < takeback; i++) {
4804             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4805             SendToProgram("undo\n", &first);
4806           }
4807         }
4808 #endif
4809
4810         forwardMostMove = moveNum;
4811         if (!pausing || currentMove > forwardMostMove)
4812           currentMove = forwardMostMove;
4813     } else {
4814         /* New part of history that is not contiguous with old part */
4815         if (pausing && gameMode == IcsExamining) {
4816             pauseExamInvalid = TRUE;
4817             forwardMostMove = pauseExamForwardMostMove;
4818             return;
4819         }
4820         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4821 #if ZIPPY
4822             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4823                 // [HGM] when we will receive the move list we now request, it will be
4824                 // fed to the engine from the first move on. So if the engine is not
4825                 // in the initial position now, bring it there.
4826                 InitChessProgram(&first, 0);
4827             }
4828 #endif
4829             ics_getting_history = H_REQUESTED;
4830             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4831             SendToICS(str);
4832         }
4833         forwardMostMove = backwardMostMove = currentMove = moveNum;
4834     }
4835
4836     /* Update the clocks */
4837     if (strchr(elapsed_time, '.')) {
4838       /* Time is in ms */
4839       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4840       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4841     } else {
4842       /* Time is in seconds */
4843       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4844       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4845     }
4846
4847
4848 #if ZIPPY
4849     if (appData.zippyPlay && newGame &&
4850         gameMode != IcsObserving && gameMode != IcsIdle &&
4851         gameMode != IcsExamining)
4852       ZippyFirstBoard(moveNum, basetime, increment);
4853 #endif
4854
4855     /* Put the move on the move list, first converting
4856        to canonical algebraic form. */
4857     if (moveNum > 0) {
4858   if (appData.debugMode) {
4859     int f = forwardMostMove;
4860     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4861             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4862             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4863     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4864     fprintf(debugFP, "moveNum = %d\n", moveNum);
4865     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4866     setbuf(debugFP, NULL);
4867   }
4868         if (moveNum <= backwardMostMove) {
4869             /* We don't know what the board looked like before
4870                this move.  Punt. */
4871           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4872             strcat(parseList[moveNum - 1], " ");
4873             strcat(parseList[moveNum - 1], elapsed_time);
4874             moveList[moveNum - 1][0] = NULLCHAR;
4875         } else if (strcmp(move_str, "none") == 0) {
4876             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4877             /* Again, we don't know what the board looked like;
4878                this is really the start of the game. */
4879             parseList[moveNum - 1][0] = NULLCHAR;
4880             moveList[moveNum - 1][0] = NULLCHAR;
4881             backwardMostMove = moveNum;
4882             startedFromSetupPosition = TRUE;
4883             fromX = fromY = toX = toY = -1;
4884         } else {
4885           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4886           //                 So we parse the long-algebraic move string in stead of the SAN move
4887           int valid; char buf[MSG_SIZ], *prom;
4888
4889           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4890                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4891           // str looks something like "Q/a1-a2"; kill the slash
4892           if(str[1] == '/')
4893             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4894           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4895           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4896                 strcat(buf, prom); // long move lacks promo specification!
4897           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4898                 if(appData.debugMode)
4899                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4900                 safeStrCpy(move_str, buf, MSG_SIZ);
4901           }
4902           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4903                                 &fromX, &fromY, &toX, &toY, &promoChar)
4904                || ParseOneMove(buf, moveNum - 1, &moveType,
4905                                 &fromX, &fromY, &toX, &toY, &promoChar);
4906           // end of long SAN patch
4907           if (valid) {
4908             (void) CoordsToAlgebraic(boards[moveNum - 1],
4909                                      PosFlags(moveNum - 1),
4910                                      fromY, fromX, toY, toX, promoChar,
4911                                      parseList[moveNum-1]);
4912             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4913               case MT_NONE:
4914               case MT_STALEMATE:
4915               default:
4916                 break;
4917               case MT_CHECK:
4918                 if(!IS_SHOGI(gameInfo.variant))
4919                     strcat(parseList[moveNum - 1], "+");
4920                 break;
4921               case MT_CHECKMATE:
4922               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4923                 strcat(parseList[moveNum - 1], "#");
4924                 break;
4925             }
4926             strcat(parseList[moveNum - 1], " ");
4927             strcat(parseList[moveNum - 1], elapsed_time);
4928             /* currentMoveString is set as a side-effect of ParseOneMove */
4929             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4930             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4931             strcat(moveList[moveNum - 1], "\n");
4932
4933             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4934                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4935               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4936                 ChessSquare old, new = boards[moveNum][k][j];
4937                   if(new == EmptySquare || fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4938                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4939                   if(old == new) continue;
4940                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4941                   else if(new == WhiteWazir || new == BlackWazir) {
4942                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4943                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4944                       else boards[moveNum][k][j] = old; // preserve type of Gold
4945                   } else if(old == WhitePawn || old == BlackPawn) // Pawn promotions (but not e.p.capture!)
4946                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4947               }
4948           } else {
4949             /* Move from ICS was illegal!?  Punt. */
4950             if (appData.debugMode) {
4951               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4952               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4953             }
4954             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4955             strcat(parseList[moveNum - 1], " ");
4956             strcat(parseList[moveNum - 1], elapsed_time);
4957             moveList[moveNum - 1][0] = NULLCHAR;
4958             fromX = fromY = toX = toY = -1;
4959           }
4960         }
4961   if (appData.debugMode) {
4962     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4963     setbuf(debugFP, NULL);
4964   }
4965
4966 #if ZIPPY
4967         /* Send move to chess program (BEFORE animating it). */
4968         if (appData.zippyPlay && !newGame && newMove &&
4969            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4970
4971             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4972                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4973                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4974                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4975                             move_str);
4976                     DisplayError(str, 0);
4977                 } else {
4978                     if (first.sendTime) {
4979                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4980                     }
4981                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4982                     if (firstMove && !bookHit) {
4983                         firstMove = FALSE;
4984                         if (first.useColors) {
4985                           SendToProgram(gameMode == IcsPlayingWhite ?
4986                                         "white\ngo\n" :
4987                                         "black\ngo\n", &first);
4988                         } else {
4989                           SendToProgram("go\n", &first);
4990                         }
4991                         first.maybeThinking = TRUE;
4992                     }
4993                 }
4994             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4995               if (moveList[moveNum - 1][0] == NULLCHAR) {
4996                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4997                 DisplayError(str, 0);
4998               } else {
4999                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
5000                 SendMoveToProgram(moveNum - 1, &first);
5001               }
5002             }
5003         }
5004 #endif
5005     }
5006
5007     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
5008         /* If move comes from a remote source, animate it.  If it
5009            isn't remote, it will have already been animated. */
5010         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
5011             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
5012         }
5013         if (!pausing && appData.highlightLastMove) {
5014             SetHighlights(fromX, fromY, toX, toY);
5015         }
5016     }
5017
5018     /* Start the clocks */
5019     whiteFlag = blackFlag = FALSE;
5020     appData.clockMode = !(basetime == 0 && increment == 0);
5021     if (ticking == 0) {
5022       ics_clock_paused = TRUE;
5023       StopClocks();
5024     } else if (ticking == 1) {
5025       ics_clock_paused = FALSE;
5026     }
5027     if (gameMode == IcsIdle ||
5028         relation == RELATION_OBSERVING_STATIC ||
5029         relation == RELATION_EXAMINING ||
5030         ics_clock_paused)
5031       DisplayBothClocks();
5032     else
5033       StartClocks();
5034
5035     /* Display opponents and material strengths */
5036     if (gameInfo.variant != VariantBughouse &&
5037         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5038         if (tinyLayout || smallLayout) {
5039             if(gameInfo.variant == VariantNormal)
5040               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5041                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5042                     basetime, increment);
5043             else
5044               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5045                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5046                     basetime, increment, (int) gameInfo.variant);
5047         } else {
5048             if(gameInfo.variant == VariantNormal)
5049               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5050                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5051                     basetime, increment);
5052             else
5053               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5054                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5055                     basetime, increment, VariantName(gameInfo.variant));
5056         }
5057         DisplayTitle(str);
5058   if (appData.debugMode) {
5059     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5060   }
5061     }
5062
5063
5064     /* Display the board */
5065     if (!pausing && !appData.noGUI) {
5066
5067       if (appData.premove)
5068           if (!gotPremove ||
5069              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5070              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5071               ClearPremoveHighlights();
5072
5073       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5074         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5075       DrawPosition(j, boards[currentMove]);
5076
5077       DisplayMove(moveNum - 1);
5078       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5079             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5080               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5081         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5082       }
5083     }
5084
5085     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5086 #if ZIPPY
5087     if(bookHit) { // [HGM] book: simulate book reply
5088         static char bookMove[MSG_SIZ]; // a bit generous?
5089
5090         programStats.nodes = programStats.depth = programStats.time =
5091         programStats.score = programStats.got_only_move = 0;
5092         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5093
5094         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5095         strcat(bookMove, bookHit);
5096         HandleMachineMove(bookMove, &first);
5097     }
5098 #endif
5099 }
5100
5101 void
5102 GetMoveListEvent ()
5103 {
5104     char buf[MSG_SIZ];
5105     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5106         ics_getting_history = H_REQUESTED;
5107         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5108         SendToICS(buf);
5109     }
5110 }
5111
5112 void
5113 SendToBoth (char *msg)
5114 {   // to make it easy to keep two engines in step in dual analysis
5115     SendToProgram(msg, &first);
5116     if(second.analyzing) SendToProgram(msg, &second);
5117 }
5118
5119 void
5120 AnalysisPeriodicEvent (int force)
5121 {
5122     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5123          && !force) || !appData.periodicUpdates)
5124       return;
5125
5126     /* Send . command to Crafty to collect stats */
5127     SendToBoth(".\n");
5128
5129     /* Don't send another until we get a response (this makes
5130        us stop sending to old Crafty's which don't understand
5131        the "." command (sending illegal cmds resets node count & time,
5132        which looks bad)) */
5133     programStats.ok_to_send = 0;
5134 }
5135
5136 void
5137 ics_update_width (int new_width)
5138 {
5139         ics_printf("set width %d\n", new_width);
5140 }
5141
5142 void
5143 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5144 {
5145     char buf[MSG_SIZ];
5146
5147     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5148         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5149             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5150             SendToProgram(buf, cps);
5151             return;
5152         }
5153         // null move in variant where engine does not understand it (for analysis purposes)
5154         SendBoard(cps, moveNum + 1); // send position after move in stead.
5155         return;
5156     }
5157     if (cps->useUsermove) {
5158       SendToProgram("usermove ", cps);
5159     }
5160     if (cps->useSAN) {
5161       char *space;
5162       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5163         int len = space - parseList[moveNum];
5164         memcpy(buf, parseList[moveNum], len);
5165         buf[len++] = '\n';
5166         buf[len] = NULLCHAR;
5167       } else {
5168         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5169       }
5170       SendToProgram(buf, cps);
5171     } else {
5172       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5173         AlphaRank(moveList[moveNum], 4);
5174         SendToProgram(moveList[moveNum], cps);
5175         AlphaRank(moveList[moveNum], 4); // and back
5176       } else
5177       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5178        * the engine. It would be nice to have a better way to identify castle
5179        * moves here. */
5180       if(appData.fischerCastling && cps->useOOCastle) {
5181         int fromX = moveList[moveNum][0] - AAA;
5182         int fromY = moveList[moveNum][1] - ONE;
5183         int toX = moveList[moveNum][2] - AAA;
5184         int toY = moveList[moveNum][3] - ONE;
5185         if((boards[moveNum][fromY][fromX] == WhiteKing
5186             && boards[moveNum][toY][toX] == WhiteRook)
5187            || (boards[moveNum][fromY][fromX] == BlackKing
5188                && boards[moveNum][toY][toX] == BlackRook)) {
5189           if(toX > fromX) SendToProgram("O-O\n", cps);
5190           else SendToProgram("O-O-O\n", cps);
5191         }
5192         else SendToProgram(moveList[moveNum], cps);
5193       } else
5194       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5195         char *m = moveList[moveNum];
5196         static char c[2];
5197         *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5198         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
5199           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5200                                                m[2], m[3] - '0',
5201                                                m[5], m[6] - '0',
5202                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5203         else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5204           *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5205           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
5206                                                m[7], m[8] - '0',
5207                                                m[7], m[8] - '0',
5208                                                m[5], m[6] - '0',
5209                                                m[5], m[6] - '0',
5210                                                m[2], m[3] - '0', c);
5211         } else
5212           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5213                                                m[5], m[6] - '0',
5214                                                m[5], m[6] - '0',
5215                                                m[2], m[3] - '0', c);
5216           SendToProgram(buf, cps);
5217       } else
5218       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5219         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5220           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5221           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5222                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5223         } else
5224           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5225                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5226         SendToProgram(buf, cps);
5227       }
5228       else SendToProgram(moveList[moveNum], cps);
5229       /* End of additions by Tord */
5230     }
5231
5232     /* [HGM] setting up the opening has brought engine in force mode! */
5233     /*       Send 'go' if we are in a mode where machine should play. */
5234     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5235         (gameMode == TwoMachinesPlay   ||
5236 #if ZIPPY
5237          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5238 #endif
5239          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5240         SendToProgram("go\n", cps);
5241   if (appData.debugMode) {
5242     fprintf(debugFP, "(extra)\n");
5243   }
5244     }
5245     setboardSpoiledMachineBlack = 0;
5246 }
5247
5248 void
5249 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5250 {
5251     char user_move[MSG_SIZ];
5252     char suffix[4];
5253
5254     if(gameInfo.variant == VariantSChess && promoChar) {
5255         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5256         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5257     } else suffix[0] = NULLCHAR;
5258
5259     switch (moveType) {
5260       default:
5261         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5262                 (int)moveType, fromX, fromY, toX, toY);
5263         DisplayError(user_move + strlen("say "), 0);
5264         break;
5265       case WhiteKingSideCastle:
5266       case BlackKingSideCastle:
5267       case WhiteQueenSideCastleWild:
5268       case BlackQueenSideCastleWild:
5269       /* PUSH Fabien */
5270       case WhiteHSideCastleFR:
5271       case BlackHSideCastleFR:
5272       /* POP Fabien */
5273         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5274         break;
5275       case WhiteQueenSideCastle:
5276       case BlackQueenSideCastle:
5277       case WhiteKingSideCastleWild:
5278       case BlackKingSideCastleWild:
5279       /* PUSH Fabien */
5280       case WhiteASideCastleFR:
5281       case BlackASideCastleFR:
5282       /* POP Fabien */
5283         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5284         break;
5285       case WhiteNonPromotion:
5286       case BlackNonPromotion:
5287         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5288         break;
5289       case WhitePromotion:
5290       case BlackPromotion:
5291         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5292            gameInfo.variant == VariantMakruk)
5293           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5294                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5295                 PieceToChar(WhiteFerz));
5296         else if(gameInfo.variant == VariantGreat)
5297           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5298                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5299                 PieceToChar(WhiteMan));
5300         else
5301           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5302                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5303                 promoChar);
5304         break;
5305       case WhiteDrop:
5306       case BlackDrop:
5307       drop:
5308         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5309                  ToUpper(PieceToChar((ChessSquare) fromX)),
5310                  AAA + toX, ONE + toY);
5311         break;
5312       case IllegalMove:  /* could be a variant we don't quite understand */
5313         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5314       case NormalMove:
5315       case WhiteCapturesEnPassant:
5316       case BlackCapturesEnPassant:
5317         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5318                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5319         break;
5320     }
5321     SendToICS(user_move);
5322     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5323         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5324 }
5325
5326 void
5327 UploadGameEvent ()
5328 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5329     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5330     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5331     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5332       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5333       return;
5334     }
5335     if(gameMode != IcsExamining) { // is this ever not the case?
5336         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5337
5338         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5339           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5340         } else { // on FICS we must first go to general examine mode
5341           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5342         }
5343         if(gameInfo.variant != VariantNormal) {
5344             // try figure out wild number, as xboard names are not always valid on ICS
5345             for(i=1; i<=36; i++) {
5346               snprintf(buf, MSG_SIZ, "wild/%d", i);
5347                 if(StringToVariant(buf) == gameInfo.variant) break;
5348             }
5349             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5350             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5351             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5352         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5353         SendToICS(ics_prefix);
5354         SendToICS(buf);
5355         if(startedFromSetupPosition || backwardMostMove != 0) {
5356           fen = PositionToFEN(backwardMostMove, NULL, 1);
5357           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5358             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5359             SendToICS(buf);
5360           } else { // FICS: everything has to set by separate bsetup commands
5361             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5362             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5363             SendToICS(buf);
5364             if(!WhiteOnMove(backwardMostMove)) {
5365                 SendToICS("bsetup tomove black\n");
5366             }
5367             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5368             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5369             SendToICS(buf);
5370             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5371             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5372             SendToICS(buf);
5373             i = boards[backwardMostMove][EP_STATUS];
5374             if(i >= 0) { // set e.p.
5375               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5376                 SendToICS(buf);
5377             }
5378             bsetup++;
5379           }
5380         }
5381       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5382             SendToICS("bsetup done\n"); // switch to normal examining.
5383     }
5384     for(i = backwardMostMove; i<last; i++) {
5385         char buf[20];
5386         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5387         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5388             int len = strlen(moveList[i]);
5389             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5390             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5391         }
5392         SendToICS(buf);
5393     }
5394     SendToICS(ics_prefix);
5395     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5396 }
5397
5398 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5399 int legNr = 1;
5400
5401 void
5402 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5403 {
5404     if (rf == DROP_RANK) {
5405       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5406       sprintf(move, "%c@%c%c\n",
5407                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5408     } else {
5409         if (promoChar == 'x' || promoChar == NULLCHAR) {
5410           sprintf(move, "%c%c%c%c\n",
5411                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5412           if(killX >= 0 && killY >= 0) {
5413             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5414             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5415           }
5416         } else {
5417             sprintf(move, "%c%c%c%c%c\n",
5418                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5419           if(killX >= 0 && killY >= 0) {
5420             sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5421             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5422           }
5423         }
5424     }
5425 }
5426
5427 void
5428 ProcessICSInitScript (FILE *f)
5429 {
5430     char buf[MSG_SIZ];
5431
5432     while (fgets(buf, MSG_SIZ, f)) {
5433         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5434     }
5435
5436     fclose(f);
5437 }
5438
5439
5440 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5441 int dragging;
5442 static ClickType lastClickType;
5443
5444 int
5445 PieceInString (char *s, ChessSquare piece)
5446 {
5447   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5448   while((p = strchr(s, ID))) {
5449     if(!suffix || p[1] == suffix) return TRUE;
5450     s = p;
5451   }
5452   return FALSE;
5453 }
5454
5455 int
5456 Partner (ChessSquare *p)
5457 { // change piece into promotion partner if one shogi-promotes to the other
5458   ChessSquare partner = promoPartner[*p];
5459   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5460   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5461   *p = partner;
5462   return 1;
5463 }
5464
5465 void
5466 Sweep (int step)
5467 {
5468     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5469     static int toggleFlag;
5470     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5471     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5472     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5473     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5474     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5475     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5476     do {
5477         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5478         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5479         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5480         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5481         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5482         if(!step) step = -1;
5483     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5484             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5485             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5486             promoSweep == pawn ||
5487             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5488             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5489     if(toX >= 0) {
5490         int victim = boards[currentMove][toY][toX];
5491         boards[currentMove][toY][toX] = promoSweep;
5492         DrawPosition(FALSE, boards[currentMove]);
5493         boards[currentMove][toY][toX] = victim;
5494     } else
5495     ChangeDragPiece(promoSweep);
5496 }
5497
5498 int
5499 PromoScroll (int x, int y)
5500 {
5501   int step = 0;
5502
5503   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5504   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5505   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5506   if(!step) return FALSE;
5507   lastX = x; lastY = y;
5508   if((promoSweep < BlackPawn) == flipView) step = -step;
5509   if(step > 0) selectFlag = 1;
5510   if(!selectFlag) Sweep(step);
5511   return FALSE;
5512 }
5513
5514 void
5515 NextPiece (int step)
5516 {
5517     ChessSquare piece = boards[currentMove][toY][toX];
5518     do {
5519         pieceSweep -= step;
5520         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5521         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5522         if(!step) step = -1;
5523     } while(PieceToChar(pieceSweep) == '.');
5524     boards[currentMove][toY][toX] = pieceSweep;
5525     DrawPosition(FALSE, boards[currentMove]);
5526     boards[currentMove][toY][toX] = piece;
5527 }
5528 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5529 void
5530 AlphaRank (char *move, int n)
5531 {
5532 //    char *p = move, c; int x, y;
5533
5534     if (appData.debugMode) {
5535         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5536     }
5537
5538     if(move[1]=='*' &&
5539        move[2]>='0' && move[2]<='9' &&
5540        move[3]>='a' && move[3]<='x'    ) {
5541         move[1] = '@';
5542         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5543         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5544     } else
5545     if(move[0]>='0' && move[0]<='9' &&
5546        move[1]>='a' && move[1]<='x' &&
5547        move[2]>='0' && move[2]<='9' &&
5548        move[3]>='a' && move[3]<='x'    ) {
5549         /* input move, Shogi -> normal */
5550         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5551         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5552         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5553         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5554     } else
5555     if(move[1]=='@' &&
5556        move[3]>='0' && move[3]<='9' &&
5557        move[2]>='a' && move[2]<='x'    ) {
5558         move[1] = '*';
5559         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5560         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5561     } else
5562     if(
5563        move[0]>='a' && move[0]<='x' &&
5564        move[3]>='0' && move[3]<='9' &&
5565        move[2]>='a' && move[2]<='x'    ) {
5566          /* output move, normal -> Shogi */
5567         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5568         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5569         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5570         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5571         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5572     }
5573     if (appData.debugMode) {
5574         fprintf(debugFP, "   out = '%s'\n", move);
5575     }
5576 }
5577
5578 char yy_textstr[8000];
5579
5580 /* Parser for moves from gnuchess, ICS, or user typein box */
5581 Boolean
5582 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5583 {
5584     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5585
5586     switch (*moveType) {
5587       case WhitePromotion:
5588       case BlackPromotion:
5589       case WhiteNonPromotion:
5590       case BlackNonPromotion:
5591       case NormalMove:
5592       case FirstLeg:
5593       case WhiteCapturesEnPassant:
5594       case BlackCapturesEnPassant:
5595       case WhiteKingSideCastle:
5596       case WhiteQueenSideCastle:
5597       case BlackKingSideCastle:
5598       case BlackQueenSideCastle:
5599       case WhiteKingSideCastleWild:
5600       case WhiteQueenSideCastleWild:
5601       case BlackKingSideCastleWild:
5602       case BlackQueenSideCastleWild:
5603       /* Code added by Tord: */
5604       case WhiteHSideCastleFR:
5605       case WhiteASideCastleFR:
5606       case BlackHSideCastleFR:
5607       case BlackASideCastleFR:
5608       /* End of code added by Tord */
5609       case IllegalMove:         /* bug or odd chess variant */
5610         if(currentMoveString[1] == '@') { // illegal drop
5611           *fromX = WhiteOnMove(moveNum) ?
5612             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5613             (int) CharToPiece(ToLower(currentMoveString[0]));
5614           goto drop;
5615         }
5616         *fromX = currentMoveString[0] - AAA;
5617         *fromY = currentMoveString[1] - ONE;
5618         *toX = currentMoveString[2] - AAA;
5619         *toY = currentMoveString[3] - ONE;
5620         *promoChar = currentMoveString[4];
5621         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5622         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5623             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5624     if (appData.debugMode) {
5625         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5626     }
5627             *fromX = *fromY = *toX = *toY = 0;
5628             return FALSE;
5629         }
5630         if (appData.testLegality) {
5631           return (*moveType != IllegalMove);
5632         } else {
5633           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5634                          // [HGM] lion: if this is a double move we are less critical
5635                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5636         }
5637
5638       case WhiteDrop:
5639       case BlackDrop:
5640         *fromX = *moveType == WhiteDrop ?
5641           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5642           (int) CharToPiece(ToLower(currentMoveString[0]));
5643       drop:
5644         *fromY = DROP_RANK;
5645         *toX = currentMoveString[2] - AAA;
5646         *toY = currentMoveString[3] - ONE;
5647         *promoChar = NULLCHAR;
5648         return TRUE;
5649
5650       case AmbiguousMove:
5651       case ImpossibleMove:
5652       case EndOfFile:
5653       case ElapsedTime:
5654       case Comment:
5655       case PGNTag:
5656       case NAG:
5657       case WhiteWins:
5658       case BlackWins:
5659       case GameIsDrawn:
5660       default:
5661     if (appData.debugMode) {
5662         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5663     }
5664         /* bug? */
5665         *fromX = *fromY = *toX = *toY = 0;
5666         *promoChar = NULLCHAR;
5667         return FALSE;
5668     }
5669 }
5670
5671 Boolean pushed = FALSE;
5672 char *lastParseAttempt;
5673
5674 void
5675 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5676 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5677   int fromX, fromY, toX, toY; char promoChar;
5678   ChessMove moveType;
5679   Boolean valid;
5680   int nr = 0;
5681
5682   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5683   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5684     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5685     pushed = TRUE;
5686   }
5687   endPV = forwardMostMove;
5688   do {
5689     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5690     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5691     lastParseAttempt = pv;
5692     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5693     if(!valid && nr == 0 &&
5694        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5695         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5696         // Hande case where played move is different from leading PV move
5697         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5698         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5699         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5700         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5701           endPV += 2; // if position different, keep this
5702           moveList[endPV-1][0] = fromX + AAA;
5703           moveList[endPV-1][1] = fromY + ONE;
5704           moveList[endPV-1][2] = toX + AAA;
5705           moveList[endPV-1][3] = toY + ONE;
5706           parseList[endPV-1][0] = NULLCHAR;
5707           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5708         }
5709       }
5710     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5711     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5712     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5713     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5714         valid++; // allow comments in PV
5715         continue;
5716     }
5717     nr++;
5718     if(endPV+1 > framePtr) break; // no space, truncate
5719     if(!valid) break;
5720     endPV++;
5721     CopyBoard(boards[endPV], boards[endPV-1]);
5722     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5723     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5724     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5725     CoordsToAlgebraic(boards[endPV - 1],
5726                              PosFlags(endPV - 1),
5727                              fromY, fromX, toY, toX, promoChar,
5728                              parseList[endPV - 1]);
5729   } while(valid);
5730   if(atEnd == 2) return; // used hidden, for PV conversion
5731   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5732   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5733   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5734                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5735   DrawPosition(TRUE, boards[currentMove]);
5736 }
5737
5738 int
5739 MultiPV (ChessProgramState *cps, int kind)
5740 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5741         int i;
5742         for(i=0; i<cps->nrOptions; i++) {
5743             char *s = cps->option[i].name;
5744             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5745             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5746                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5747         }
5748         return -1;
5749 }
5750
5751 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5752 static int multi, pv_margin;
5753 static ChessProgramState *activeCps;
5754
5755 Boolean
5756 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5757 {
5758         int startPV, lineStart, origIndex = index;
5759         char *p, buf2[MSG_SIZ];
5760         ChessProgramState *cps = (pane ? &second : &first);
5761
5762         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5763         lastX = x; lastY = y;
5764         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5765         lineStart = startPV = index;
5766         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5767         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5768         index = startPV;
5769         do{ while(buf[index] && buf[index] != '\n') index++;
5770         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5771         buf[index] = 0;
5772         if(lineStart == 0 && gameMode == AnalyzeMode) {
5773             int n = 0;
5774             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5775             if(n == 0) { // click not on "fewer" or "more"
5776                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5777                     pv_margin = cps->option[multi].value;
5778                     activeCps = cps; // non-null signals margin adjustment
5779                 }
5780             } else if((multi = MultiPV(cps, 1)) >= 0) {
5781                 n += cps->option[multi].value; if(n < 1) n = 1;
5782                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5783                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5784                 cps->option[multi].value = n;
5785                 *start = *end = 0;
5786                 return FALSE;
5787             }
5788         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5789                 ExcludeClick(origIndex - lineStart);
5790                 return FALSE;
5791         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5792                 Collapse(origIndex - lineStart);
5793                 return FALSE;
5794         }
5795         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5796         *start = startPV; *end = index-1;
5797         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5798         return TRUE;
5799 }
5800
5801 char *
5802 PvToSAN (char *pv)
5803 {
5804         static char buf[10*MSG_SIZ];
5805         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5806         *buf = NULLCHAR;
5807         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5808         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5809         for(i = forwardMostMove; i<endPV; i++){
5810             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5811             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5812             k += strlen(buf+k);
5813         }
5814         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5815         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5816         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5817         endPV = savedEnd;
5818         return buf;
5819 }
5820
5821 Boolean
5822 LoadPV (int x, int y)
5823 { // called on right mouse click to load PV
5824   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5825   lastX = x; lastY = y;
5826   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5827   extendGame = FALSE;
5828   return TRUE;
5829 }
5830
5831 void
5832 UnLoadPV ()
5833 {
5834   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5835   if(activeCps) {
5836     if(pv_margin != activeCps->option[multi].value) {
5837       char buf[MSG_SIZ];
5838       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5839       SendToProgram(buf, activeCps);
5840       activeCps->option[multi].value = pv_margin;
5841     }
5842     activeCps = NULL;
5843     return;
5844   }
5845   if(endPV < 0) return;
5846   if(appData.autoCopyPV) CopyFENToClipboard();
5847   endPV = -1;
5848   if(extendGame && currentMove > forwardMostMove) {
5849         Boolean saveAnimate = appData.animate;
5850         if(pushed) {
5851             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5852                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5853             } else storedGames--; // abandon shelved tail of original game
5854         }
5855         pushed = FALSE;
5856         forwardMostMove = currentMove;
5857         currentMove = oldFMM;
5858         appData.animate = FALSE;
5859         ToNrEvent(forwardMostMove);
5860         appData.animate = saveAnimate;
5861   }
5862   currentMove = forwardMostMove;
5863   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5864   ClearPremoveHighlights();
5865   DrawPosition(TRUE, boards[currentMove]);
5866 }
5867
5868 void
5869 MovePV (int x, int y, int h)
5870 { // step through PV based on mouse coordinates (called on mouse move)
5871   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5872
5873   if(activeCps) { // adjusting engine's multi-pv margin
5874     if(x > lastX) pv_margin++; else
5875     if(x < lastX) pv_margin -= (pv_margin > 0);
5876     if(x != lastX) {
5877       char buf[MSG_SIZ];
5878       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5879       DisplayMessage(buf, "");
5880     }
5881     lastX = x;
5882     return;
5883   }
5884   // we must somehow check if right button is still down (might be released off board!)
5885   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5886   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5887   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5888   if(!step) return;
5889   lastX = x; lastY = y;
5890
5891   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5892   if(endPV < 0) return;
5893   if(y < margin) step = 1; else
5894   if(y > h - margin) step = -1;
5895   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5896   currentMove += step;
5897   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5898   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5899                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5900   DrawPosition(FALSE, boards[currentMove]);
5901 }
5902
5903
5904 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5905 // All positions will have equal probability, but the current method will not provide a unique
5906 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5907 #define DARK 1
5908 #define LITE 2
5909 #define ANY 3
5910
5911 int squaresLeft[4];
5912 int piecesLeft[(int)BlackPawn];
5913 int seed, nrOfShuffles;
5914
5915 void
5916 GetPositionNumber ()
5917 {       // sets global variable seed
5918         int i;
5919
5920         seed = appData.defaultFrcPosition;
5921         if(seed < 0) { // randomize based on time for negative FRC position numbers
5922                 for(i=0; i<50; i++) seed += random();
5923                 seed = random() ^ random() >> 8 ^ random() << 8;
5924                 if(seed<0) seed = -seed;
5925         }
5926 }
5927
5928 int
5929 put (Board board, int pieceType, int rank, int n, int shade)
5930 // put the piece on the (n-1)-th empty squares of the given shade
5931 {
5932         int i;
5933
5934         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5935                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5936                         board[rank][i] = (ChessSquare) pieceType;
5937                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5938                         squaresLeft[ANY]--;
5939                         piecesLeft[pieceType]--;
5940                         return i;
5941                 }
5942         }
5943         return -1;
5944 }
5945
5946
5947 void
5948 AddOnePiece (Board board, int pieceType, int rank, int shade)
5949 // calculate where the next piece goes, (any empty square), and put it there
5950 {
5951         int i;
5952
5953         i = seed % squaresLeft[shade];
5954         nrOfShuffles *= squaresLeft[shade];
5955         seed /= squaresLeft[shade];
5956         put(board, pieceType, rank, i, shade);
5957 }
5958
5959 void
5960 AddTwoPieces (Board board, int pieceType, int rank)
5961 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5962 {
5963         int i, n=squaresLeft[ANY], j=n-1, k;
5964
5965         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5966         i = seed % k;  // pick one
5967         nrOfShuffles *= k;
5968         seed /= k;
5969         while(i >= j) i -= j--;
5970         j = n - 1 - j; i += j;
5971         put(board, pieceType, rank, j, ANY);
5972         put(board, pieceType, rank, i, ANY);
5973 }
5974
5975 void
5976 SetUpShuffle (Board board, int number)
5977 {
5978         int i, p, first=1;
5979
5980         GetPositionNumber(); nrOfShuffles = 1;
5981
5982         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5983         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5984         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5985
5986         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5987
5988         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5989             p = (int) board[0][i];
5990             if(p < (int) BlackPawn) piecesLeft[p] ++;
5991             board[0][i] = EmptySquare;
5992         }
5993
5994         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5995             // shuffles restricted to allow normal castling put KRR first
5996             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5997                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5998             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5999                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
6000             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
6001                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
6002             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
6003                 put(board, WhiteRook, 0, 0, ANY);
6004             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
6005         }
6006
6007         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
6008             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
6009             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
6010                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
6011                 while(piecesLeft[p] >= 2) {
6012                     AddOnePiece(board, p, 0, LITE);
6013                     AddOnePiece(board, p, 0, DARK);
6014                 }
6015                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
6016             }
6017
6018         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
6019             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
6020             // but we leave King and Rooks for last, to possibly obey FRC restriction
6021             if(p == (int)WhiteRook) continue;
6022             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
6023             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
6024         }
6025
6026         // now everything is placed, except perhaps King (Unicorn) and Rooks
6027
6028         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
6029             // Last King gets castling rights
6030             while(piecesLeft[(int)WhiteUnicorn]) {
6031                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6032                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6033             }
6034
6035             while(piecesLeft[(int)WhiteKing]) {
6036                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6037                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6038             }
6039
6040
6041         } else {
6042             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6043             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6044         }
6045
6046         // Only Rooks can be left; simply place them all
6047         while(piecesLeft[(int)WhiteRook]) {
6048                 i = put(board, WhiteRook, 0, 0, ANY);
6049                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6050                         if(first) {
6051                                 first=0;
6052                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6053                         }
6054                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6055                 }
6056         }
6057         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6058             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6059         }
6060
6061         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6062 }
6063
6064 int
6065 ptclen (const char *s, char *escapes)
6066 {
6067     int n = 0;
6068     if(!*escapes) return strlen(s);
6069     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6070     return n;
6071 }
6072
6073 int
6074 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6075 /* [HGM] moved here from winboard.c because of its general usefulness */
6076 /*       Basically a safe strcpy that uses the last character as King */
6077 {
6078     int result = FALSE; int NrPieces;
6079     unsigned char partner[EmptySquare];
6080
6081     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6082                     && NrPieces >= 12 && !(NrPieces&1)) {
6083         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6084
6085         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6086         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6087             char *p, c=0;
6088             if(map[j] == '/') offs = WhitePBishop - i, j++;
6089             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6090             table[i+offs] = map[j++];
6091             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6092             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6093             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6094         }
6095         table[(int) WhiteKing]  = map[j++];
6096         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6097             char *p, c=0;
6098             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6099             i = WHITE_TO_BLACK ii;
6100             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6101             table[i+offs] = map[j++];
6102             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6103             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6104             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6105         }
6106         table[(int) BlackKing]  = map[j++];
6107
6108
6109         if(*escapes) { // set up promotion pairing
6110             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6111             // pieceToChar entirely filled, so we can look up specified partners
6112             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6113                 int c = table[i];
6114                 if(c == '^' || c == '-') { // has specified partner
6115                     int p;
6116                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6117                     if(c == '^') table[i] = '+';
6118                     if(p < EmptySquare) {
6119                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6120                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6121                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6122                     }
6123                 } else if(c == '*') {
6124                     table[i] = partner[i];
6125                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6126                 }
6127             }
6128         }
6129
6130         result = TRUE;
6131     }
6132
6133     return result;
6134 }
6135
6136 int
6137 SetCharTable (unsigned char *table, const char * map)
6138 {
6139     return SetCharTableEsc(table, map, "");
6140 }
6141
6142 void
6143 Prelude (Board board)
6144 {       // [HGM] superchess: random selection of exo-pieces
6145         int i, j, k; ChessSquare p;
6146         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6147
6148         GetPositionNumber(); // use FRC position number
6149
6150         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6151             SetCharTable(pieceToChar, appData.pieceToCharTable);
6152             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6153                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6154         }
6155
6156         j = seed%4;                 seed /= 4;
6157         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6158         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6159         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6160         j = seed%3 + (seed%3 >= j); seed /= 3;
6161         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6162         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6163         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6164         j = seed%3;                 seed /= 3;
6165         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6166         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6167         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6168         j = seed%2 + (seed%2 >= j); seed /= 2;
6169         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6170         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6171         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6172         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6173         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6174         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6175         put(board, exoPieces[0],    0, 0, ANY);
6176         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6177 }
6178
6179 void
6180 InitPosition (int redraw)
6181 {
6182     ChessSquare (* pieces)[BOARD_FILES];
6183     int i, j, pawnRow=1, pieceRows=1, overrule,
6184     oldx = gameInfo.boardWidth,
6185     oldy = gameInfo.boardHeight,
6186     oldh = gameInfo.holdingsWidth;
6187     static int oldv;
6188
6189     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6190
6191     /* [AS] Initialize pv info list [HGM] and game status */
6192     {
6193         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6194             pvInfoList[i].depth = 0;
6195             boards[i][EP_STATUS] = EP_NONE;
6196             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6197         }
6198
6199         initialRulePlies = 0; /* 50-move counter start */
6200
6201         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6202         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6203     }
6204
6205
6206     /* [HGM] logic here is completely changed. In stead of full positions */
6207     /* the initialized data only consist of the two backranks. The switch */
6208     /* selects which one we will use, which is than copied to the Board   */
6209     /* initialPosition, which for the rest is initialized by Pawns and    */
6210     /* empty squares. This initial position is then copied to boards[0],  */
6211     /* possibly after shuffling, so that it remains available.            */
6212
6213     gameInfo.holdingsWidth = 0; /* default board sizes */
6214     gameInfo.boardWidth    = 8;
6215     gameInfo.boardHeight   = 8;
6216     gameInfo.holdingsSize  = 0;
6217     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6218     for(i=0; i<BOARD_FILES-6; i++)
6219       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6220     initialPosition[EP_STATUS] = EP_NONE;
6221     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6222     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6223     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6224          SetCharTable(pieceNickName, appData.pieceNickNames);
6225     else SetCharTable(pieceNickName, "............");
6226     pieces = FIDEArray;
6227
6228     switch (gameInfo.variant) {
6229     case VariantFischeRandom:
6230       shuffleOpenings = TRUE;
6231       appData.fischerCastling = TRUE;
6232     default:
6233       break;
6234     case VariantShatranj:
6235       pieces = ShatranjArray;
6236       nrCastlingRights = 0;
6237       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6238       break;
6239     case VariantMakruk:
6240       pieces = makrukArray;
6241       nrCastlingRights = 0;
6242       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6243       break;
6244     case VariantASEAN:
6245       pieces = aseanArray;
6246       nrCastlingRights = 0;
6247       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6248       break;
6249     case VariantTwoKings:
6250       pieces = twoKingsArray;
6251       break;
6252     case VariantGrand:
6253       pieces = GrandArray;
6254       nrCastlingRights = 0;
6255       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6256       gameInfo.boardWidth = 10;
6257       gameInfo.boardHeight = 10;
6258       gameInfo.holdingsSize = 7;
6259       break;
6260     case VariantCapaRandom:
6261       shuffleOpenings = TRUE;
6262       appData.fischerCastling = TRUE;
6263     case VariantCapablanca:
6264       pieces = CapablancaArray;
6265       gameInfo.boardWidth = 10;
6266       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6267       break;
6268     case VariantGothic:
6269       pieces = GothicArray;
6270       gameInfo.boardWidth = 10;
6271       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6272       break;
6273     case VariantSChess:
6274       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6275       gameInfo.holdingsSize = 7;
6276       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6277       break;
6278     case VariantJanus:
6279       pieces = JanusArray;
6280       gameInfo.boardWidth = 10;
6281       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6282       nrCastlingRights = 6;
6283         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6284         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6285         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6286         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6287         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6288         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6289       break;
6290     case VariantFalcon:
6291       pieces = FalconArray;
6292       gameInfo.boardWidth = 10;
6293       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6294       break;
6295     case VariantXiangqi:
6296       pieces = XiangqiArray;
6297       gameInfo.boardWidth  = 9;
6298       gameInfo.boardHeight = 10;
6299       nrCastlingRights = 0;
6300       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6301       break;
6302     case VariantShogi:
6303       pieces = ShogiArray;
6304       gameInfo.boardWidth  = 9;
6305       gameInfo.boardHeight = 9;
6306       gameInfo.holdingsSize = 7;
6307       nrCastlingRights = 0;
6308       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6309       break;
6310     case VariantChu:
6311       pieces = ChuArray; pieceRows = 3;
6312       gameInfo.boardWidth  = 12;
6313       gameInfo.boardHeight = 12;
6314       nrCastlingRights = 0;
6315 //      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6316   //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6317       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"
6318                                    "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);
6319       break;
6320     case VariantCourier:
6321       pieces = CourierArray;
6322       gameInfo.boardWidth  = 12;
6323       nrCastlingRights = 0;
6324       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6325       break;
6326     case VariantKnightmate:
6327       pieces = KnightmateArray;
6328       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6329       break;
6330     case VariantSpartan:
6331       pieces = SpartanArray;
6332       SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6333       break;
6334     case VariantLion:
6335       pieces = lionArray;
6336       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6337       break;
6338     case VariantChuChess:
6339       pieces = ChuChessArray;
6340       gameInfo.boardWidth = 10;
6341       gameInfo.boardHeight = 10;
6342       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6343       break;
6344     case VariantFairy:
6345       pieces = fairyArray;
6346       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6347       break;
6348     case VariantGreat:
6349       pieces = GreatArray;
6350       gameInfo.boardWidth = 10;
6351       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6352       gameInfo.holdingsSize = 8;
6353       break;
6354     case VariantSuper:
6355       pieces = FIDEArray;
6356       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6357       gameInfo.holdingsSize = 8;
6358       startedFromSetupPosition = TRUE;
6359       break;
6360     case VariantCrazyhouse:
6361     case VariantBughouse:
6362       pieces = FIDEArray;
6363       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6364       gameInfo.holdingsSize = 5;
6365       break;
6366     case VariantWildCastle:
6367       pieces = FIDEArray;
6368       /* !!?shuffle with kings guaranteed to be on d or e file */
6369       shuffleOpenings = 1;
6370       break;
6371     case VariantNoCastle:
6372       /* !!?unconstrained back-rank shuffle */
6373       shuffleOpenings = 1;
6374     case VariantSuicide:
6375       pieces = FIDEArray;
6376       nrCastlingRights = 0;
6377       break;
6378     }
6379
6380     overrule = 0;
6381     if(appData.NrFiles >= 0) {
6382         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6383         gameInfo.boardWidth = appData.NrFiles;
6384     }
6385     if(appData.NrRanks >= 0) {
6386         gameInfo.boardHeight = appData.NrRanks;
6387     }
6388     if(appData.holdingsSize >= 0) {
6389         i = appData.holdingsSize;
6390 //        if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6391         gameInfo.holdingsSize = i;
6392     }
6393     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6394     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6395         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6396
6397     if(!handSize) handSize = BOARD_HEIGHT;
6398     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6399     if(pawnRow < 1) pawnRow = 1;
6400     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6401        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6402     if(gameInfo.variant == VariantChu) pawnRow = 3;
6403
6404     /* User pieceToChar list overrules defaults */
6405     if(appData.pieceToCharTable != NULL)
6406         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6407
6408     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6409
6410         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6411             s = (ChessSquare) 0; /* account holding counts in guard band */
6412         for( i=0; i<BOARD_HEIGHT; i++ )
6413             initialPosition[i][j] = s;
6414
6415         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6416         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6417         initialPosition[pawnRow][j] = WhitePawn;
6418         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6419         if(gameInfo.variant == VariantXiangqi) {
6420             if(j&1) {
6421                 initialPosition[pawnRow][j] =
6422                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6423                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6424                    initialPosition[2][j] = WhiteCannon;
6425                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6426                 }
6427             }
6428         }
6429         if(gameInfo.variant == VariantChu) {
6430              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6431                initialPosition[pawnRow+1][j] = WhiteCobra,
6432                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6433              for(i=1; i<pieceRows; i++) {
6434                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6435                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6436              }
6437         }
6438         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6439             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6440                initialPosition[0][j] = WhiteRook;
6441                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6442             }
6443         }
6444         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6445     }
6446     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6447     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6448
6449             j=BOARD_LEFT+1;
6450             initialPosition[1][j] = WhiteBishop;
6451             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6452             j=BOARD_RGHT-2;
6453             initialPosition[1][j] = WhiteRook;
6454             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6455     }
6456
6457     if( nrCastlingRights == -1) {
6458         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6459         /*       This sets default castling rights from none to normal corners   */
6460         /* Variants with other castling rights must set them themselves above    */
6461         nrCastlingRights = 6;
6462
6463         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6464         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6465         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6466         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6467         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6468         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6469      }
6470
6471      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6472      if(gameInfo.variant == VariantGreat) { // promotion commoners
6473         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6474         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6475         initialPosition[handSize-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6476         initialPosition[handSize-1-PieceToNumber(WhiteMan)][1] = 9;
6477      }
6478      if( gameInfo.variant == VariantSChess ) {
6479       initialPosition[1][0] = BlackMarshall;
6480       initialPosition[2][0] = BlackAngel;
6481       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6482       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6483       initialPosition[1][1] = initialPosition[2][1] =
6484       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6485      }
6486      initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6487   if (appData.debugMode) {
6488     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6489   }
6490     if(shuffleOpenings) {
6491         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6492         startedFromSetupPosition = TRUE;
6493     }
6494     if(startedFromPositionFile) {
6495       /* [HGM] loadPos: use PositionFile for every new game */
6496       CopyBoard(initialPosition, filePosition);
6497       for(i=0; i<nrCastlingRights; i++)
6498           initialRights[i] = filePosition[CASTLING][i];
6499       startedFromSetupPosition = TRUE;
6500     }
6501     if(*appData.men) LoadPieceDesc(appData.men);
6502
6503     CopyBoard(boards[0], initialPosition);
6504
6505     if(oldx != gameInfo.boardWidth ||
6506        oldy != gameInfo.boardHeight ||
6507        oldv != gameInfo.variant ||
6508        oldh != gameInfo.holdingsWidth
6509                                          )
6510             InitDrawingSizes(-2 ,0);
6511
6512     oldv = gameInfo.variant;
6513     if (redraw)
6514       DrawPosition(TRUE, boards[currentMove]);
6515 }
6516
6517 void
6518 SendBoard (ChessProgramState *cps, int moveNum)
6519 {
6520     char message[MSG_SIZ];
6521
6522     if (cps->useSetboard) {
6523       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6524       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6525       SendToProgram(message, cps);
6526       free(fen);
6527
6528     } else {
6529       ChessSquare *bp;
6530       int i, j, left=0, right=BOARD_WIDTH;
6531       /* Kludge to set black to move, avoiding the troublesome and now
6532        * deprecated "black" command.
6533        */
6534       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6535         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6536
6537       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6538
6539       SendToProgram("edit\n", cps);
6540       SendToProgram("#\n", cps);
6541       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6542         bp = &boards[moveNum][i][left];
6543         for (j = left; j < right; j++, bp++) {
6544           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6545           if ((int) *bp < (int) BlackPawn) {
6546             if(j == BOARD_RGHT+1)
6547                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6548             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6549             if(message[0] == '+' || message[0] == '~') {
6550               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6551                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6552                         AAA + j, ONE + i - '0');
6553             }
6554             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6555                 message[1] = BOARD_RGHT   - 1 - j + '1';
6556                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6557             }
6558             SendToProgram(message, cps);
6559           }
6560         }
6561       }
6562
6563       SendToProgram("c\n", cps);
6564       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6565         bp = &boards[moveNum][i][left];
6566         for (j = left; j < right; j++, bp++) {
6567           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6568           if (((int) *bp != (int) EmptySquare)
6569               && ((int) *bp >= (int) BlackPawn)) {
6570             if(j == BOARD_LEFT-2)
6571                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6572             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6573                     AAA + j, ONE + i - '0');
6574             if(message[0] == '+' || message[0] == '~') {
6575               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6576                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6577                         AAA + j, ONE + i - '0');
6578             }
6579             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6580                 message[1] = BOARD_RGHT   - 1 - j + '1';
6581                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6582             }
6583             SendToProgram(message, cps);
6584           }
6585         }
6586       }
6587
6588       SendToProgram(".\n", cps);
6589     }
6590     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6591 }
6592
6593 char exclusionHeader[MSG_SIZ];
6594 int exCnt, excludePtr;
6595 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6596 static Exclusion excluTab[200];
6597 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6598
6599 static void
6600 WriteMap (int s)
6601 {
6602     int j;
6603     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6604     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6605 }
6606
6607 static void
6608 ClearMap ()
6609 {
6610     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6611     excludePtr = 24; exCnt = 0;
6612     WriteMap(0);
6613 }
6614
6615 static void
6616 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6617 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6618     char buf[2*MOVE_LEN], *p;
6619     Exclusion *e = excluTab;
6620     int i;
6621     for(i=0; i<exCnt; i++)
6622         if(e[i].ff == fromX && e[i].fr == fromY &&
6623            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6624     if(i == exCnt) { // was not in exclude list; add it
6625         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6626         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6627             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6628             return; // abort
6629         }
6630         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6631         excludePtr++; e[i].mark = excludePtr++;
6632         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6633         exCnt++;
6634     }
6635     exclusionHeader[e[i].mark] = state;
6636 }
6637
6638 static int
6639 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6640 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6641     char buf[MSG_SIZ];
6642     int j, k;
6643     ChessMove moveType;
6644     if((signed char)promoChar == -1) { // kludge to indicate best move
6645         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6646             return 1; // if unparsable, abort
6647     }
6648     // update exclusion map (resolving toggle by consulting existing state)
6649     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6650     j = k%8; k >>= 3;
6651     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6652     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6653          excludeMap[k] |=   1<<j;
6654     else excludeMap[k] &= ~(1<<j);
6655     // update header
6656     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6657     // inform engine
6658     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6659     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6660     SendToBoth(buf);
6661     return (state == '+');
6662 }
6663
6664 static void
6665 ExcludeClick (int index)
6666 {
6667     int i, j;
6668     Exclusion *e = excluTab;
6669     if(index < 25) { // none, best or tail clicked
6670         if(index < 13) { // none: include all
6671             WriteMap(0); // clear map
6672             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6673             SendToBoth("include all\n"); // and inform engine
6674         } else if(index > 18) { // tail
6675             if(exclusionHeader[19] == '-') { // tail was excluded
6676                 SendToBoth("include all\n");
6677                 WriteMap(0); // clear map completely
6678                 // now re-exclude selected moves
6679                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6680                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6681             } else { // tail was included or in mixed state
6682                 SendToBoth("exclude all\n");
6683                 WriteMap(0xFF); // fill map completely
6684                 // now re-include selected moves
6685                 j = 0; // count them
6686                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6687                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6688                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6689             }
6690         } else { // best
6691             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6692         }
6693     } else {
6694         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6695             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6696             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6697             break;
6698         }
6699     }
6700 }
6701
6702 ChessSquare
6703 DefaultPromoChoice (int white)
6704 {
6705     ChessSquare result;
6706     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6707        gameInfo.variant == VariantMakruk)
6708         result = WhiteFerz; // no choice
6709     else if(gameInfo.variant == VariantASEAN)
6710         result = WhiteRook; // no choice
6711     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6712         result= WhiteKing; // in Suicide Q is the last thing we want
6713     else if(gameInfo.variant == VariantSpartan)
6714         result = white ? WhiteQueen : WhiteAngel;
6715     else result = WhiteQueen;
6716     if(!white) result = WHITE_TO_BLACK result;
6717     return result;
6718 }
6719
6720 static int autoQueen; // [HGM] oneclick
6721
6722 int
6723 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6724 {
6725     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6726     /* [HGM] add Shogi promotions */
6727     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6728     ChessSquare piece, partner;
6729     ChessMove moveType;
6730     Boolean premove;
6731
6732     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6733     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6734
6735     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6736       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6737         return FALSE;
6738
6739     if(legal[toY][toX] == 4) return FALSE;
6740
6741     piece = boards[currentMove][fromY][fromX];
6742     if(gameInfo.variant == VariantChu) {
6743         promotionZoneSize = (BOARD_HEIGHT - deadRanks)/3;
6744         if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6745         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6746     } else if(gameInfo.variant == VariantShogi) {
6747         promotionZoneSize = (BOARD_HEIGHT- deadRanks)/3 +(BOARD_HEIGHT == 8);
6748         highestPromotingPiece = (int)WhiteAlfil;
6749         if(PieceToChar(piece) != '+' || PieceToChar(CHUPROMOTED(piece)) == '+') highestPromotingPiece = piece;
6750     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6751         promotionZoneSize = 3;
6752     }
6753
6754     // Treat Lance as Pawn when it is not representing Amazon or Lance
6755     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6756         if(piece == WhiteLance) piece = WhitePawn; else
6757         if(piece == BlackLance) piece = BlackPawn;
6758     }
6759
6760     // next weed out all moves that do not touch the promotion zone at all
6761     if((int)piece >= BlackPawn) {
6762         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6763              return FALSE;
6764         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6765         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6766     } else {
6767         if(  toY < BOARD_HEIGHT - deadRanks - promotionZoneSize &&
6768            fromY < BOARD_HEIGHT - deadRanks - promotionZoneSize) return FALSE;
6769         if(fromY >= BOARD_HEIGHT - deadRanks - promotionZoneSize && gameInfo.variant == VariantChuChess)
6770              return FALSE;
6771     }
6772
6773     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6774
6775     // weed out mandatory Shogi promotions
6776     if(gameInfo.variant == VariantShogi) {
6777         if(piece >= BlackPawn) {
6778             if(toY == 0 && piece == BlackPawn ||
6779                toY == 0 && piece == BlackQueen ||
6780                toY <= 1 && piece == BlackKnight) {
6781                 *promoChoice = '+';
6782                 return FALSE;
6783             }
6784         } else {
6785             if(toY == BOARD_HEIGHT-deadRanks-1 && piece == WhitePawn ||
6786                toY == BOARD_HEIGHT-deadRanks-1 && piece == WhiteQueen ||
6787                toY >= BOARD_HEIGHT-deadRanks-2 && piece == WhiteKnight) {
6788                 *promoChoice = '+';
6789                 return FALSE;
6790             }
6791         }
6792     }
6793
6794     // weed out obviously illegal Pawn moves
6795     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6796         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6797         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6798         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6799         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6800         // note we are not allowed to test for valid (non-)capture, due to premove
6801     }
6802
6803     // we either have a choice what to promote to, or (in Shogi) whether to promote
6804     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6805        gameInfo.variant == VariantMakruk) {
6806         ChessSquare p=BlackFerz;  // no choice
6807         while(p < EmptySquare) {  //but make sure we use piece that exists
6808             *promoChoice = PieceToChar(p++);
6809             if(*promoChoice != '.') break;
6810         }
6811         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6812     }
6813     // no sense asking what we must promote to if it is going to explode...
6814     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6815         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6816         return FALSE;
6817     }
6818     // give caller the default choice even if we will not make it
6819     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6820     partner = piece; // pieces can promote if the pieceToCharTable says so
6821     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6822     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6823     if(        sweepSelect && gameInfo.variant != VariantGreat
6824                            && gameInfo.variant != VariantGrand
6825                            && gameInfo.variant != VariantSuper) return FALSE;
6826     if(autoQueen) return FALSE; // predetermined
6827
6828     // suppress promotion popup on illegal moves that are not premoves
6829     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6830               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6831     if(appData.testLegality && !premove) {
6832         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6833                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6834         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6835         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6836             return FALSE;
6837     }
6838
6839     return TRUE;
6840 }
6841
6842 int
6843 InPalace (int row, int column)
6844 {   /* [HGM] for Xiangqi */
6845     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6846          column < (BOARD_WIDTH + 4)/2 &&
6847          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6848     return FALSE;
6849 }
6850
6851 int
6852 PieceForSquare (int x, int y)
6853 {
6854   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) return -1;
6855   if(x == BOARD_RGHT+1 && handOffsets & 1) y += handSize - BOARD_HEIGHT;
6856   if(x == BOARD_LEFT-2 && !(handOffsets & 2)) y += handSize - BOARD_HEIGHT;
6857      return boards[currentMove][y][x];
6858 }
6859
6860 ChessSquare
6861 More (Board board, int col, int start, int end)
6862 {
6863     int k;
6864     for(k=start; k<end; k++) if(board[k][col]) return (col == 1 ? WhiteMonarch : BlackMonarch); // arrow image
6865     return EmptySquare;
6866 }
6867
6868 void
6869 DrawPosition (int repaint, Board board)
6870 {
6871     Board compactedBoard;
6872     if(handSize > BOARD_HEIGHT && board) {
6873         int k;
6874         CopyBoard(compactedBoard, board);
6875         if(handOffsets & 1) {
6876             for(k=0; k<BOARD_HEIGHT; k++) {
6877                 compactedBoard[k][BOARD_WIDTH-1] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-1];
6878                 compactedBoard[k][BOARD_WIDTH-2] = board[k+handSize-BOARD_HEIGHT][BOARD_WIDTH-2];
6879             }
6880             compactedBoard[0][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, 0, handSize-BOARD_HEIGHT+1);
6881         } else compactedBoard[BOARD_HEIGHT-1][BOARD_WIDTH-1] = More(board, BOARD_WIDTH-2, BOARD_HEIGHT-1, handSize);
6882         if(!(handOffsets & 2)) {
6883             for(k=0; k<BOARD_HEIGHT; k++) {
6884                 compactedBoard[k][0] = board[k+handSize-BOARD_HEIGHT][0];
6885                 compactedBoard[k][1] = board[k+handSize-BOARD_HEIGHT][1];
6886             }
6887             compactedBoard[0][0] = More(board, 1, 0, handSize-BOARD_HEIGHT+1);
6888         } else compactedBoard[BOARD_HEIGHT-1][0] = More(board, 1, BOARD_HEIGHT-1, handSize);
6889         DrawPositionX(TRUE, compactedBoard);
6890     } else DrawPositionX(repaint, board);
6891 }
6892
6893 int
6894 OKToStartUserMove (int x, int y)
6895 {
6896     ChessSquare from_piece;
6897     int white_piece;
6898
6899     if (matchMode) return FALSE;
6900     if (gameMode == EditPosition) return TRUE;
6901
6902     if (x >= 0 && y >= 0)
6903       from_piece = boards[currentMove][y][x];
6904     else
6905       from_piece = EmptySquare;
6906
6907     if (from_piece == EmptySquare) return FALSE;
6908
6909     white_piece = (int)from_piece >= (int)WhitePawn &&
6910       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6911
6912     switch (gameMode) {
6913       case AnalyzeFile:
6914       case TwoMachinesPlay:
6915       case EndOfGame:
6916         return FALSE;
6917
6918       case IcsObserving:
6919       case IcsIdle:
6920         return FALSE;
6921
6922       case MachinePlaysWhite:
6923       case IcsPlayingBlack:
6924         if (appData.zippyPlay) return FALSE;
6925         if (white_piece) {
6926             DisplayMoveError(_("You are playing Black"));
6927             return FALSE;
6928         }
6929         break;
6930
6931       case MachinePlaysBlack:
6932       case IcsPlayingWhite:
6933         if (appData.zippyPlay) return FALSE;
6934         if (!white_piece) {
6935             DisplayMoveError(_("You are playing White"));
6936             return FALSE;
6937         }
6938         break;
6939
6940       case PlayFromGameFile:
6941             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6942       case EditGame:
6943       case AnalyzeMode:
6944         if (!white_piece && WhiteOnMove(currentMove)) {
6945             DisplayMoveError(_("It is White's turn"));
6946             return FALSE;
6947         }
6948         if (white_piece && !WhiteOnMove(currentMove)) {
6949             DisplayMoveError(_("It is Black's turn"));
6950             return FALSE;
6951         }
6952         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6953             /* Editing correspondence game history */
6954             /* Could disallow this or prompt for confirmation */
6955             cmailOldMove = -1;
6956         }
6957         break;
6958
6959       case BeginningOfGame:
6960         if (appData.icsActive) return FALSE;
6961         if (!appData.noChessProgram) {
6962             if (!white_piece) {
6963                 DisplayMoveError(_("You are playing White"));
6964                 return FALSE;
6965             }
6966         }
6967         break;
6968
6969       case Training:
6970         if (!white_piece && WhiteOnMove(currentMove)) {
6971             DisplayMoveError(_("It is White's turn"));
6972             return FALSE;
6973         }
6974         if (white_piece && !WhiteOnMove(currentMove)) {
6975             DisplayMoveError(_("It is Black's turn"));
6976             return FALSE;
6977         }
6978         break;
6979
6980       default:
6981       case IcsExamining:
6982         break;
6983     }
6984     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6985         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6986         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6987         && gameMode != AnalyzeFile && gameMode != Training) {
6988         DisplayMoveError(_("Displayed position is not current"));
6989         return FALSE;
6990     }
6991     return TRUE;
6992 }
6993
6994 Boolean
6995 OnlyMove (int *x, int *y, Boolean captures)
6996 {
6997     DisambiguateClosure cl;
6998     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6999     switch(gameMode) {
7000       case MachinePlaysBlack:
7001       case IcsPlayingWhite:
7002       case BeginningOfGame:
7003         if(!WhiteOnMove(currentMove)) return FALSE;
7004         break;
7005       case MachinePlaysWhite:
7006       case IcsPlayingBlack:
7007         if(WhiteOnMove(currentMove)) return FALSE;
7008         break;
7009       case EditGame:
7010         break;
7011       default:
7012         return FALSE;
7013     }
7014     cl.pieceIn = EmptySquare;
7015     cl.rfIn = *y;
7016     cl.ffIn = *x;
7017     cl.rtIn = -1;
7018     cl.ftIn = -1;
7019     cl.promoCharIn = NULLCHAR;
7020     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7021     if( cl.kind == NormalMove ||
7022         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7023         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7024         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7025       fromX = cl.ff;
7026       fromY = cl.rf;
7027       *x = cl.ft;
7028       *y = cl.rt;
7029       return TRUE;
7030     }
7031     if(cl.kind != ImpossibleMove) return FALSE;
7032     cl.pieceIn = EmptySquare;
7033     cl.rfIn = -1;
7034     cl.ffIn = -1;
7035     cl.rtIn = *y;
7036     cl.ftIn = *x;
7037     cl.promoCharIn = NULLCHAR;
7038     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
7039     if( cl.kind == NormalMove ||
7040         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
7041         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
7042         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
7043       fromX = cl.ff;
7044       fromY = cl.rf;
7045       *x = cl.ft;
7046       *y = cl.rt;
7047       autoQueen = TRUE; // act as if autoQueen on when we click to-square
7048       return TRUE;
7049     }
7050     return FALSE;
7051 }
7052
7053 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
7054 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
7055 int lastLoadGameUseList = FALSE;
7056 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
7057 ChessMove lastLoadGameStart = EndOfFile;
7058 int doubleClick;
7059 Boolean addToBookFlag;
7060 static Board rightsBoard, nullBoard;
7061
7062 void
7063 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7064 {
7065     ChessMove moveType;
7066     ChessSquare pup;
7067     int ff=fromX, rf=fromY, ft=toX, rt=toY;
7068
7069     /* Check if the user is playing in turn.  This is complicated because we
7070        let the user "pick up" a piece before it is his turn.  So the piece he
7071        tried to pick up may have been captured by the time he puts it down!
7072        Therefore we use the color the user is supposed to be playing in this
7073        test, not the color of the piece that is currently on the starting
7074        square---except in EditGame mode, where the user is playing both
7075        sides; fortunately there the capture race can't happen.  (It can
7076        now happen in IcsExamining mode, but that's just too bad.  The user
7077        will get a somewhat confusing message in that case.)
7078        */
7079
7080     switch (gameMode) {
7081       case AnalyzeFile:
7082       case TwoMachinesPlay:
7083       case EndOfGame:
7084       case IcsObserving:
7085       case IcsIdle:
7086         /* We switched into a game mode where moves are not accepted,
7087            perhaps while the mouse button was down. */
7088         return;
7089
7090       case MachinePlaysWhite:
7091         /* User is moving for Black */
7092         if (WhiteOnMove(currentMove)) {
7093             DisplayMoveError(_("It is White's turn"));
7094             return;
7095         }
7096         break;
7097
7098       case MachinePlaysBlack:
7099         /* User is moving for White */
7100         if (!WhiteOnMove(currentMove)) {
7101             DisplayMoveError(_("It is Black's turn"));
7102             return;
7103         }
7104         break;
7105
7106       case PlayFromGameFile:
7107             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7108       case EditGame:
7109       case IcsExamining:
7110       case BeginningOfGame:
7111       case AnalyzeMode:
7112       case Training:
7113         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7114         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7115             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7116             /* User is moving for Black */
7117             if (WhiteOnMove(currentMove)) {
7118                 DisplayMoveError(_("It is White's turn"));
7119                 return;
7120             }
7121         } else {
7122             /* User is moving for White */
7123             if (!WhiteOnMove(currentMove)) {
7124                 DisplayMoveError(_("It is Black's turn"));
7125                 return;
7126             }
7127         }
7128         break;
7129
7130       case IcsPlayingBlack:
7131         /* User is moving for Black */
7132         if (WhiteOnMove(currentMove)) {
7133             if (!appData.premove) {
7134                 DisplayMoveError(_("It is White's turn"));
7135             } else if (toX >= 0 && toY >= 0) {
7136                 premoveToX = toX;
7137                 premoveToY = toY;
7138                 premoveFromX = fromX;
7139                 premoveFromY = fromY;
7140                 premovePromoChar = promoChar;
7141                 gotPremove = 1;
7142                 if (appData.debugMode)
7143                     fprintf(debugFP, "Got premove: fromX %d,"
7144                             "fromY %d, toX %d, toY %d\n",
7145                             fromX, fromY, toX, toY);
7146             }
7147             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7148             return;
7149         }
7150         break;
7151
7152       case IcsPlayingWhite:
7153         /* User is moving for White */
7154         if (!WhiteOnMove(currentMove)) {
7155             if (!appData.premove) {
7156                 DisplayMoveError(_("It is Black's turn"));
7157             } else if (toX >= 0 && toY >= 0) {
7158                 premoveToX = toX;
7159                 premoveToY = toY;
7160                 premoveFromX = fromX;
7161                 premoveFromY = fromY;
7162                 premovePromoChar = promoChar;
7163                 gotPremove = 1;
7164                 if (appData.debugMode)
7165                     fprintf(debugFP, "Got premove: fromX %d,"
7166                             "fromY %d, toX %d, toY %d\n",
7167                             fromX, fromY, toX, toY);
7168             }
7169             DrawPosition(TRUE, boards[currentMove]);
7170             return;
7171         }
7172         break;
7173
7174       default:
7175         break;
7176
7177       case EditPosition:
7178         /* EditPosition, empty square, or different color piece;
7179            click-click move is possible */
7180         if (toX == -2 || toY == -2) {
7181             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7182             DrawPosition(FALSE, boards[currentMove]);
7183             return;
7184         } else if (toX >= 0 && toY >= 0) {
7185             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7186                 ChessSquare p = boards[0][rf][ff];
7187                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7188                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7189                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7190                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7191                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7192                     gatingPiece = p;
7193                 }
7194             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7195             boards[0][toY][toX] = boards[0][fromY][fromX];
7196             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7197                 if(boards[0][fromY][0] != EmptySquare) {
7198                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7199                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7200                 }
7201             } else
7202             if(fromX == BOARD_RGHT+1) {
7203                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7204                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7205                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7206                 }
7207             } else
7208             boards[0][fromY][fromX] = gatingPiece;
7209             ClearHighlights();
7210             DrawPosition(FALSE, boards[currentMove]);
7211             return;
7212         }
7213         return;
7214     }
7215
7216     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7217     pup = boards[currentMove][toY][toX];
7218
7219     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7220     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7221          if( pup != EmptySquare ) return;
7222          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7223            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7224                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7225            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7226            if(fromX == 0) fromY = handSize-1 - fromY; // black holdings upside-down
7227            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7228            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7229          fromY = DROP_RANK;
7230     }
7231
7232     /* [HGM] always test for legality, to get promotion info */
7233     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7234                                          fromY, fromX, toY, toX, promoChar);
7235
7236     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7237
7238     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7239
7240     /* [HGM] but possibly ignore an IllegalMove result */
7241     if (appData.testLegality) {
7242         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7243             DisplayMoveError(_("Illegal move"));
7244             return;
7245         }
7246     }
7247
7248     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7249         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7250              ClearPremoveHighlights(); // was included
7251         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7252         DrawPosition(FALSE, NULL);
7253         return;
7254     }
7255
7256     if(addToBookFlag) { // adding moves to book
7257         char buf[MSG_SIZ], move[MSG_SIZ];
7258         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7259         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7260                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7261         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7262         AddBookMove(buf);
7263         addToBookFlag = FALSE;
7264         ClearHighlights();
7265         return;
7266     }
7267
7268     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7269 }
7270
7271 /* Common tail of UserMoveEvent and DropMenuEvent */
7272 int
7273 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7274 {
7275     char *bookHit = 0;
7276
7277     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7278         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7279         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7280         if(WhiteOnMove(currentMove)) {
7281             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7282         } else {
7283             if(!boards[currentMove][handSize-1-k][1]) return 0;
7284         }
7285     }
7286
7287     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7288        move type in caller when we know the move is a legal promotion */
7289     if(moveType == NormalMove && promoChar)
7290         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7291
7292     /* [HGM] <popupFix> The following if has been moved here from
7293        UserMoveEvent(). Because it seemed to belong here (why not allow
7294        piece drops in training games?), and because it can only be
7295        performed after it is known to what we promote. */
7296     if (gameMode == Training) {
7297       /* compare the move played on the board to the next move in the
7298        * game. If they match, display the move and the opponent's response.
7299        * If they don't match, display an error message.
7300        */
7301       int saveAnimate;
7302       Board testBoard;
7303       CopyBoard(testBoard, boards[currentMove]);
7304       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7305
7306       if (CompareBoards(testBoard, boards[currentMove+1])) {
7307         ForwardInner(currentMove+1);
7308
7309         /* Autoplay the opponent's response.
7310          * if appData.animate was TRUE when Training mode was entered,
7311          * the response will be animated.
7312          */
7313         saveAnimate = appData.animate;
7314         appData.animate = animateTraining;
7315         ForwardInner(currentMove+1);
7316         appData.animate = saveAnimate;
7317
7318         /* check for the end of the game */
7319         if (currentMove >= forwardMostMove) {
7320           gameMode = PlayFromGameFile;
7321           ModeHighlight();
7322           SetTrainingModeOff();
7323           DisplayInformation(_("End of game"));
7324         }
7325       } else {
7326         DisplayError(_("Incorrect move"), 0);
7327       }
7328       return 1;
7329     }
7330
7331   /* Ok, now we know that the move is good, so we can kill
7332      the previous line in Analysis Mode */
7333   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7334                                 && currentMove < forwardMostMove) {
7335     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7336     else forwardMostMove = currentMove;
7337   }
7338
7339   ClearMap();
7340
7341   /* If we need the chess program but it's dead, restart it */
7342   ResurrectChessProgram();
7343
7344   /* A user move restarts a paused game*/
7345   if (pausing)
7346     PauseEvent();
7347
7348   thinkOutput[0] = NULLCHAR;
7349
7350   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7351
7352   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7353     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7354     return 1;
7355   }
7356
7357   if (gameMode == BeginningOfGame) {
7358     if (appData.noChessProgram) {
7359       gameMode = EditGame;
7360       SetGameInfo();
7361     } else {
7362       char buf[MSG_SIZ];
7363       gameMode = MachinePlaysBlack;
7364       StartClocks();
7365       SetGameInfo();
7366       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7367       DisplayTitle(buf);
7368       if (first.sendName) {
7369         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7370         SendToProgram(buf, &first);
7371       }
7372       StartClocks();
7373     }
7374     ModeHighlight();
7375   }
7376
7377   /* Relay move to ICS or chess engine */
7378   if (appData.icsActive) {
7379     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7380         gameMode == IcsExamining) {
7381       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7382         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7383         SendToICS("draw ");
7384         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7385       }
7386       // also send plain move, in case ICS does not understand atomic claims
7387       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7388       ics_user_moved = 1;
7389     }
7390   } else {
7391     if (first.sendTime && (gameMode == BeginningOfGame ||
7392                            gameMode == MachinePlaysWhite ||
7393                            gameMode == MachinePlaysBlack)) {
7394       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7395     }
7396     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7397          // [HGM] book: if program might be playing, let it use book
7398         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7399         first.maybeThinking = TRUE;
7400     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7401         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7402         SendBoard(&first, currentMove+1);
7403         if(second.analyzing) {
7404             if(!second.useSetboard) SendToProgram("undo\n", &second);
7405             SendBoard(&second, currentMove+1);
7406         }
7407     } else {
7408         SendMoveToProgram(forwardMostMove-1, &first);
7409         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7410     }
7411     if (currentMove == cmailOldMove + 1) {
7412       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7413     }
7414   }
7415
7416   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7417
7418   switch (gameMode) {
7419   case EditGame:
7420     if(appData.testLegality)
7421     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7422     case MT_NONE:
7423     case MT_CHECK:
7424       break;
7425     case MT_CHECKMATE:
7426     case MT_STAINMATE:
7427       if (WhiteOnMove(currentMove)) {
7428         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7429       } else {
7430         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7431       }
7432       break;
7433     case MT_STALEMATE:
7434       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7435       break;
7436     }
7437     break;
7438
7439   case MachinePlaysBlack:
7440   case MachinePlaysWhite:
7441     /* disable certain menu options while machine is thinking */
7442     SetMachineThinkingEnables();
7443     break;
7444
7445   default:
7446     break;
7447   }
7448
7449   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7450   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7451
7452   if(bookHit) { // [HGM] book: simulate book reply
7453         static char bookMove[MSG_SIZ]; // a bit generous?
7454
7455         programStats.nodes = programStats.depth = programStats.time =
7456         programStats.score = programStats.got_only_move = 0;
7457         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7458
7459         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7460         strcat(bookMove, bookHit);
7461         HandleMachineMove(bookMove, &first);
7462   }
7463   return 1;
7464 }
7465
7466 void
7467 MarkByFEN(char *fen)
7468 {
7469         int r, f;
7470         if(!appData.markers || !appData.highlightDragging) return;
7471         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7472         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7473         while(*fen) {
7474             int s = 0;
7475             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7476             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7477             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7478             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7479             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7480             if(*fen == 'T') marker[r][f++] = 0; else
7481             if(*fen == 'Y') marker[r][f++] = 1; else
7482             if(*fen == 'G') marker[r][f++] = 3; else
7483             if(*fen == 'B') marker[r][f++] = 4; else
7484             if(*fen == 'C') marker[r][f++] = 5; else
7485             if(*fen == 'M') marker[r][f++] = 6; else
7486             if(*fen == 'W') marker[r][f++] = 7; else
7487             if(*fen == 'D') marker[r][f++] = 8; else
7488             if(*fen == 'R') marker[r][f++] = 2; else {
7489                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7490               f += s; fen -= s>0;
7491             }
7492             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7493             if(r < 0) break;
7494             fen++;
7495         }
7496         DrawPosition(TRUE, NULL);
7497 }
7498
7499 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7500
7501 void
7502 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7503 {
7504     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7505     Markers *m = (Markers *) closure;
7506     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7507                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7508         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7509                          || kind == WhiteCapturesEnPassant
7510                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7511     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7512 }
7513
7514 static int hoverSavedValid;
7515
7516 void
7517 MarkTargetSquares (int clear)
7518 {
7519   int x, y, sum=0;
7520   if(clear) { // no reason to ever suppress clearing
7521     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7522     hoverSavedValid = 0;
7523     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7524   } else {
7525     int capt = 0;
7526     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7527        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7528     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7529     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7530       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7531       if(capt)
7532       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;
7533     }
7534   }
7535   DrawPosition(FALSE, NULL);
7536 }
7537
7538 int
7539 Explode (Board board, int fromX, int fromY, int toX, int toY)
7540 {
7541     if(gameInfo.variant == VariantAtomic &&
7542        (board[toY][toX] != EmptySquare ||                     // capture?
7543         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7544                          board[fromY][fromX] == BlackPawn   )
7545       )) {
7546         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7547         return TRUE;
7548     }
7549     return FALSE;
7550 }
7551
7552 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7553
7554 int
7555 CanPromote (ChessSquare piece, int y)
7556 {
7557         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7558         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7559         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7560         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7561            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7562           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7563            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7564         return (piece == BlackPawn && y <= zone ||
7565                 piece == WhitePawn && y >= BOARD_HEIGHT-1-deadRanks-zone ||
7566                 piece == BlackLance && y <= zone ||
7567                 piece == WhiteLance && y >= BOARD_HEIGHT-1-deadRanks-zone );
7568 }
7569
7570 void
7571 HoverEvent (int xPix, int yPix, int x, int y)
7572 {
7573         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7574         int r, f;
7575         if(!first.highlight) return;
7576         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7577         if(x == oldX && y == oldY) return; // only do something if we enter new square
7578         oldFromX = fromX; oldFromY = fromY;
7579         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7580           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7581             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7582           hoverSavedValid = 1;
7583         } else if(oldX != x || oldY != y) {
7584           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7585           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7586           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7587             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7588           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7589             char buf[MSG_SIZ];
7590             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7591             SendToProgram(buf, &first);
7592           }
7593           oldX = x; oldY = y;
7594 //        SetHighlights(fromX, fromY, x, y);
7595         }
7596 }
7597
7598 void ReportClick(char *action, int x, int y)
7599 {
7600         char buf[MSG_SIZ]; // Inform engine of what user does
7601         int r, f;
7602         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7603           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7604             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7605         if(!first.highlight || gameMode == EditPosition) return;
7606         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7607         SendToProgram(buf, &first);
7608 }
7609
7610 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7611 Boolean deferChoice;
7612 int createX = -1, createY = -1; // square where we last created a piece in EditPosition mode
7613
7614 void
7615 LeftClick (ClickType clickType, int xPix, int yPix)
7616 {
7617     int x, y;
7618     static Boolean saveAnimate;
7619     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7620     char promoChoice = NULLCHAR;
7621     ChessSquare piece;
7622     static TimeMark lastClickTime, prevClickTime;
7623
7624     if(flashing) return;
7625
7626   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7627     x = EventToSquare(xPix, BOARD_WIDTH);
7628     y = EventToSquare(yPix, BOARD_HEIGHT);
7629     if (!flipView && y >= 0) {
7630         y = BOARD_HEIGHT - 1 - y;
7631     }
7632     if (flipView && x >= 0) {
7633         x = BOARD_WIDTH - 1 - x;
7634     }
7635
7636     // map clicks in offsetted holdings back to true coords (or switch the offset)
7637     if(x == BOARD_RGHT+1) {
7638         if(handOffsets & 1) {
7639             if(y == 0) { handOffsets &= ~1; DrawPosition(TRUE, boards[currentMove]); return; }
7640             y += handSize - BOARD_HEIGHT;
7641         } else if(y == BOARD_HEIGHT-1) { handOffsets |= 1; DrawPosition(TRUE, boards[currentMove]); return; }
7642     }
7643     if(x == BOARD_LEFT-2) {
7644         if(!(handOffsets & 2)) {
7645             if(y == 0) { handOffsets |= 2; DrawPosition(TRUE, boards[currentMove]); return; }
7646             y += handSize - BOARD_HEIGHT;
7647         } else if(y == BOARD_HEIGHT-1) { handOffsets &= ~2; DrawPosition(TRUE, boards[currentMove]); return; }
7648     }
7649
7650     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && 
7651         (boards[currentMove][y][x] == EmptySquare || x == createX && y == createY) ) {
7652         static int dummy;
7653         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7654         right = TRUE;
7655         return;
7656     }
7657
7658     createX = createY = -1;
7659
7660     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7661
7662     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7663
7664     if (clickType == Press) ErrorPopDown();
7665     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7666
7667     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7668         defaultPromoChoice = promoSweep;
7669         promoSweep = EmptySquare;   // terminate sweep
7670         promoDefaultAltered = TRUE;
7671         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7672     }
7673
7674     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7675         if(clickType == Release) return; // ignore upclick of click-click destination
7676         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7677         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7678         if(gameInfo.holdingsWidth &&
7679                 (WhiteOnMove(currentMove)
7680                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7681                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7682             // click in right holdings, for determining promotion piece
7683             ChessSquare p = boards[currentMove][y][x];
7684             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7685             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7686             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7687                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7688                 fromX = fromY = -1;
7689                 return;
7690             }
7691         }
7692         DrawPosition(FALSE, boards[currentMove]);
7693         return;
7694     }
7695
7696     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7697     if(clickType == Press
7698             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7699               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7700               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7701         return;
7702
7703     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7704         // could be static click on premove from-square: abort premove
7705         gotPremove = 0;
7706         ClearPremoveHighlights();
7707     }
7708
7709     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7710         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7711
7712     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7713         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7714                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7715         defaultPromoChoice = DefaultPromoChoice(side);
7716     }
7717
7718     autoQueen = appData.alwaysPromoteToQueen;
7719
7720     if (fromX == -1) {
7721       int originalY = y;
7722       gatingPiece = EmptySquare;
7723       if (clickType != Press) {
7724         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7725             DragPieceEnd(xPix, yPix); dragging = 0;
7726             DrawPosition(FALSE, NULL);
7727         }
7728         return;
7729       }
7730       doubleClick = FALSE;
7731       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7732         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7733       }
7734       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7735       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7736          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7737          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7738             /* First square */
7739             if (OKToStartUserMove(fromX, fromY)) {
7740                 second = 0;
7741                 ReportClick("lift", x, y);
7742                 MarkTargetSquares(0);
7743                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7744                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7745                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7746                     promoSweep = defaultPromoChoice;
7747                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7748                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7749                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7750                 }
7751                 if (appData.highlightDragging) {
7752                     SetHighlights(fromX, fromY, -1, -1);
7753                 } else {
7754                     ClearHighlights();
7755                 }
7756             } else fromX = fromY = -1;
7757             return;
7758         }
7759     }
7760
7761     /* fromX != -1 */
7762     if (clickType == Press && gameMode != EditPosition) {
7763         ChessSquare fromP;
7764         ChessSquare toP;
7765         int frc;
7766
7767         // ignore off-board to clicks
7768         if(y < 0 || x < 0) return;
7769
7770         /* Check if clicking again on the same color piece */
7771         fromP = boards[currentMove][fromY][fromX];
7772         toP = boards[currentMove][y][x];
7773         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7774         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7775             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7776            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7777              WhitePawn <= toP && toP <= WhiteKing &&
7778              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7779              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7780             (BlackPawn <= fromP && fromP <= BlackKing &&
7781              BlackPawn <= toP && toP <= BlackKing &&
7782              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7783              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7784             /* Clicked again on same color piece -- changed his mind */
7785             second = (x == fromX && y == fromY);
7786             killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7787             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7788                 second = FALSE; // first double-click rather than scond click
7789                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7790             }
7791             promoDefaultAltered = FALSE;
7792            if(!second) MarkTargetSquares(1);
7793            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7794             if (appData.highlightDragging) {
7795                 SetHighlights(x, y, -1, -1);
7796             } else {
7797                 ClearHighlights();
7798             }
7799             if (OKToStartUserMove(x, y)) {
7800                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7801                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7802                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7803                  gatingPiece = boards[currentMove][fromY][fromX];
7804                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7805                 fromX = x;
7806                 fromY = y; dragging = 1;
7807                 if(!second) ReportClick("lift", x, y);
7808                 MarkTargetSquares(0);
7809                 DragPieceBegin(xPix, yPix, FALSE);
7810                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7811                     promoSweep = defaultPromoChoice;
7812                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7813                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7814                 }
7815             }
7816            }
7817            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7818            second = FALSE;
7819         }
7820         // ignore clicks on holdings
7821         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7822     }
7823
7824     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7825         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7826         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7827         return;
7828     }
7829
7830     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7831         DragPieceEnd(xPix, yPix); dragging = 0;
7832         if(clearFlag) {
7833             // a deferred attempt to click-click move an empty square on top of a piece
7834             boards[currentMove][y][x] = EmptySquare;
7835             ClearHighlights();
7836             DrawPosition(FALSE, boards[currentMove]);
7837             fromX = fromY = -1; clearFlag = 0;
7838             return;
7839         }
7840         if (appData.animateDragging) {
7841             /* Undo animation damage if any */
7842             DrawPosition(FALSE, NULL);
7843         }
7844         if (second) {
7845             /* Second up/down in same square; just abort move */
7846             second = 0;
7847             fromX = fromY = -1;
7848             gatingPiece = EmptySquare;
7849             ClearHighlights();
7850             gotPremove = 0;
7851             ClearPremoveHighlights();
7852             MarkTargetSquares(-1);
7853             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7854         } else {
7855             /* First upclick in same square; start click-click mode */
7856             SetHighlights(x, y, -1, -1);
7857         }
7858         return;
7859     }
7860
7861     clearFlag = 0;
7862
7863     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7864        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7865         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7866         DisplayMessage(_("only marked squares are legal"),"");
7867         DrawPosition(TRUE, NULL);
7868         return; // ignore to-click
7869     }
7870
7871     /* we now have a different from- and (possibly off-board) to-square */
7872     /* Completed move */
7873     if(!sweepSelecting) {
7874         toX = x;
7875         toY = y;
7876     }
7877
7878     piece = boards[currentMove][fromY][fromX];
7879
7880     saveAnimate = appData.animate;
7881     if (clickType == Press) {
7882         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7883         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7884             // must be Edit Position mode with empty-square selected
7885             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7886             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7887             return;
7888         }
7889         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7890             return;
7891         }
7892         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7893             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7894         } else
7895         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7896         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7897           if(appData.sweepSelect) {
7898             promoSweep = defaultPromoChoice;
7899             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7900             selectFlag = 0; lastX = xPix; lastY = yPix;
7901             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7902             saveFlash = appData.flashCount; appData.flashCount = 0;
7903             Sweep(0); // Pawn that is going to promote: preview promotion piece
7904             sweepSelecting = 1;
7905             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7906             MarkTargetSquares(1);
7907           }
7908           return; // promo popup appears on up-click
7909         }
7910         /* Finish clickclick move */
7911         if (appData.animate || appData.highlightLastMove) {
7912             SetHighlights(fromX, fromY, toX, toY);
7913         } else {
7914             ClearHighlights();
7915         }
7916         MarkTargetSquares(1);
7917     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7918         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7919         *promoRestrict = 0; appData.flashCount = saveFlash;
7920         if (appData.animate || appData.highlightLastMove) {
7921             SetHighlights(fromX, fromY, toX, toY);
7922         } else {
7923             ClearHighlights();
7924         }
7925         MarkTargetSquares(1);
7926     } else {
7927 #if 0
7928 // [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
7929         /* Finish drag move */
7930         if (appData.highlightLastMove) {
7931             SetHighlights(fromX, fromY, toX, toY);
7932         } else {
7933             ClearHighlights();
7934         }
7935 #endif
7936         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7937           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7938         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7939         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7940           dragging *= 2;            // flag button-less dragging if we are dragging
7941           MarkTargetSquares(1);
7942           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7943           else {
7944             kill2X = killX; kill2Y = killY;
7945             killX = x; killY = y;     // remember this square as intermediate
7946             ReportClick("put", x, y); // and inform engine
7947             ReportClick("lift", x, y);
7948             MarkTargetSquares(0);
7949             return;
7950           }
7951         }
7952         DragPieceEnd(xPix, yPix); dragging = 0;
7953         /* Don't animate move and drag both */
7954         appData.animate = FALSE;
7955         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7956     }
7957
7958     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7959     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7960         ChessSquare piece = boards[currentMove][fromY][fromX];
7961         if(gameMode == EditPosition && piece != EmptySquare &&
7962            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7963             int n;
7964
7965             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7966                 n = PieceToNumber(piece - (int)BlackPawn);
7967                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7968                 boards[currentMove][handSize-1 - n][0] = piece;
7969                 boards[currentMove][handSize-1 - n][1]++;
7970             } else
7971             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7972                 n = PieceToNumber(piece);
7973                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7974                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7975                 boards[currentMove][n][BOARD_WIDTH-2]++;
7976             }
7977             boards[currentMove][fromY][fromX] = EmptySquare;
7978         }
7979         ClearHighlights();
7980         fromX = fromY = -1;
7981         MarkTargetSquares(1);
7982         DrawPosition(TRUE, boards[currentMove]);
7983         return;
7984     }
7985
7986     // off-board moves should not be highlighted
7987     if(x < 0 || y < 0) {
7988         ClearHighlights();
7989         DrawPosition(FALSE, NULL);
7990     } else ReportClick("put", x, y);
7991
7992     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7993  }
7994
7995     if(legal[toY][toX] == 2) { // highlight-induced promotion
7996         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7997         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7998     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7999       if(!*promoRestrict) {           // but has not done that yet
8000         deferChoice = TRUE;           // set up retry for when it does
8001         return;                       // and wait for that
8002       }
8003       promoChoice = ToLower(*promoRestrict); // force engine's choice
8004       deferChoice = FALSE;
8005     }
8006
8007     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
8008         SetHighlights(fromX, fromY, toX, toY);
8009         MarkTargetSquares(1);
8010         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
8011             // [HGM] super: promotion to captured piece selected from holdings
8012             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
8013             promotionChoice = TRUE;
8014             // kludge follows to temporarily execute move on display, without promoting yet
8015             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
8016             boards[currentMove][toY][toX] = p;
8017             DrawPosition(FALSE, boards[currentMove]);
8018             boards[currentMove][fromY][fromX] = p; // take back, but display stays
8019             boards[currentMove][toY][toX] = q;
8020             DisplayMessage("Click in holdings to choose piece", "");
8021             return;
8022         }
8023         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
8024         PromotionPopUp(promoChoice);
8025     } else {
8026         int oldMove = currentMove;
8027         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
8028         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
8029         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
8030         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
8031         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
8032            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
8033             DrawPosition(TRUE, boards[currentMove]);
8034         else DrawPosition(FALSE, NULL);
8035         fromX = fromY = -1;
8036         flashing = 0;
8037     }
8038     appData.animate = saveAnimate;
8039     if (appData.animate || appData.animateDragging) {
8040         /* Undo animation damage if needed */
8041 //      DrawPosition(FALSE, NULL);
8042     }
8043 }
8044
8045 int
8046 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
8047 {   // front-end-free part taken out of PieceMenuPopup
8048     int whichMenu; int xSqr, ySqr;
8049
8050     if(seekGraphUp) { // [HGM] seekgraph
8051         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
8052         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
8053         return -2;
8054     }
8055
8056     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
8057          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
8058         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
8059         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
8060         if(action == Press)   {
8061             originalFlip = flipView;
8062             flipView = !flipView; // temporarily flip board to see game from partners perspective
8063             DrawPosition(TRUE, partnerBoard);
8064             DisplayMessage(partnerStatus, "");
8065             partnerUp = TRUE;
8066         } else if(action == Release) {
8067             flipView = originalFlip;
8068             DrawPosition(TRUE, boards[currentMove]);
8069             partnerUp = FALSE;
8070         }
8071         return -2;
8072     }
8073
8074     xSqr = EventToSquare(x, BOARD_WIDTH);
8075     ySqr = EventToSquare(y, BOARD_HEIGHT);
8076     if (action == Release) {
8077         if(pieceSweep != EmptySquare) {
8078             EditPositionMenuEvent(pieceSweep, toX, toY);
8079             pieceSweep = EmptySquare;
8080         } else UnLoadPV(); // [HGM] pv
8081     }
8082     if (action != Press) return -2; // return code to be ignored
8083     switch (gameMode) {
8084       case IcsExamining:
8085         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8086       case EditPosition:
8087         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8088         if (xSqr < 0 || ySqr < 0) return -1;
8089         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8090         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8091         if(xSqr == createX && ySqr == createY && xSqr != BOARD_LEFT-2 && xSqr != BOARD_RGHT+1) {
8092             ChessSquare p = boards[currentMove][ySqr][xSqr];
8093             do { if(++p == EmptySquare) p = WhitePawn; } while(PieceToChar(p) == '.');
8094             boards[currentMove][ySqr][xSqr] = p; DrawPosition(FALSE, boards[currentMove]);
8095             return -2;
8096         }
8097         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8098         createX = toX = xSqr; createY = toY = ySqr; lastX = x, lastY = y;
8099         NextPiece(0);
8100         return 2; // grab
8101       case IcsObserving:
8102         if(!appData.icsEngineAnalyze) return -1;
8103       case IcsPlayingWhite:
8104       case IcsPlayingBlack:
8105         if(!appData.zippyPlay) goto noZip;
8106       case AnalyzeMode:
8107       case AnalyzeFile:
8108       case MachinePlaysWhite:
8109       case MachinePlaysBlack:
8110       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8111         if (!appData.dropMenu) {
8112           LoadPV(x, y);
8113           return 2; // flag front-end to grab mouse events
8114         }
8115         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8116            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8117       case EditGame:
8118       noZip:
8119         if (xSqr < 0 || ySqr < 0) return -1;
8120         if (!appData.dropMenu || appData.testLegality &&
8121             gameInfo.variant != VariantBughouse &&
8122             gameInfo.variant != VariantCrazyhouse) return -1;
8123         whichMenu = 1; // drop menu
8124         break;
8125       default:
8126         return -1;
8127     }
8128
8129     if (((*fromX = xSqr) < 0) ||
8130         ((*fromY = ySqr) < 0)) {
8131         *fromX = *fromY = -1;
8132         return -1;
8133     }
8134     if (flipView)
8135       *fromX = BOARD_WIDTH - 1 - *fromX;
8136     else
8137       *fromY = BOARD_HEIGHT - 1 - *fromY;
8138
8139     return whichMenu;
8140 }
8141
8142 void
8143 Wheel (int dir, int x, int y)
8144 {
8145     if(gameMode == EditPosition) {
8146         int xSqr = EventToSquare(x, BOARD_WIDTH);
8147         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8148         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8149         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8150         do {
8151             boards[currentMove][ySqr][xSqr] += dir;
8152             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8153             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8154         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8155         DrawPosition(FALSE, boards[currentMove]);
8156     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8157 }
8158
8159 void
8160 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8161 {
8162 //    char * hint = lastHint;
8163     FrontEndProgramStats stats;
8164
8165     stats.which = cps == &first ? 0 : 1;
8166     stats.depth = cpstats->depth;
8167     stats.nodes = cpstats->nodes;
8168     stats.score = cpstats->score;
8169     stats.time = cpstats->time;
8170     stats.pv = cpstats->movelist;
8171     stats.hint = lastHint;
8172     stats.an_move_index = 0;
8173     stats.an_move_count = 0;
8174
8175     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8176         stats.hint = cpstats->move_name;
8177         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8178         stats.an_move_count = cpstats->nr_moves;
8179     }
8180
8181     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
8182
8183     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8184         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8185
8186     SetProgramStats( &stats );
8187 }
8188
8189 void
8190 ClearEngineOutputPane (int which)
8191 {
8192     static FrontEndProgramStats dummyStats;
8193     dummyStats.which = which;
8194     dummyStats.pv = "#";
8195     SetProgramStats( &dummyStats );
8196 }
8197
8198 #define MAXPLAYERS 500
8199
8200 char *
8201 TourneyStandings (int display)
8202 {
8203     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8204     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8205     char result, *p, *names[MAXPLAYERS];
8206
8207     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8208         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8209     names[0] = p = strdup(appData.participants);
8210     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8211
8212     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8213
8214     while(result = appData.results[nr]) {
8215         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8216         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8217         wScore = bScore = 0;
8218         switch(result) {
8219           case '+': wScore = 2; break;
8220           case '-': bScore = 2; break;
8221           case '=': wScore = bScore = 1; break;
8222           case ' ':
8223           case '*': return strdup("busy"); // tourney not finished
8224         }
8225         score[w] += wScore;
8226         score[b] += bScore;
8227         games[w]++;
8228         games[b]++;
8229         nr++;
8230     }
8231     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8232     for(w=0; w<nPlayers; w++) {
8233         bScore = -1;
8234         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8235         ranking[w] = b; points[w] = bScore; score[b] = -2;
8236     }
8237     p = malloc(nPlayers*34+1);
8238     for(w=0; w<nPlayers && w<display; w++)
8239         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8240     free(names[0]);
8241     return p;
8242 }
8243
8244 void
8245 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8246 {       // count all piece types
8247         int p, f, r;
8248         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8249         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8250         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8251                 p = board[r][f];
8252                 pCnt[p]++;
8253                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8254                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8255                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8256                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8257                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8258                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8259         }
8260 }
8261
8262 int
8263 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8264 {
8265         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8266         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8267
8268         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8269         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8270         if(myPawns == 2 && nMine == 3) // KPP
8271             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8272         if(myPawns == 1 && nMine == 2) // KP
8273             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8274         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8275             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8276         if(myPawns) return FALSE;
8277         if(pCnt[WhiteRook+side])
8278             return pCnt[BlackRook-side] ||
8279                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8280                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8281                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8282         if(pCnt[WhiteCannon+side]) {
8283             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8284             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8285         }
8286         if(pCnt[WhiteKnight+side])
8287             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8288         return FALSE;
8289 }
8290
8291 int
8292 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8293 {
8294         VariantClass v = gameInfo.variant;
8295
8296         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8297         if(v == VariantShatranj) return TRUE; // always winnable through baring
8298         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8299         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8300
8301         if(v == VariantXiangqi) {
8302                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8303
8304                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8305                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8306                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8307                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8308                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8309                 if(stale) // we have at least one last-rank P plus perhaps C
8310                     return majors // KPKX
8311                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8312                 else // KCA*E*
8313                     return pCnt[WhiteFerz+side] // KCAK
8314                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8315                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8316                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8317
8318         } else if(v == VariantKnightmate) {
8319                 if(nMine == 1) return FALSE;
8320                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8321         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8322                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8323
8324                 if(nMine == 1) return FALSE; // bare King
8325                 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
8326                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8327                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8328                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8329                 if(pCnt[WhiteKnight+side])
8330                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8331                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8332                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8333                 if(nBishops)
8334                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8335                 if(pCnt[WhiteAlfil+side])
8336                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8337                 if(pCnt[WhiteWazir+side])
8338                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8339         }
8340
8341         return TRUE;
8342 }
8343
8344 int
8345 CompareWithRights (Board b1, Board b2)
8346 {
8347     int rights = 0;
8348     if(!CompareBoards(b1, b2)) return FALSE;
8349     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8350     /* compare castling rights */
8351     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8352            rights++; /* King lost rights, while rook still had them */
8353     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8354         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8355            rights++; /* but at least one rook lost them */
8356     }
8357     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8358            rights++;
8359     if( b1[CASTLING][5] != NoRights ) {
8360         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8361            rights++;
8362     }
8363     return rights == 0;
8364 }
8365
8366 int
8367 Adjudicate (ChessProgramState *cps)
8368 {       // [HGM] some adjudications useful with buggy engines
8369         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8370         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8371         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8372         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8373         int k, drop, count = 0; static int bare = 1;
8374         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8375         Boolean canAdjudicate = !appData.icsActive;
8376
8377         // most tests only when we understand the game, i.e. legality-checking on
8378             if( appData.testLegality )
8379             {   /* [HGM] Some more adjudications for obstinate engines */
8380                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8381                 static int moveCount = 6;
8382                 ChessMove result;
8383                 char *reason = NULL;
8384
8385                 /* Count what is on board. */
8386                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8387
8388                 /* Some material-based adjudications that have to be made before stalemate test */
8389                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8390                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8391                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8392                      if(canAdjudicate && appData.checkMates) {
8393                          if(engineOpponent)
8394                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8395                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8396                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8397                          return 1;
8398                      }
8399                 }
8400
8401                 /* Bare King in Shatranj (loses) or Losers (wins) */
8402                 if( nrW == 1 || nrB == 1) {
8403                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8404                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8405                      if(canAdjudicate && appData.checkMates) {
8406                          if(engineOpponent)
8407                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8408                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8409                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8410                          return 1;
8411                      }
8412                   } else
8413                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8414                   {    /* bare King */
8415                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8416                         if(canAdjudicate && appData.checkMates) {
8417                             /* but only adjudicate if adjudication enabled */
8418                             if(engineOpponent)
8419                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8420                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8421                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8422                             return 1;
8423                         }
8424                   }
8425                 } else bare = 1;
8426
8427
8428             // don't wait for engine to announce game end if we can judge ourselves
8429             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8430               case MT_CHECK:
8431                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8432                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8433                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8434                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8435                             checkCnt++;
8436                         if(checkCnt >= 2) {
8437                             reason = "Xboard adjudication: 3rd check";
8438                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8439                             break;
8440                         }
8441                     }
8442                 }
8443               case MT_NONE:
8444               default:
8445                 break;
8446               case MT_STEALMATE:
8447               case MT_STALEMATE:
8448               case MT_STAINMATE:
8449                 reason = "Xboard adjudication: Stalemate";
8450                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8451                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8452                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8453                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8454                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8455                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8456                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8457                                                                         EP_CHECKMATE : EP_WINS);
8458                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8459                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8460                 }
8461                 break;
8462               case MT_CHECKMATE:
8463                 reason = "Xboard adjudication: Checkmate";
8464                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8465                 if(gameInfo.variant == VariantShogi) {
8466                     if(forwardMostMove > backwardMostMove
8467                        && moveList[forwardMostMove-1][1] == '@'
8468                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8469                         reason = "XBoard adjudication: pawn-drop mate";
8470                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8471                     }
8472                 }
8473                 break;
8474             }
8475
8476                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8477                     case EP_STALEMATE:
8478                         result = GameIsDrawn; break;
8479                     case EP_CHECKMATE:
8480                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8481                     case EP_WINS:
8482                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8483                     default:
8484                         result = EndOfFile;
8485                 }
8486                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8487                     if(engineOpponent)
8488                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8489                     GameEnds( result, reason, GE_XBOARD );
8490                     return 1;
8491                 }
8492
8493                 /* Next absolutely insufficient mating material. */
8494                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8495                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8496                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8497
8498                      /* always flag draws, for judging claims */
8499                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8500
8501                      if(canAdjudicate && appData.materialDraws) {
8502                          /* but only adjudicate them if adjudication enabled */
8503                          if(engineOpponent) {
8504                            SendToProgram("force\n", engineOpponent); // suppress reply
8505                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8506                          }
8507                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8508                          return 1;
8509                      }
8510                 }
8511
8512                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8513                 if(gameInfo.variant == VariantXiangqi ?
8514                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8515                  : nrW + nrB == 4 &&
8516                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8517                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8518                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8519                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8520                    ) ) {
8521                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8522                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8523                           if(engineOpponent) {
8524                             SendToProgram("force\n", engineOpponent); // suppress reply
8525                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8526                           }
8527                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8528                           return 1;
8529                      }
8530                 } else moveCount = 6;
8531
8532                 if(gameInfo.variant == VariantMakruk && // Makruk counting rules
8533                   (nrW == 1 || nrB == 1 || nr[WhitePawn] + nr[BlackPawn] == 0)) { // which only kick in when pawnless or bare King
8534                     int maxcnt, his, mine, c, wom = WhiteOnMove(forwardMostMove);
8535                     count = forwardMostMove;
8536                     while(count >= backwardMostMove) {
8537                         int np = nr[WhitePawn] + nr[BlackPawn];
8538                         if(wom) mine = nrW, his = nrB, c = BlackPawn;
8539                         else    mine = nrB, his = nrW, c = WhitePawn;
8540                         if(mine > 1 && np) { count++; break; }
8541                         if(mine > 1) maxcnt = 64; else
8542                         maxcnt = (nr[WhiteRook+c] > 1 ? 8 : nr[WhiteRook+c] ? 16 : nr[WhiteMan+c] > 1 ? 22 :
8543                                                             nr[WhiteKnight+c] > 1 ? 32 : nr[WhiteMan+c] ? 44 : 64) - his - 1;
8544                         while(boards[count][EP_STATUS] != EP_CAPTURE && count > backwardMostMove) count--; // seek previous character
8545                         if(count == backwardMostMove) break;
8546                         if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) break;
8547                         Count(boards[--count], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8548                     }
8549                     if(forwardMostMove - count >= 2*maxcnt + 1 - (mine == 1)) {
8550                         boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8551                         if(canAdjudicate && appData.ruleMoves >= 0) {
8552                             GameEnds( GameIsDrawn, "Xboard adjudication: counting rule", GE_XBOARD );
8553                             return 1;
8554                         }
8555                     }
8556                 }
8557             }
8558
8559         // Repetition draws and 50-move rule can be applied independently of legality testing
8560
8561                 /* Check for rep-draws */
8562                 count = 0;
8563                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8564                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8565                 for(k = forwardMostMove-2;
8566                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8567                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8568                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8569                     k-=2)
8570                 {   int rights=0;
8571                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8572                         /* compare castling rights */
8573                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8574                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8575                                 rights++; /* King lost rights, while rook still had them */
8576                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8577                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8578                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8579                                    rights++; /* but at least one rook lost them */
8580                         }
8581                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8582                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8583                                 rights++;
8584                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8585                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8586                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8587                                    rights++;
8588                         }
8589                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8590                             && appData.drawRepeats > 1) {
8591                              /* adjudicate after user-specified nr of repeats */
8592                              int result = GameIsDrawn;
8593                              char *details = "XBoard adjudication: repetition draw";
8594                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8595                                 // [HGM] xiangqi: check for forbidden perpetuals
8596                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8597                                 for(m=forwardMostMove; m>k; m-=2) {
8598                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8599                                         ourPerpetual = 0; // the current mover did not always check
8600                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8601                                         hisPerpetual = 0; // the opponent did not always check
8602                                 }
8603                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8604                                                                         ourPerpetual, hisPerpetual);
8605                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8606                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8607                                     details = "Xboard adjudication: perpetual checking";
8608                                 } else
8609                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8610                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8611                                 } else
8612                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8613                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8614                                         result = BlackWins;
8615                                         details = "Xboard adjudication: repetition";
8616                                     }
8617                                 } else // it must be XQ
8618                                 // Now check for perpetual chases
8619                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8620                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8621                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8622                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8623                                         static char resdet[MSG_SIZ];
8624                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8625                                         details = resdet;
8626                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8627                                     } else
8628                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8629                                         break; // Abort repetition-checking loop.
8630                                 }
8631                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8632                              }
8633                              if(engineOpponent) {
8634                                SendToProgram("force\n", engineOpponent); // suppress reply
8635                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8636                              }
8637                              GameEnds( result, details, GE_XBOARD );
8638                              return 1;
8639                         }
8640                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8641                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8642                     }
8643                 }
8644
8645                 /* Now we test for 50-move draws. Determine ply count */
8646                 count = forwardMostMove;
8647                 /* look for last irreversble move */
8648                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8649                     count--;
8650                 /* if we hit starting position, add initial plies */
8651                 if( count == backwardMostMove )
8652                     count -= initialRulePlies;
8653                 count = forwardMostMove - count;
8654                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8655                         // adjust reversible move counter for checks in Xiangqi
8656                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8657                         if(i < backwardMostMove) i = backwardMostMove;
8658                         while(i <= forwardMostMove) {
8659                                 lastCheck = inCheck; // check evasion does not count
8660                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8661                                 if(inCheck || lastCheck) count--; // check does not count
8662                                 i++;
8663                         }
8664                 }
8665                 if( count >= 100 && gameInfo.variant != VariantMakruk) // do not accept 50-move claims in Makruk
8666                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8667                          /* this is used to judge if draw claims are legal */
8668                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8669                          if(engineOpponent) {
8670                            SendToProgram("force\n", engineOpponent); // suppress reply
8671                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8672                          }
8673                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8674                          return 1;
8675                 }
8676
8677                 /* if draw offer is pending, treat it as a draw claim
8678                  * when draw condition present, to allow engines a way to
8679                  * claim draws before making their move to avoid a race
8680                  * condition occurring after their move
8681                  */
8682                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8683                          char *p = NULL;
8684                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8685                              p = "Draw claim: 50-move rule";
8686                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8687                              p = "Draw claim: 3-fold repetition";
8688                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8689                              p = "Draw claim: insufficient mating material";
8690                          if( p != NULL && canAdjudicate) {
8691                              if(engineOpponent) {
8692                                SendToProgram("force\n", engineOpponent); // suppress reply
8693                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8694                              }
8695                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8696                              return 1;
8697                          }
8698                 }
8699
8700                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8701                     if(engineOpponent) {
8702                       SendToProgram("force\n", engineOpponent); // suppress reply
8703                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8704                     }
8705                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8706                     return 1;
8707                 }
8708         return 0;
8709 }
8710
8711 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8712 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8713 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8714
8715 static int
8716 BitbaseProbe ()
8717 {
8718     int pieces[10], squares[10], cnt=0, r, f, res;
8719     static int loaded;
8720     static PPROBE_EGBB probeBB;
8721     if(!appData.testLegality) return 10;
8722     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8723     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8724     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8725     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8726         ChessSquare piece = boards[forwardMostMove][r][f];
8727         int black = (piece >= BlackPawn);
8728         int type = piece - black*BlackPawn;
8729         if(piece == EmptySquare) continue;
8730         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8731         if(type == WhiteKing) type = WhiteQueen + 1;
8732         type = egbbCode[type];
8733         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8734         pieces[cnt] = type + black*6;
8735         if(++cnt > 5) return 11;
8736     }
8737     pieces[cnt] = squares[cnt] = 0;
8738     // probe EGBB
8739     if(loaded == 2) return 13; // loading failed before
8740     if(loaded == 0) {
8741         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8742         HMODULE lib;
8743         PLOAD_EGBB loadBB;
8744         loaded = 2; // prepare for failure
8745         if(!path) return 13; // no egbb installed
8746         strncpy(buf, path + 8, MSG_SIZ);
8747         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8748         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8749         lib = LoadLibrary(buf);
8750         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8751         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8752         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8753         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8754         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8755         loaded = 1; // success!
8756     }
8757     res = probeBB(forwardMostMove & 1, pieces, squares);
8758     return res > 0 ? 1 : res < 0 ? -1 : 0;
8759 }
8760
8761 char *
8762 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8763 {   // [HGM] book: this routine intercepts moves to simulate book replies
8764     char *bookHit = NULL;
8765
8766     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8767         char buf[MSG_SIZ];
8768         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8769         SendToProgram(buf, cps);
8770     }
8771     //first determine if the incoming move brings opponent into his book
8772     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8773         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8774     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8775     if(bookHit != NULL && !cps->bookSuspend) {
8776         // make sure opponent is not going to reply after receiving move to book position
8777         SendToProgram("force\n", cps);
8778         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8779     }
8780     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8781     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8782     // now arrange restart after book miss
8783     if(bookHit) {
8784         // after a book hit we never send 'go', and the code after the call to this routine
8785         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8786         char buf[MSG_SIZ], *move = bookHit;
8787         if(cps->useSAN) {
8788             int fromX, fromY, toX, toY;
8789             char promoChar;
8790             ChessMove moveType;
8791             move = buf + 30;
8792             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8793                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8794                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8795                                     PosFlags(forwardMostMove),
8796                                     fromY, fromX, toY, toX, promoChar, move);
8797             } else {
8798                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8799                 bookHit = NULL;
8800             }
8801         }
8802         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8803         SendToProgram(buf, cps);
8804         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8805     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8806         SendToProgram("go\n", cps);
8807         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8808     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8809         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8810             SendToProgram("go\n", cps);
8811         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8812     }
8813     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8814 }
8815
8816 int
8817 LoadError (char *errmess, ChessProgramState *cps)
8818 {   // unloads engine and switches back to -ncp mode if it was first
8819     if(cps->initDone) return FALSE;
8820     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8821     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8822     cps->pr = NoProc;
8823     if(cps == &first) {
8824         appData.noChessProgram = TRUE;
8825         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8826         gameMode = BeginningOfGame; ModeHighlight();
8827         SetNCPMode();
8828     }
8829     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8830     DisplayMessage("", ""); // erase waiting message
8831     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8832     return TRUE;
8833 }
8834
8835 char *savedMessage;
8836 ChessProgramState *savedState;
8837 void
8838 DeferredBookMove (void)
8839 {
8840         if(savedState->lastPing != savedState->lastPong)
8841                     ScheduleDelayedEvent(DeferredBookMove, 10);
8842         else
8843         HandleMachineMove(savedMessage, savedState);
8844 }
8845
8846 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8847 static ChessProgramState *stalledEngine;
8848 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8849
8850 void
8851 HandleMachineMove (char *message, ChessProgramState *cps)
8852 {
8853     static char firstLeg[20], legs;
8854     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8855     char realname[MSG_SIZ];
8856     int fromX, fromY, toX, toY;
8857     ChessMove moveType;
8858     char promoChar, roar;
8859     char *p, *pv=buf1;
8860     int oldError;
8861     char *bookHit;
8862
8863     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8864         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8865         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8866             DisplayError(_("Invalid pairing from pairing engine"), 0);
8867             return;
8868         }
8869         pairingReceived = 1;
8870         NextMatchGame();
8871         return; // Skim the pairing messages here.
8872     }
8873
8874     oldError = cps->userError; cps->userError = 0;
8875
8876 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8877     /*
8878      * Kludge to ignore BEL characters
8879      */
8880     while (*message == '\007') message++;
8881
8882     /*
8883      * [HGM] engine debug message: ignore lines starting with '#' character
8884      */
8885     if(cps->debug && *message == '#') return;
8886
8887     /*
8888      * Look for book output
8889      */
8890     if (cps == &first && bookRequested) {
8891         if (message[0] == '\t' || message[0] == ' ') {
8892             /* Part of the book output is here; append it */
8893             strcat(bookOutput, message);
8894             strcat(bookOutput, "  \n");
8895             return;
8896         } else if (bookOutput[0] != NULLCHAR) {
8897             /* All of book output has arrived; display it */
8898             char *p = bookOutput;
8899             while (*p != NULLCHAR) {
8900                 if (*p == '\t') *p = ' ';
8901                 p++;
8902             }
8903             DisplayInformation(bookOutput);
8904             bookRequested = FALSE;
8905             /* Fall through to parse the current output */
8906         }
8907     }
8908
8909     /*
8910      * Look for machine move.
8911      */
8912     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8913         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8914     {
8915         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8916             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8917             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8918             stalledEngine = cps;
8919             if(appData.ponderNextMove) { // bring opponent out of ponder
8920                 if(gameMode == TwoMachinesPlay) {
8921                     if(cps->other->pause)
8922                         PauseEngine(cps->other);
8923                     else
8924                         SendToProgram("easy\n", cps->other);
8925                 }
8926             }
8927             StopClocks();
8928             return;
8929         }
8930
8931       if(cps->usePing) {
8932
8933         /* This method is only useful on engines that support ping */
8934         if(abortEngineThink) {
8935             if (appData.debugMode) {
8936                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8937             }
8938             SendToProgram("undo\n", cps);
8939             return;
8940         }
8941
8942         if (cps->lastPing != cps->lastPong) {
8943             /* Extra move from before last new; ignore */
8944             if (appData.debugMode) {
8945                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8946             }
8947           return;
8948         }
8949
8950       } else {
8951
8952         int machineWhite = FALSE;
8953
8954         switch (gameMode) {
8955           case BeginningOfGame:
8956             /* Extra move from before last reset; ignore */
8957             if (appData.debugMode) {
8958                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8959             }
8960             return;
8961
8962           case EndOfGame:
8963           case IcsIdle:
8964           default:
8965             /* Extra move after we tried to stop.  The mode test is
8966                not a reliable way of detecting this problem, but it's
8967                the best we can do on engines that don't support ping.
8968             */
8969             if (appData.debugMode) {
8970                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8971                         cps->which, gameMode);
8972             }
8973             SendToProgram("undo\n", cps);
8974             return;
8975
8976           case MachinePlaysWhite:
8977           case IcsPlayingWhite:
8978             machineWhite = TRUE;
8979             break;
8980
8981           case MachinePlaysBlack:
8982           case IcsPlayingBlack:
8983             machineWhite = FALSE;
8984             break;
8985
8986           case TwoMachinesPlay:
8987             machineWhite = (cps->twoMachinesColor[0] == 'w');
8988             break;
8989         }
8990         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8991             if (appData.debugMode) {
8992                 fprintf(debugFP,
8993                         "Ignoring move out of turn by %s, gameMode %d"
8994                         ", forwardMost %d\n",
8995                         cps->which, gameMode, forwardMostMove);
8996             }
8997             return;
8998         }
8999       }
9000
9001         if(cps->alphaRank) AlphaRank(machineMove, 4);
9002
9003         // [HGM] lion: (some very limited) support for Alien protocol
9004         killX = killY = kill2X = kill2Y = -1;
9005         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
9006             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
9007             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
9008             return;
9009         }
9010         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
9011             char *q = strchr(p+1, ',');            // second comma?
9012             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
9013             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
9014             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
9015         }
9016         if(firstLeg[0]) { // there was a previous leg;
9017             // only support case where same piece makes two step
9018             char buf[20], *p = machineMove+1, *q = buf+1, f;
9019             safeStrCpy(buf, machineMove, 20);
9020             while(isdigit(*q)) q++; // find start of to-square
9021             safeStrCpy(machineMove, firstLeg, 20);
9022             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
9023             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
9024             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)
9025             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
9026             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
9027             firstLeg[0] = NULLCHAR; legs = 0;
9028         }
9029
9030         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
9031                               &fromX, &fromY, &toX, &toY, &promoChar)) {
9032             /* Machine move could not be parsed; ignore it. */
9033           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
9034                     machineMove, _(cps->which));
9035             DisplayMoveError(buf1);
9036             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
9037                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
9038             if (gameMode == TwoMachinesPlay) {
9039               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9040                        buf1, GE_XBOARD);
9041             }
9042             return;
9043         }
9044
9045         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
9046         /* So we have to redo legality test with true e.p. status here,  */
9047         /* to make sure an illegal e.p. capture does not slip through,   */
9048         /* to cause a forfeit on a justified illegal-move complaint      */
9049         /* of the opponent.                                              */
9050         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
9051            ChessMove moveType;
9052            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
9053                              fromY, fromX, toY, toX, promoChar);
9054             if(moveType == IllegalMove) {
9055               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
9056                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
9057                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9058                            buf1, GE_XBOARD);
9059                 return;
9060            } else if(!appData.fischerCastling && toX != BOARD_WIDTH>>1)
9061            /* [HGM] Kludge to handle engines that send FRC-style castling
9062               when they shouldn't (like TSCP-Gothic) */
9063            switch(moveType) {
9064              case WhiteASideCastleFR:
9065              case BlackASideCastleFR:
9066                toX+=2;
9067                currentMoveString[2]++;
9068                break;
9069              case WhiteHSideCastleFR:
9070              case BlackHSideCastleFR:
9071                toX--;
9072                currentMoveString[2]--;
9073                break;
9074              default: ; // nothing to do, but suppresses warning of pedantic compilers
9075            }
9076         }
9077         hintRequested = FALSE;
9078         lastHint[0] = NULLCHAR;
9079         bookRequested = FALSE;
9080         /* Program may be pondering now */
9081         cps->maybeThinking = TRUE;
9082         if (cps->sendTime == 2) cps->sendTime = 1;
9083         if (cps->offeredDraw) cps->offeredDraw--;
9084
9085         /* [AS] Save move info*/
9086         pvInfoList[ forwardMostMove ].score = programStats.score;
9087         pvInfoList[ forwardMostMove ].depth = programStats.depth;
9088         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
9089
9090         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
9091
9092         /* Test suites abort the 'game' after one move */
9093         if(*appData.finger) {
9094            static FILE *f;
9095            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
9096            if(!f) f = fopen(appData.finger, "w");
9097            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
9098            else { DisplayFatalError("Bad output file", errno, 0); return; }
9099            free(fen);
9100            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9101         }
9102         if(appData.epd) {
9103            if(solvingTime >= 0) {
9104               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
9105               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
9106            } else {
9107               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
9108               if(solvingTime == -2) second.matchWins++;
9109            }
9110            OutputKibitz(2, buf1);
9111            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9112         }
9113
9114         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9115         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9116             int count = 0;
9117
9118             while( count < adjudicateLossPlies ) {
9119                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9120
9121                 if( count & 1 ) {
9122                     score = -score; /* Flip score for winning side */
9123                 }
9124
9125                 if( score > appData.adjudicateLossThreshold ) {
9126                     break;
9127                 }
9128
9129                 count++;
9130             }
9131
9132             if( count >= adjudicateLossPlies ) {
9133                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9134
9135                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9136                     "Xboard adjudication",
9137                     GE_XBOARD );
9138
9139                 return;
9140             }
9141         }
9142
9143         if(Adjudicate(cps)) {
9144             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9145             return; // [HGM] adjudicate: for all automatic game ends
9146         }
9147
9148 #if ZIPPY
9149         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9150             first.initDone) {
9151           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9152                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9153                 SendToICS("draw ");
9154                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9155           }
9156           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9157           ics_user_moved = 1;
9158           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9159                 char buf[3*MSG_SIZ];
9160
9161                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9162                         programStats.score / 100.,
9163                         programStats.depth,
9164                         programStats.time / 100.,
9165                         (unsigned int)programStats.nodes,
9166                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9167                         programStats.movelist);
9168                 SendToICS(buf);
9169           }
9170         }
9171 #endif
9172
9173         /* [AS] Clear stats for next move */
9174         ClearProgramStats();
9175         thinkOutput[0] = NULLCHAR;
9176         hiddenThinkOutputState = 0;
9177
9178         bookHit = NULL;
9179         if (gameMode == TwoMachinesPlay) {
9180             /* [HGM] relaying draw offers moved to after reception of move */
9181             /* and interpreting offer as claim if it brings draw condition */
9182             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9183                 SendToProgram("draw\n", cps->other);
9184             }
9185             if (cps->other->sendTime) {
9186                 SendTimeRemaining(cps->other,
9187                                   cps->other->twoMachinesColor[0] == 'w');
9188             }
9189             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9190             if (firstMove && !bookHit) {
9191                 firstMove = FALSE;
9192                 if (cps->other->useColors) {
9193                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9194                 }
9195                 SendToProgram("go\n", cps->other);
9196             }
9197             cps->other->maybeThinking = TRUE;
9198         }
9199
9200         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9201
9202         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9203
9204         if (!pausing && appData.ringBellAfterMoves) {
9205             if(!roar) RingBell();
9206         }
9207
9208         /*
9209          * Reenable menu items that were disabled while
9210          * machine was thinking
9211          */
9212         if (gameMode != TwoMachinesPlay)
9213             SetUserThinkingEnables();
9214
9215         // [HGM] book: after book hit opponent has received move and is now in force mode
9216         // force the book reply into it, and then fake that it outputted this move by jumping
9217         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9218         if(bookHit) {
9219                 static char bookMove[MSG_SIZ]; // a bit generous?
9220
9221                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9222                 strcat(bookMove, bookHit);
9223                 message = bookMove;
9224                 cps = cps->other;
9225                 programStats.nodes = programStats.depth = programStats.time =
9226                 programStats.score = programStats.got_only_move = 0;
9227                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9228
9229                 if(cps->lastPing != cps->lastPong) {
9230                     savedMessage = message; // args for deferred call
9231                     savedState = cps;
9232                     ScheduleDelayedEvent(DeferredBookMove, 10);
9233                     return;
9234                 }
9235                 goto FakeBookMove;
9236         }
9237
9238         return;
9239     }
9240
9241     /* Set special modes for chess engines.  Later something general
9242      *  could be added here; for now there is just one kludge feature,
9243      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9244      *  when "xboard" is given as an interactive command.
9245      */
9246     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9247         cps->useSigint = FALSE;
9248         cps->useSigterm = FALSE;
9249     }
9250     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9251       ParseFeatures(message+8, cps); if(tryNr && tryNr < 3) tryNr = 3;
9252       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9253     }
9254
9255     if (!strncmp(message, "setup ", 6) && 
9256         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9257           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9258                                         ) { // [HGM] allow first engine to define opening position
9259       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9260       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9261       *buf = NULLCHAR;
9262       if(sscanf(message, "setup (%s", buf) == 1) {
9263         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9264         ASSIGN(appData.pieceToCharTable, buf);
9265       }
9266       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9267       if(dummy >= 3) {
9268         while(message[s] && message[s++] != ' ');
9269         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9270            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9271 //          if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9272             if(hand > h) handSize = hand; else handSize = h;
9273             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9274             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9275           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9276           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9277           startedFromSetupPosition = FALSE;
9278         }
9279       }
9280       if(startedFromSetupPosition) return;
9281       ParseFEN(boards[0], &dummy, message+s, FALSE);
9282       DrawPosition(TRUE, boards[0]);
9283       CopyBoard(initialPosition, boards[0]);
9284       startedFromSetupPosition = TRUE;
9285       return;
9286     }
9287     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9288       ChessSquare piece = WhitePawn;
9289       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9290       if(*p == '+') promoted++, ID = *++p;
9291       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9292       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9293       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9294       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9295       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9296       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9297       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9298       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9299                                                && gameInfo.variant != VariantGreat
9300                                                && gameInfo.variant != VariantFairy    ) return;
9301       if(piece < EmptySquare) {
9302         pieceDefs = TRUE;
9303         ASSIGN(pieceDesc[piece], buf1);
9304         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9305       }
9306       return;
9307     }
9308     if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9309       if(deferChoice) {
9310         LeftClick(Press, 0, 0); // finish the click that was interrupted
9311       } else if(promoSweep != EmptySquare) {
9312         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9313         if(strlen(promoRestrict) > 1) Sweep(0);
9314       }
9315       return;
9316     }
9317     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9318      * want this, I was asked to put it in, and obliged.
9319      */
9320     if (!strncmp(message, "setboard ", 9)) {
9321         Board initial_position;
9322
9323         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9324
9325         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9326             DisplayError(_("Bad FEN received from engine"), 0);
9327             return ;
9328         } else {
9329            Reset(TRUE, FALSE);
9330            CopyBoard(boards[0], initial_position);
9331            initialRulePlies = FENrulePlies;
9332            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9333            else gameMode = MachinePlaysBlack;
9334            DrawPosition(FALSE, boards[currentMove]);
9335         }
9336         return;
9337     }
9338
9339     /*
9340      * Look for communication commands
9341      */
9342     if (!strncmp(message, "telluser ", 9)) {
9343         if(message[9] == '\\' && message[10] == '\\')
9344             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9345         PlayTellSound();
9346         DisplayNote(message + 9);
9347         return;
9348     }
9349     if (!strncmp(message, "tellusererror ", 14)) {
9350         cps->userError = 1;
9351         if(message[14] == '\\' && message[15] == '\\')
9352             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9353         PlayTellSound();
9354         DisplayError(message + 14, 0);
9355         return;
9356     }
9357     if (!strncmp(message, "tellopponent ", 13)) {
9358       if (appData.icsActive) {
9359         if (loggedOn) {
9360           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9361           SendToICS(buf1);
9362         }
9363       } else {
9364         DisplayNote(message + 13);
9365       }
9366       return;
9367     }
9368     if (!strncmp(message, "tellothers ", 11)) {
9369       if (appData.icsActive) {
9370         if (loggedOn) {
9371           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9372           SendToICS(buf1);
9373         }
9374       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9375       return;
9376     }
9377     if (!strncmp(message, "tellall ", 8)) {
9378       if (appData.icsActive) {
9379         if (loggedOn) {
9380           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9381           SendToICS(buf1);
9382         }
9383       } else {
9384         DisplayNote(message + 8);
9385       }
9386       return;
9387     }
9388     if (strncmp(message, "warning", 7) == 0) {
9389         /* Undocumented feature, use tellusererror in new code */
9390         DisplayError(message, 0);
9391         return;
9392     }
9393     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9394         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9395         strcat(realname, " query");
9396         AskQuestion(realname, buf2, buf1, cps->pr);
9397         return;
9398     }
9399     /* Commands from the engine directly to ICS.  We don't allow these to be
9400      *  sent until we are logged on. Crafty kibitzes have been known to
9401      *  interfere with the login process.
9402      */
9403     if (loggedOn) {
9404         if (!strncmp(message, "tellics ", 8)) {
9405             SendToICS(message + 8);
9406             SendToICS("\n");
9407             return;
9408         }
9409         if (!strncmp(message, "tellicsnoalias ", 15)) {
9410             SendToICS(ics_prefix);
9411             SendToICS(message + 15);
9412             SendToICS("\n");
9413             return;
9414         }
9415         /* The following are for backward compatibility only */
9416         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9417             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9418             SendToICS(ics_prefix);
9419             SendToICS(message);
9420             SendToICS("\n");
9421             return;
9422         }
9423     }
9424     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9425         if(initPing == cps->lastPong) {
9426             if(gameInfo.variant == VariantUnknown) {
9427                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9428                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9429                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9430             }
9431             initPing = -1;
9432         }
9433         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9434             abortEngineThink = FALSE;
9435             DisplayMessage("", "");
9436             ThawUI();
9437         }
9438         return;
9439     }
9440     if(!strncmp(message, "highlight ", 10)) {
9441         if(appData.testLegality && !*engineVariant && appData.markers) return;
9442         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9443         return;
9444     }
9445     if(!strncmp(message, "click ", 6)) {
9446         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9447         if(appData.testLegality || !appData.oneClick) return;
9448         sscanf(message+6, "%c%d%c", &f, &y, &c);
9449         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9450         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9451         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9452         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9453         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9454         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9455             LeftClick(Release, lastLeftX, lastLeftY);
9456         controlKey  = (c == ',');
9457         LeftClick(Press, x, y);
9458         LeftClick(Release, x, y);
9459         first.highlight = f;
9460         return;
9461     }
9462     if(strncmp(message, "uciok", 5) == 0) { // response to "uci" probe
9463         int nr = (cps == &second);
9464         appData.isUCI[nr] = isUCI = 1;
9465         ReplaceEngine(cps, nr); // retry install as UCI
9466         return;
9467     }
9468     /*
9469      * If the move is illegal, cancel it and redraw the board.
9470      * Also deal with other error cases.  Matching is rather loose
9471      * here to accommodate engines written before the spec.
9472      */
9473     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9474         strncmp(message, "Error", 5) == 0) {
9475         if (StrStr(message, "name") ||
9476             StrStr(message, "rating") || StrStr(message, "?") ||
9477             StrStr(message, "result") || StrStr(message, "board") ||
9478             StrStr(message, "bk") || StrStr(message, "computer") ||
9479             StrStr(message, "variant") || StrStr(message, "hint") ||
9480             StrStr(message, "random") || StrStr(message, "depth") ||
9481             StrStr(message, "accepted")) {
9482             return;
9483         }
9484         if (StrStr(message, "protover")) {
9485           /* Program is responding to input, so it's apparently done
9486              initializing, and this error message indicates it is
9487              protocol version 1.  So we don't need to wait any longer
9488              for it to initialize and send feature commands. */
9489           FeatureDone(cps, 1);
9490           cps->protocolVersion = 1;
9491           return;
9492         }
9493         cps->maybeThinking = FALSE;
9494
9495         if (StrStr(message, "draw")) {
9496             /* Program doesn't have "draw" command */
9497             cps->sendDrawOffers = 0;
9498             return;
9499         }
9500         if (cps->sendTime != 1 &&
9501             (StrStr(message, "time") || StrStr(message, "otim"))) {
9502           /* Program apparently doesn't have "time" or "otim" command */
9503           cps->sendTime = 0;
9504           return;
9505         }
9506         if (StrStr(message, "analyze")) {
9507             cps->analysisSupport = FALSE;
9508             cps->analyzing = FALSE;
9509 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9510             EditGameEvent(); // [HGM] try to preserve loaded game
9511             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9512             DisplayError(buf2, 0);
9513             return;
9514         }
9515         if (StrStr(message, "(no matching move)st")) {
9516           /* Special kludge for GNU Chess 4 only */
9517           cps->stKludge = TRUE;
9518           SendTimeControl(cps, movesPerSession, timeControl,
9519                           timeIncrement, appData.searchDepth,
9520                           searchTime);
9521           return;
9522         }
9523         if (StrStr(message, "(no matching move)sd")) {
9524           /* Special kludge for GNU Chess 4 only */
9525           cps->sdKludge = TRUE;
9526           SendTimeControl(cps, movesPerSession, timeControl,
9527                           timeIncrement, appData.searchDepth,
9528                           searchTime);
9529           return;
9530         }
9531         if (!StrStr(message, "llegal")) {
9532             return;
9533         }
9534         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9535             gameMode == IcsIdle) return;
9536         if (forwardMostMove <= backwardMostMove) return;
9537         if (pausing) PauseEvent();
9538       if(appData.forceIllegal) {
9539             // [HGM] illegal: machine refused move; force position after move into it
9540           SendToProgram("force\n", cps);
9541           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9542                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9543                 // when black is to move, while there might be nothing on a2 or black
9544                 // might already have the move. So send the board as if white has the move.
9545                 // But first we must change the stm of the engine, as it refused the last move
9546                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9547                 if(WhiteOnMove(forwardMostMove)) {
9548                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9549                     SendBoard(cps, forwardMostMove); // kludgeless board
9550                 } else {
9551                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9552                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9553                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9554                 }
9555           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9556             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9557                  gameMode == TwoMachinesPlay)
9558               SendToProgram("go\n", cps);
9559             return;
9560       } else
9561         if (gameMode == PlayFromGameFile) {
9562             /* Stop reading this game file */
9563             gameMode = EditGame;
9564             ModeHighlight();
9565         }
9566         /* [HGM] illegal-move claim should forfeit game when Xboard */
9567         /* only passes fully legal moves                            */
9568         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9569             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9570                                 "False illegal-move claim", GE_XBOARD );
9571             return; // do not take back move we tested as valid
9572         }
9573         currentMove = forwardMostMove-1;
9574         DisplayMove(currentMove-1); /* before DisplayMoveError */
9575         SwitchClocks(forwardMostMove-1); // [HGM] race
9576         DisplayBothClocks();
9577         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9578                 parseList[currentMove], _(cps->which));
9579         DisplayMoveError(buf1);
9580         DrawPosition(FALSE, boards[currentMove]);
9581
9582         SetUserThinkingEnables();
9583         return;
9584     }
9585     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9586         /* Program has a broken "time" command that
9587            outputs a string not ending in newline.
9588            Don't use it. */
9589         cps->sendTime = 0;
9590     }
9591     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9592         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9593             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9594     }
9595
9596     /*
9597      * If chess program startup fails, exit with an error message.
9598      * Attempts to recover here are futile. [HGM] Well, we try anyway
9599      */
9600     if ((StrStr(message, "unknown host") != NULL)
9601         || (StrStr(message, "No remote directory") != NULL)
9602         || (StrStr(message, "not found") != NULL)
9603         || (StrStr(message, "No such file") != NULL)
9604         || (StrStr(message, "can't alloc") != NULL)
9605         || (StrStr(message, "Permission denied") != NULL)) {
9606
9607         cps->maybeThinking = FALSE;
9608         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9609                 _(cps->which), cps->program, cps->host, message);
9610         RemoveInputSource(cps->isr);
9611         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9612             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9613             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9614         }
9615         return;
9616     }
9617
9618     /*
9619      * Look for hint output
9620      */
9621     if (sscanf(message, "Hint: %s", buf1) == 1) {
9622         if (cps == &first && hintRequested) {
9623             hintRequested = FALSE;
9624             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9625                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9626                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9627                                     PosFlags(forwardMostMove),
9628                                     fromY, fromX, toY, toX, promoChar, buf1);
9629                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9630                 DisplayInformation(buf2);
9631             } else {
9632                 /* Hint move could not be parsed!? */
9633               snprintf(buf2, sizeof(buf2),
9634                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9635                         buf1, _(cps->which));
9636                 DisplayError(buf2, 0);
9637             }
9638         } else {
9639           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9640         }
9641         return;
9642     }
9643
9644     /*
9645      * Ignore other messages if game is not in progress
9646      */
9647     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9648         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9649
9650     /*
9651      * look for win, lose, draw, or draw offer
9652      */
9653     if (strncmp(message, "1-0", 3) == 0) {
9654         char *p, *q, *r = "";
9655         p = strchr(message, '{');
9656         if (p) {
9657             q = strchr(p, '}');
9658             if (q) {
9659                 *q = NULLCHAR;
9660                 r = p + 1;
9661             }
9662         }
9663         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9664         return;
9665     } else if (strncmp(message, "0-1", 3) == 0) {
9666         char *p, *q, *r = "";
9667         p = strchr(message, '{');
9668         if (p) {
9669             q = strchr(p, '}');
9670             if (q) {
9671                 *q = NULLCHAR;
9672                 r = p + 1;
9673             }
9674         }
9675         /* Kludge for Arasan 4.1 bug */
9676         if (strcmp(r, "Black resigns") == 0) {
9677             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9678             return;
9679         }
9680         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9681         return;
9682     } else if (strncmp(message, "1/2", 3) == 0) {
9683         char *p, *q, *r = "";
9684         p = strchr(message, '{');
9685         if (p) {
9686             q = strchr(p, '}');
9687             if (q) {
9688                 *q = NULLCHAR;
9689                 r = p + 1;
9690             }
9691         }
9692
9693         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9694         return;
9695
9696     } else if (strncmp(message, "White resign", 12) == 0) {
9697         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9698         return;
9699     } else if (strncmp(message, "Black resign", 12) == 0) {
9700         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9701         return;
9702     } else if (strncmp(message, "White matches", 13) == 0 ||
9703                strncmp(message, "Black matches", 13) == 0   ) {
9704         /* [HGM] ignore GNUShogi noises */
9705         return;
9706     } else if (strncmp(message, "White", 5) == 0 &&
9707                message[5] != '(' &&
9708                StrStr(message, "Black") == NULL) {
9709         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9710         return;
9711     } else if (strncmp(message, "Black", 5) == 0 &&
9712                message[5] != '(') {
9713         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9714         return;
9715     } else if (strcmp(message, "resign") == 0 ||
9716                strcmp(message, "computer resigns") == 0) {
9717         switch (gameMode) {
9718           case MachinePlaysBlack:
9719           case IcsPlayingBlack:
9720             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9721             break;
9722           case MachinePlaysWhite:
9723           case IcsPlayingWhite:
9724             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9725             break;
9726           case TwoMachinesPlay:
9727             if (cps->twoMachinesColor[0] == 'w')
9728               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9729             else
9730               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9731             break;
9732           default:
9733             /* can't happen */
9734             break;
9735         }
9736         return;
9737     } else if (strncmp(message, "opponent mates", 14) == 0) {
9738         switch (gameMode) {
9739           case MachinePlaysBlack:
9740           case IcsPlayingBlack:
9741             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9742             break;
9743           case MachinePlaysWhite:
9744           case IcsPlayingWhite:
9745             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9746             break;
9747           case TwoMachinesPlay:
9748             if (cps->twoMachinesColor[0] == 'w')
9749               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9750             else
9751               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9752             break;
9753           default:
9754             /* can't happen */
9755             break;
9756         }
9757         return;
9758     } else if (strncmp(message, "computer mates", 14) == 0) {
9759         switch (gameMode) {
9760           case MachinePlaysBlack:
9761           case IcsPlayingBlack:
9762             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9763             break;
9764           case MachinePlaysWhite:
9765           case IcsPlayingWhite:
9766             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9767             break;
9768           case TwoMachinesPlay:
9769             if (cps->twoMachinesColor[0] == 'w')
9770               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9771             else
9772               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9773             break;
9774           default:
9775             /* can't happen */
9776             break;
9777         }
9778         return;
9779     } else if (strncmp(message, "checkmate", 9) == 0) {
9780         if (WhiteOnMove(forwardMostMove)) {
9781             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9782         } else {
9783             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9784         }
9785         return;
9786     } else if (strstr(message, "Draw") != NULL ||
9787                strstr(message, "game is a draw") != NULL) {
9788         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9789         return;
9790     } else if (strstr(message, "offer") != NULL &&
9791                strstr(message, "draw") != NULL) {
9792 #if ZIPPY
9793         if (appData.zippyPlay && first.initDone) {
9794             /* Relay offer to ICS */
9795             SendToICS(ics_prefix);
9796             SendToICS("draw\n");
9797         }
9798 #endif
9799         cps->offeredDraw = 2; /* valid until this engine moves twice */
9800         if (gameMode == TwoMachinesPlay) {
9801             if (cps->other->offeredDraw) {
9802                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9803             /* [HGM] in two-machine mode we delay relaying draw offer      */
9804             /* until after we also have move, to see if it is really claim */
9805             }
9806         } else if (gameMode == MachinePlaysWhite ||
9807                    gameMode == MachinePlaysBlack) {
9808           if (userOfferedDraw) {
9809             DisplayInformation(_("Machine accepts your draw offer"));
9810             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9811           } else {
9812             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9813           }
9814         }
9815     }
9816
9817
9818     /*
9819      * Look for thinking output
9820      */
9821     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9822           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9823                                 ) {
9824         int plylev, mvleft, mvtot, curscore, time;
9825         char mvname[MOVE_LEN];
9826         u64 nodes; // [DM]
9827         char plyext;
9828         int ignore = FALSE;
9829         int prefixHint = FALSE;
9830         mvname[0] = NULLCHAR;
9831
9832         switch (gameMode) {
9833           case MachinePlaysBlack:
9834           case IcsPlayingBlack:
9835             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9836             break;
9837           case MachinePlaysWhite:
9838           case IcsPlayingWhite:
9839             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9840             break;
9841           case AnalyzeMode:
9842           case AnalyzeFile:
9843             break;
9844           case IcsObserving: /* [DM] icsEngineAnalyze */
9845             if (!appData.icsEngineAnalyze) ignore = TRUE;
9846             break;
9847           case TwoMachinesPlay:
9848             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9849                 ignore = TRUE;
9850             }
9851             break;
9852           default:
9853             ignore = TRUE;
9854             break;
9855         }
9856
9857         if (!ignore) {
9858             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9859             int solved = 0;
9860             buf1[0] = NULLCHAR;
9861             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9862                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9863                 char score_buf[MSG_SIZ];
9864
9865                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9866                     nodes += u64Const(0x100000000);
9867
9868                 if (plyext != ' ' && plyext != '\t') {
9869                     time *= 100;
9870                 }
9871
9872                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9873                 if( cps->scoreIsAbsolute &&
9874                     ( gameMode == MachinePlaysBlack ||
9875                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9876                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9877                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9878                      !WhiteOnMove(currentMove)
9879                     ) )
9880                 {
9881                     curscore = -curscore;
9882                 }
9883
9884                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9885
9886                 if(*bestMove) { // rememer time best EPD move was first found
9887                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9888                     ChessMove mt; char *p = bestMove;
9889                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9890                     solved = 0;
9891                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9892                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9893                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9894                             solved = 1;
9895                             break;
9896                         }
9897                         while(*p && *p != ' ') p++;
9898                         while(*p == ' ') p++;
9899                     }
9900                     if(!solved) solvingTime = -1;
9901                 }
9902                 if(*avoidMove && !solved) {
9903                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9904                     ChessMove mt; char *p = avoidMove, solved = 1;
9905                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9906                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9907                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9908                             solved = 0; solvingTime = -2;
9909                             break;
9910                         }
9911                         while(*p && *p != ' ') p++;
9912                         while(*p == ' ') p++;
9913                     }
9914                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9915                 }
9916
9917                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9918                         char buf[MSG_SIZ];
9919                         FILE *f;
9920                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9921                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9922                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9923                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9924                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9925                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9926                                 fclose(f);
9927                         }
9928                         else
9929                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9930                           DisplayError(_("failed writing PV"), 0);
9931                 }
9932
9933                 tempStats.depth = plylev;
9934                 tempStats.nodes = nodes;
9935                 tempStats.time = time;
9936                 tempStats.score = curscore;
9937                 tempStats.got_only_move = 0;
9938
9939                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9940                         int ticklen;
9941
9942                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9943                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9944                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9945                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9946                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9947                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9948                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9949                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9950                 }
9951
9952                 /* Buffer overflow protection */
9953                 if (pv[0] != NULLCHAR) {
9954                     if (strlen(pv) >= sizeof(tempStats.movelist)
9955                         && appData.debugMode) {
9956                         fprintf(debugFP,
9957                                 "PV is too long; using the first %u bytes.\n",
9958                                 (unsigned) sizeof(tempStats.movelist) - 1);
9959                     }
9960
9961                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9962                 } else {
9963                     sprintf(tempStats.movelist, " no PV\n");
9964                 }
9965
9966                 if (tempStats.seen_stat) {
9967                     tempStats.ok_to_send = 1;
9968                 }
9969
9970                 if (strchr(tempStats.movelist, '(') != NULL) {
9971                     tempStats.line_is_book = 1;
9972                     tempStats.nr_moves = 0;
9973                     tempStats.moves_left = 0;
9974                 } else {
9975                     tempStats.line_is_book = 0;
9976                 }
9977
9978                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9979                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9980
9981                 SendProgramStatsToFrontend( cps, &tempStats );
9982
9983                 /*
9984                     [AS] Protect the thinkOutput buffer from overflow... this
9985                     is only useful if buf1 hasn't overflowed first!
9986                 */
9987                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9988                 if(curscore >= MATE_SCORE) 
9989                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9990                 else if(curscore <= -MATE_SCORE) 
9991                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9992                 else
9993                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9994                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9995                          plylev,
9996                          (gameMode == TwoMachinesPlay ?
9997                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9998                          score_buf,
9999                          prefixHint ? lastHint : "",
10000                          prefixHint ? " " : "" );
10001
10002                 if( buf1[0] != NULLCHAR ) {
10003                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
10004
10005                     if( strlen(pv) > max_len ) {
10006                         if( appData.debugMode) {
10007                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
10008                         }
10009                         pv[max_len+1] = '\0';
10010                     }
10011
10012                     strcat( thinkOutput, pv);
10013                 }
10014
10015                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
10016                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10017                     DisplayMove(currentMove - 1);
10018                 }
10019                 return;
10020
10021             } else if ((p=StrStr(message, "(only move)")) != NULL) {
10022                 /* crafty (9.25+) says "(only move) <move>"
10023                  * if there is only 1 legal move
10024                  */
10025                 sscanf(p, "(only move) %s", buf1);
10026                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
10027                 sprintf(programStats.movelist, "%s (only move)", buf1);
10028                 programStats.depth = 1;
10029                 programStats.nr_moves = 1;
10030                 programStats.moves_left = 1;
10031                 programStats.nodes = 1;
10032                 programStats.time = 1;
10033                 programStats.got_only_move = 1;
10034
10035                 /* Not really, but we also use this member to
10036                    mean "line isn't going to change" (Crafty
10037                    isn't searching, so stats won't change) */
10038                 programStats.line_is_book = 1;
10039
10040                 SendProgramStatsToFrontend( cps, &programStats );
10041
10042                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10043                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10044                     DisplayMove(currentMove - 1);
10045                 }
10046                 return;
10047             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
10048                               &time, &nodes, &plylev, &mvleft,
10049                               &mvtot, mvname) >= 5) {
10050                 /* The stat01: line is from Crafty (9.29+) in response
10051                    to the "." command */
10052                 programStats.seen_stat = 1;
10053                 cps->maybeThinking = TRUE;
10054
10055                 if (programStats.got_only_move || !appData.periodicUpdates)
10056                   return;
10057
10058                 programStats.depth = plylev;
10059                 programStats.time = time;
10060                 programStats.nodes = nodes;
10061                 programStats.moves_left = mvleft;
10062                 programStats.nr_moves = mvtot;
10063                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
10064                 programStats.ok_to_send = 1;
10065                 programStats.movelist[0] = '\0';
10066
10067                 SendProgramStatsToFrontend( cps, &programStats );
10068
10069                 return;
10070
10071             } else if (strncmp(message,"++",2) == 0) {
10072                 /* Crafty 9.29+ outputs this */
10073                 programStats.got_fail = 2;
10074                 return;
10075
10076             } else if (strncmp(message,"--",2) == 0) {
10077                 /* Crafty 9.29+ outputs this */
10078                 programStats.got_fail = 1;
10079                 return;
10080
10081             } else if (thinkOutput[0] != NULLCHAR &&
10082                        strncmp(message, "    ", 4) == 0) {
10083                 unsigned message_len;
10084
10085                 p = message;
10086                 while (*p && *p == ' ') p++;
10087
10088                 message_len = strlen( p );
10089
10090                 /* [AS] Avoid buffer overflow */
10091                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
10092                     strcat(thinkOutput, " ");
10093                     strcat(thinkOutput, p);
10094                 }
10095
10096                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
10097                     strcat(programStats.movelist, " ");
10098                     strcat(programStats.movelist, p);
10099                 }
10100
10101                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
10102                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
10103                     DisplayMove(currentMove - 1);
10104                 }
10105                 return;
10106             }
10107         }
10108         else {
10109             buf1[0] = NULLCHAR;
10110
10111             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
10112                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
10113             {
10114                 ChessProgramStats cpstats;
10115
10116                 if (plyext != ' ' && plyext != '\t') {
10117                     time *= 100;
10118                 }
10119
10120                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10121                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10122                     curscore = -curscore;
10123                 }
10124
10125                 cpstats.depth = plylev;
10126                 cpstats.nodes = nodes;
10127                 cpstats.time = time;
10128                 cpstats.score = curscore;
10129                 cpstats.got_only_move = 0;
10130                 cpstats.movelist[0] = '\0';
10131
10132                 if (buf1[0] != NULLCHAR) {
10133                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10134                 }
10135
10136                 cpstats.ok_to_send = 0;
10137                 cpstats.line_is_book = 0;
10138                 cpstats.nr_moves = 0;
10139                 cpstats.moves_left = 0;
10140
10141                 SendProgramStatsToFrontend( cps, &cpstats );
10142             }
10143         }
10144     }
10145 }
10146
10147
10148 /* Parse a game score from the character string "game", and
10149    record it as the history of the current game.  The game
10150    score is NOT assumed to start from the standard position.
10151    The display is not updated in any way.
10152    */
10153 void
10154 ParseGameHistory (char *game)
10155 {
10156     ChessMove moveType;
10157     int fromX, fromY, toX, toY, boardIndex, mask;
10158     char promoChar;
10159     char *p, *q;
10160     char buf[MSG_SIZ];
10161
10162     if (appData.debugMode)
10163       fprintf(debugFP, "Parsing game history: %s\n", game);
10164
10165     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10166     gameInfo.site = StrSave(appData.icsHost);
10167     gameInfo.date = PGNDate();
10168     gameInfo.round = StrSave("-");
10169
10170     /* Parse out names of players */
10171     while (*game == ' ') game++;
10172     p = buf;
10173     while (*game != ' ') *p++ = *game++;
10174     *p = NULLCHAR;
10175     gameInfo.white = StrSave(buf);
10176     while (*game == ' ') game++;
10177     p = buf;
10178     while (*game != ' ' && *game != '\n') *p++ = *game++;
10179     *p = NULLCHAR;
10180     gameInfo.black = StrSave(buf);
10181
10182     /* Parse moves */
10183     boardIndex = blackPlaysFirst ? 1 : 0;
10184     yynewstr(game);
10185     for (;;) {
10186         yyboardindex = boardIndex;
10187         moveType = (ChessMove) Myylex();
10188         switch (moveType) {
10189           case IllegalMove:             /* maybe suicide chess, etc. */
10190   if (appData.debugMode) {
10191     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10192     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10193     setbuf(debugFP, NULL);
10194   }
10195           case WhitePromotion:
10196           case BlackPromotion:
10197           case WhiteNonPromotion:
10198           case BlackNonPromotion:
10199           case NormalMove:
10200           case FirstLeg:
10201           case WhiteCapturesEnPassant:
10202           case BlackCapturesEnPassant:
10203           case WhiteKingSideCastle:
10204           case WhiteQueenSideCastle:
10205           case BlackKingSideCastle:
10206           case BlackQueenSideCastle:
10207           case WhiteKingSideCastleWild:
10208           case WhiteQueenSideCastleWild:
10209           case BlackKingSideCastleWild:
10210           case BlackQueenSideCastleWild:
10211           /* PUSH Fabien */
10212           case WhiteHSideCastleFR:
10213           case WhiteASideCastleFR:
10214           case BlackHSideCastleFR:
10215           case BlackASideCastleFR:
10216           /* POP Fabien */
10217             fromX = currentMoveString[0] - AAA;
10218             fromY = currentMoveString[1] - ONE;
10219             toX = currentMoveString[2] - AAA;
10220             toY = currentMoveString[3] - ONE;
10221             promoChar = currentMoveString[4];
10222             break;
10223           case WhiteDrop:
10224           case BlackDrop:
10225             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10226             fromX = moveType == WhiteDrop ?
10227               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10228             (int) CharToPiece(ToLower(currentMoveString[0]));
10229             fromY = DROP_RANK;
10230             toX = currentMoveString[2] - AAA;
10231             toY = currentMoveString[3] - ONE;
10232             promoChar = NULLCHAR;
10233             break;
10234           case AmbiguousMove:
10235             /* bug? */
10236             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10237   if (appData.debugMode) {
10238     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10239     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10240     setbuf(debugFP, NULL);
10241   }
10242             DisplayError(buf, 0);
10243             return;
10244           case ImpossibleMove:
10245             /* bug? */
10246             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10247   if (appData.debugMode) {
10248     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10249     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10250     setbuf(debugFP, NULL);
10251   }
10252             DisplayError(buf, 0);
10253             return;
10254           case EndOfFile:
10255             if (boardIndex < backwardMostMove) {
10256                 /* Oops, gap.  How did that happen? */
10257                 DisplayError(_("Gap in move list"), 0);
10258                 return;
10259             }
10260             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10261             if (boardIndex > forwardMostMove) {
10262                 forwardMostMove = boardIndex;
10263             }
10264             return;
10265           case ElapsedTime:
10266             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10267                 strcat(parseList[boardIndex-1], " ");
10268                 strcat(parseList[boardIndex-1], yy_text);
10269             }
10270             continue;
10271           case Comment:
10272           case PGNTag:
10273           case NAG:
10274           default:
10275             /* ignore */
10276             continue;
10277           case WhiteWins:
10278           case BlackWins:
10279           case GameIsDrawn:
10280           case GameUnfinished:
10281             if (gameMode == IcsExamining) {
10282                 if (boardIndex < backwardMostMove) {
10283                     /* Oops, gap.  How did that happen? */
10284                     return;
10285                 }
10286                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10287                 return;
10288             }
10289             gameInfo.result = moveType;
10290             p = strchr(yy_text, '{');
10291             if (p == NULL) p = strchr(yy_text, '(');
10292             if (p == NULL) {
10293                 p = yy_text;
10294                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10295             } else {
10296                 q = strchr(p, *p == '{' ? '}' : ')');
10297                 if (q != NULL) *q = NULLCHAR;
10298                 p++;
10299             }
10300             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10301             gameInfo.resultDetails = StrSave(p);
10302             continue;
10303         }
10304         if (boardIndex >= forwardMostMove &&
10305             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10306             backwardMostMove = blackPlaysFirst ? 1 : 0;
10307             return;
10308         }
10309         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10310                                  fromY, fromX, toY, toX, promoChar,
10311                                  parseList[boardIndex]);
10312         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10313         /* currentMoveString is set as a side-effect of yylex */
10314         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10315         strcat(moveList[boardIndex], "\n");
10316         boardIndex++;
10317         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10318         mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10319         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10320           case MT_NONE:
10321           case MT_STALEMATE:
10322           default:
10323             break;
10324           case MT_CHECK:
10325             if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10326             if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10327                 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10328                 break;
10329             }
10330           case MT_CHECKMATE:
10331           case MT_STAINMATE:
10332             strcat(parseList[boardIndex - 1], "#");
10333             break;
10334         }
10335     }
10336 }
10337
10338
10339 /* Apply a move to the given board  */
10340 void
10341 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10342 {
10343   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10344   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10345
10346     /* [HGM] compute & store e.p. status and castling rights for new position */
10347     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10348
10349       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10350       oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10351       board[EP_STATUS] = EP_NONE;
10352       board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10353
10354   if (fromY == DROP_RANK) {
10355         /* must be first */
10356         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10357             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10358             return;
10359         }
10360         piece = board[toY][toX] = (ChessSquare) fromX;
10361   } else {
10362 //      ChessSquare victim;
10363       int i;
10364
10365       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10366 //           victim = board[killY][killX],
10367            killed = board[killY][killX],
10368            board[killY][killX] = EmptySquare,
10369            board[EP_STATUS] = EP_CAPTURE;
10370            if( kill2X >= 0 && kill2Y >= 0)
10371              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10372       }
10373
10374       if( board[toY][toX] != EmptySquare ) {
10375            board[EP_STATUS] = EP_CAPTURE;
10376            if( (fromX != toX || fromY != toY) && // not igui!
10377                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10378                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10379                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10380            }
10381       }
10382
10383       pawn = board[fromY][fromX];
10384       if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10385         if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10386             captured = board[lastRank][lastFile]; // remove victim
10387             board[lastRank][lastFile] = EmptySquare;
10388             pawn = EmptySquare; // kludge to suppress old e.p. code
10389         }
10390       }
10391       if( pawn == WhiteLance || pawn == BlackLance ) {
10392            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10393                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10394                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10395            }
10396       }
10397       if( pawn == WhitePawn ) {
10398            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10399                board[EP_STATUS] = EP_PAWN_MOVE;
10400            if( toY-fromY>=2) {
10401                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10402                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10403                         gameInfo.variant != VariantBerolina || toX < fromX)
10404                       board[EP_STATUS] = toX | berolina;
10405                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10406                         gameInfo.variant != VariantBerolina || toX > fromX)
10407                       board[EP_STATUS] = toX;
10408                board[LAST_TO] = toX + 256*toY;
10409            }
10410       } else
10411       if( pawn == BlackPawn ) {
10412            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10413                board[EP_STATUS] = EP_PAWN_MOVE;
10414            if( toY-fromY<= -2) {
10415                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10416                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10417                         gameInfo.variant != VariantBerolina || toX < fromX)
10418                       board[EP_STATUS] = toX | berolina;
10419                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10420                         gameInfo.variant != VariantBerolina || toX > fromX)
10421                       board[EP_STATUS] = toX;
10422                board[LAST_TO] = toX + 256*toY;
10423            }
10424        }
10425
10426        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10427        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10428        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10429        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10430
10431        for(i=0; i<nrCastlingRights; i++) {
10432            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10433               board[CASTLING][i] == toX   && castlingRank[i] == toY
10434              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10435        }
10436
10437        if(gameInfo.variant == VariantSChess) { // update virginity
10438            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10439            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10440            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10441            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10442        }
10443
10444      if (fromX == toX && fromY == toY && killX < 0) return;
10445
10446      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10447      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10448      if(gameInfo.variant == VariantKnightmate)
10449          king += (int) WhiteUnicorn - (int) WhiteKing;
10450
10451     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10452        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10453         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10454         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10455         board[EP_STATUS] = EP_NONE; // capture was fake!
10456     } else
10457     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10458         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10459         board[toY][toX] = piece;
10460         board[EP_STATUS] = EP_NONE; // capture was fake!
10461     } else
10462     /* Code added by Tord: */
10463     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10464     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10465         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10466       board[EP_STATUS] = EP_NONE; // capture was fake!
10467       board[fromY][fromX] = EmptySquare;
10468       board[toY][toX] = EmptySquare;
10469       if((toX > fromX) != (piece == WhiteRook)) {
10470         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10471       } else {
10472         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10473       }
10474     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10475                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10476       board[EP_STATUS] = EP_NONE;
10477       board[fromY][fromX] = EmptySquare;
10478       board[toY][toX] = EmptySquare;
10479       if((toX > fromX) != (piece == BlackRook)) {
10480         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10481       } else {
10482         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10483       }
10484     /* End of code added by Tord */
10485
10486     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10487         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10488         board[toY][toX] = piece;
10489     } else if (board[fromY][fromX] == king
10490         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10491         && toY == fromY && toX > fromX+1) {
10492         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10493                                                                                              ; // castle with nearest piece
10494         board[fromY][toX-1] = board[fromY][rookX];
10495         board[fromY][rookX] = EmptySquare;
10496         board[fromY][fromX] = EmptySquare;
10497         board[toY][toX] = king;
10498     } else if (board[fromY][fromX] == king
10499         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10500                && toY == fromY && toX < fromX-1) {
10501         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10502                                                                                   ; // castle with nearest piece
10503         board[fromY][toX+1] = board[fromY][rookX];
10504         board[fromY][rookX] = EmptySquare;
10505         board[fromY][fromX] = EmptySquare;
10506         board[toY][toX] = king;
10507     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10508                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10509                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10510                ) {
10511         /* white pawn promotion */
10512         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10513         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10514             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10515         board[fromY][fromX] = EmptySquare;
10516     } else if ((fromY >= BOARD_HEIGHT>>1)
10517                && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10518                && (toX != fromX)
10519                && gameInfo.variant != VariantXiangqi
10520                && gameInfo.variant != VariantBerolina
10521                && (pawn == WhitePawn)
10522                && (board[toY][toX] == EmptySquare)) {
10523         if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10524         board[fromY][fromX] = EmptySquare;
10525         board[toY][toX] = piece;
10526         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10527     } else if ((fromY == BOARD_HEIGHT-4)
10528                && (toX == fromX)
10529                && gameInfo.variant == VariantBerolina
10530                && (board[fromY][fromX] == WhitePawn)
10531                && (board[toY][toX] == EmptySquare)) {
10532         board[fromY][fromX] = EmptySquare;
10533         board[toY][toX] = WhitePawn;
10534         if(oldEP & EP_BEROLIN_A) {
10535                 captured = board[fromY][fromX-1];
10536                 board[fromY][fromX-1] = EmptySquare;
10537         }else{  captured = board[fromY][fromX+1];
10538                 board[fromY][fromX+1] = EmptySquare;
10539         }
10540     } else if (board[fromY][fromX] == king
10541         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10542                && toY == fromY && toX > fromX+1) {
10543         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10544                                                                                              ;
10545         board[fromY][toX-1] = board[fromY][rookX];
10546         board[fromY][rookX] = EmptySquare;
10547         board[fromY][fromX] = EmptySquare;
10548         board[toY][toX] = king;
10549     } else if (board[fromY][fromX] == king
10550         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10551                && toY == fromY && toX < fromX-1) {
10552         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10553                                                                                 ;
10554         board[fromY][toX+1] = board[fromY][rookX];
10555         board[fromY][rookX] = EmptySquare;
10556         board[fromY][fromX] = EmptySquare;
10557         board[toY][toX] = king;
10558     } else if (fromY == 7 && fromX == 3
10559                && board[fromY][fromX] == BlackKing
10560                && toY == 7 && toX == 5) {
10561         board[fromY][fromX] = EmptySquare;
10562         board[toY][toX] = BlackKing;
10563         board[fromY][7] = EmptySquare;
10564         board[toY][4] = BlackRook;
10565     } else if (fromY == 7 && fromX == 3
10566                && board[fromY][fromX] == BlackKing
10567                && toY == 7 && toX == 1) {
10568         board[fromY][fromX] = EmptySquare;
10569         board[toY][toX] = BlackKing;
10570         board[fromY][0] = EmptySquare;
10571         board[toY][2] = BlackRook;
10572     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10573                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10574                && toY < promoRank && promoChar
10575                ) {
10576         /* black pawn promotion */
10577         board[toY][toX] = CharToPiece(ToLower(promoChar));
10578         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10579             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10580         board[fromY][fromX] = EmptySquare;
10581     } else if ((fromY < BOARD_HEIGHT>>1)
10582                && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10583                && (toX != fromX)
10584                && gameInfo.variant != VariantXiangqi
10585                && gameInfo.variant != VariantBerolina
10586                && (pawn == BlackPawn)
10587                && (board[toY][toX] == EmptySquare)) {
10588         if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10589         board[fromY][fromX] = EmptySquare;
10590         board[toY][toX] = piece;
10591         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10592     } else if ((fromY == 3)
10593                && (toX == fromX)
10594                && gameInfo.variant == VariantBerolina
10595                && (board[fromY][fromX] == BlackPawn)
10596                && (board[toY][toX] == EmptySquare)) {
10597         board[fromY][fromX] = EmptySquare;
10598         board[toY][toX] = BlackPawn;
10599         if(oldEP & EP_BEROLIN_A) {
10600                 captured = board[fromY][fromX-1];
10601                 board[fromY][fromX-1] = EmptySquare;
10602         }else{  captured = board[fromY][fromX+1];
10603                 board[fromY][fromX+1] = EmptySquare;
10604         }
10605     } else {
10606         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10607         board[fromY][fromX] = EmptySquare;
10608         board[toY][toX] = piece;
10609     }
10610   }
10611
10612     if (gameInfo.holdingsWidth != 0) {
10613
10614       /* !!A lot more code needs to be written to support holdings  */
10615       /* [HGM] OK, so I have written it. Holdings are stored in the */
10616       /* penultimate board files, so they are automaticlly stored   */
10617       /* in the game history.                                       */
10618       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10619                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10620         /* Delete from holdings, by decreasing count */
10621         /* and erasing image if necessary            */
10622         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10623         if(p < (int) BlackPawn) { /* white drop */
10624              p -= (int)WhitePawn;
10625                  p = PieceToNumber((ChessSquare)p);
10626              if(p >= gameInfo.holdingsSize) p = 0;
10627              if(--board[p][BOARD_WIDTH-2] <= 0)
10628                   board[p][BOARD_WIDTH-1] = EmptySquare;
10629              if((int)board[p][BOARD_WIDTH-2] < 0)
10630                         board[p][BOARD_WIDTH-2] = 0;
10631         } else {                  /* black drop */
10632              p -= (int)BlackPawn;
10633                  p = PieceToNumber((ChessSquare)p);
10634              if(p >= gameInfo.holdingsSize) p = 0;
10635              if(--board[handSize-1-p][1] <= 0)
10636                   board[handSize-1-p][0] = EmptySquare;
10637              if((int)board[handSize-1-p][1] < 0)
10638                         board[handSize-1-p][1] = 0;
10639         }
10640       }
10641       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10642           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10643         /* [HGM] holdings: Add to holdings, if holdings exist */
10644         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10645                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10646                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10647         }
10648         p = (int) captured;
10649         if (p >= (int) BlackPawn) {
10650           p -= (int)BlackPawn;
10651           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10652                   /* Restore shogi-promoted piece to its original  first */
10653                   captured = (ChessSquare) (DEMOTED(captured));
10654                   p = DEMOTED(p);
10655           }
10656           p = PieceToNumber((ChessSquare)p);
10657           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10658           board[p][BOARD_WIDTH-2]++;
10659           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10660         } else {
10661           p -= (int)WhitePawn;
10662           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10663                   captured = (ChessSquare) (DEMOTED(captured));
10664                   p = DEMOTED(p);
10665           }
10666           p = PieceToNumber((ChessSquare)p);
10667           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10668           board[handSize-1-p][1]++;
10669           board[handSize-1-p][0] = WHITE_TO_BLACK captured;
10670         }
10671       }
10672     } else if (gameInfo.variant == VariantAtomic) {
10673       if (captured != EmptySquare) {
10674         int y, x;
10675         for (y = toY-1; y <= toY+1; y++) {
10676           for (x = toX-1; x <= toX+1; x++) {
10677             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10678                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10679               board[y][x] = EmptySquare;
10680             }
10681           }
10682         }
10683         board[toY][toX] = EmptySquare;
10684       }
10685     }
10686
10687     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10688         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10689     } else
10690     if(promoChar == '+') {
10691         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10692         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10693         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10694           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10695     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10696         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10697         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10698            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10699         board[toY][toX] = newPiece;
10700     }
10701     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10702                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10703         // [HGM] superchess: take promotion piece out of holdings
10704         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10705         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10706             if(!--board[k][BOARD_WIDTH-2])
10707                 board[k][BOARD_WIDTH-1] = EmptySquare;
10708         } else {
10709             if(!--board[handSize-1-k][1])
10710                 board[handSize-1-k][0] = EmptySquare;
10711         }
10712     }
10713 }
10714
10715 /* Updates forwardMostMove */
10716 void
10717 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10718 {
10719     int x = toX, y = toY, mask;
10720     char *s = parseList[forwardMostMove];
10721     ChessSquare p = boards[forwardMostMove][toY][toX];
10722 //    forwardMostMove++; // [HGM] bare: moved downstream
10723
10724     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10725     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10726     (void) CoordsToAlgebraic(boards[forwardMostMove],
10727                              PosFlags(forwardMostMove),
10728                              fromY, fromX, y, x, (killX < 0)*promoChar,
10729                              s);
10730     if(kill2X >= 0 && kill2Y >= 0)
10731         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10732     if(killX >= 0 && killY >= 0)
10733         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10734                                            toX + AAA, toY + ONE - '0', promoChar);
10735
10736     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10737         int timeLeft; static int lastLoadFlag=0; int king, piece;
10738         piece = boards[forwardMostMove][fromY][fromX];
10739         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10740         if(gameInfo.variant == VariantKnightmate)
10741             king += (int) WhiteUnicorn - (int) WhiteKing;
10742         if(forwardMostMove == 0) {
10743             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10744                 fprintf(serverMoves, "%s;", UserName());
10745             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10746                 fprintf(serverMoves, "%s;", second.tidy);
10747             fprintf(serverMoves, "%s;", first.tidy);
10748             if(gameMode == MachinePlaysWhite)
10749                 fprintf(serverMoves, "%s;", UserName());
10750             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10751                 fprintf(serverMoves, "%s;", second.tidy);
10752         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10753         lastLoadFlag = loadFlag;
10754         // print base move
10755         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10756         // print castling suffix
10757         if( toY == fromY && piece == king ) {
10758             if(toX-fromX > 1)
10759                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10760             if(fromX-toX >1)
10761                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10762         }
10763         // e.p. suffix
10764         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10765              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10766              boards[forwardMostMove][toY][toX] == EmptySquare
10767              && fromX != toX && fromY != toY)
10768                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10769         // promotion suffix
10770         if(promoChar != NULLCHAR) {
10771             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10772                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10773                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10774             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10775         }
10776         if(!loadFlag) {
10777                 char buf[MOVE_LEN*2], *p; int len;
10778             fprintf(serverMoves, "/%d/%d",
10779                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10780             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10781             else                      timeLeft = blackTimeRemaining/1000;
10782             fprintf(serverMoves, "/%d", timeLeft);
10783                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10784                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10785                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10786                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10787             fprintf(serverMoves, "/%s", buf);
10788         }
10789         fflush(serverMoves);
10790     }
10791
10792     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10793         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10794       return;
10795     }
10796     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10797     if (commentList[forwardMostMove+1] != NULL) {
10798         free(commentList[forwardMostMove+1]);
10799         commentList[forwardMostMove+1] = NULL;
10800     }
10801     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10802     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10803     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10804     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10805     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10806     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10807     adjustedClock = FALSE;
10808     gameInfo.result = GameUnfinished;
10809     if (gameInfo.resultDetails != NULL) {
10810         free(gameInfo.resultDetails);
10811         gameInfo.resultDetails = NULL;
10812     }
10813     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10814                               moveList[forwardMostMove - 1]);
10815     mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10816     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10817       case MT_NONE:
10818       case MT_STALEMATE:
10819       default:
10820         break;
10821       case MT_CHECK:
10822         if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10823         if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10824             if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10825             break;
10826         }
10827       case MT_CHECKMATE:
10828       case MT_STAINMATE:
10829         strcat(parseList[forwardMostMove - 1], "#");
10830         break;
10831     }
10832 }
10833
10834 /* Updates currentMove if not pausing */
10835 void
10836 ShowMove (int fromX, int fromY, int toX, int toY)
10837 {
10838     int instant = (gameMode == PlayFromGameFile) ?
10839         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10840     if(appData.noGUI) return;
10841     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10842         if (!instant) {
10843             if (forwardMostMove == currentMove + 1) {
10844                 AnimateMove(boards[forwardMostMove - 1],
10845                             fromX, fromY, toX, toY);
10846             }
10847         }
10848         currentMove = forwardMostMove;
10849     }
10850
10851     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10852
10853     if (instant) return;
10854
10855     DisplayMove(currentMove - 1);
10856     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10857             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10858                 SetHighlights(fromX, fromY, toX, toY);
10859             }
10860     }
10861     DrawPosition(FALSE, boards[currentMove]);
10862     DisplayBothClocks();
10863     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10864 }
10865
10866 void
10867 SendEgtPath (ChessProgramState *cps)
10868 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10869         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10870
10871         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10872
10873         while(*p) {
10874             char c, *q = name+1, *r, *s;
10875
10876             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10877             while(*p && *p != ',') *q++ = *p++;
10878             *q++ = ':'; *q = 0;
10879             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10880                 strcmp(name, ",nalimov:") == 0 ) {
10881                 // take nalimov path from the menu-changeable option first, if it is defined
10882               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10883                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10884             } else
10885             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10886                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10887                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10888                 s = r = StrStr(s, ":") + 1; // beginning of path info
10889                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10890                 c = *r; *r = 0;             // temporarily null-terminate path info
10891                     *--q = 0;               // strip of trailig ':' from name
10892                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10893                 *r = c;
10894                 SendToProgram(buf,cps);     // send egtbpath command for this format
10895             }
10896             if(*p == ',') p++; // read away comma to position for next format name
10897         }
10898 }
10899
10900 static int
10901 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10902 {
10903       int width = 8, height = 8, holdings = 0;             // most common sizes
10904       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10905       // correct the deviations default for each variant
10906       if( v == VariantXiangqi ) width = 9,  height = 10;
10907       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10908       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10909       if( v == VariantCapablanca || v == VariantCapaRandom ||
10910           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10911                                 width = 10;
10912       if( v == VariantCourier ) width = 12;
10913       if( v == VariantSuper )                            holdings = 8;
10914       if( v == VariantGreat )   width = 10,              holdings = 8;
10915       if( v == VariantSChess )                           holdings = 7;
10916       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10917       if( v == VariantChuChess) width = 10, height = 10;
10918       if( v == VariantChu )     width = 12, height = 12;
10919       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10920              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10921              holdingsSize >= 0 && holdingsSize != holdings;
10922 }
10923
10924 char variantError[MSG_SIZ];
10925
10926 char *
10927 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10928 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10929       char *p, *variant = VariantName(v);
10930       static char b[MSG_SIZ];
10931       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10932            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10933                                                holdingsSize, variant); // cook up sized variant name
10934            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10935            if(StrStr(list, b) == NULL) {
10936                // specific sized variant not known, check if general sizing allowed
10937                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10938                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10939                             boardWidth, boardHeight, holdingsSize, engine);
10940                    return NULL;
10941                }
10942                /* [HGM] here we really should compare with the maximum supported board size */
10943            }
10944       } else snprintf(b, MSG_SIZ,"%s", variant);
10945       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10946       p = StrStr(list, b);
10947       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10948       if(p == NULL) {
10949           // occurs not at all in list, or only as sub-string
10950           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10951           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10952               int l = strlen(variantError);
10953               char *q;
10954               while(p != list && p[-1] != ',') p--;
10955               q = strchr(p, ',');
10956               if(q) *q = NULLCHAR;
10957               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10958               if(q) *q= ',';
10959           }
10960           return NULL;
10961       }
10962       return b;
10963 }
10964
10965 void
10966 InitChessProgram (ChessProgramState *cps, int setup)
10967 /* setup needed to setup FRC opening position */
10968 {
10969     char buf[MSG_SIZ], *b;
10970     if (appData.noChessProgram) return;
10971     hintRequested = FALSE;
10972     bookRequested = FALSE;
10973
10974     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10975     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10976     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10977     if(cps->memSize) { /* [HGM] memory */
10978       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10979         SendToProgram(buf, cps);
10980     }
10981     SendEgtPath(cps); /* [HGM] EGT */
10982     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10983       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10984         SendToProgram(buf, cps);
10985     }
10986
10987     setboardSpoiledMachineBlack = FALSE;
10988     SendToProgram(cps->initString, cps);
10989     if (gameInfo.variant != VariantNormal &&
10990         gameInfo.variant != VariantLoadable
10991         /* [HGM] also send variant if board size non-standard */
10992         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10993
10994       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10995                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10996
10997       if (b == NULL) {
10998         VariantClass v;
10999         char c, *q = cps->variants, *p = strchr(q, ',');
11000         if(p) *p = NULLCHAR;
11001         v = StringToVariant(q);
11002         DisplayError(variantError, 0);
11003         if(v != VariantUnknown && cps == &first) {
11004             int w, h, s;
11005             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
11006                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
11007             ASSIGN(appData.variant, q);
11008             Reset(TRUE, FALSE);
11009         }
11010         if(p) *p = ',';
11011         return;
11012       }
11013
11014       snprintf(buf, MSG_SIZ, "variant %s\n", b);
11015       SendToProgram(buf, cps);
11016     }
11017     currentlyInitializedVariant = gameInfo.variant;
11018
11019     /* [HGM] send opening position in FRC to first engine */
11020     if(setup) {
11021           SendToProgram("force\n", cps);
11022           SendBoard(cps, 0);
11023           /* engine is now in force mode! Set flag to wake it up after first move. */
11024           setboardSpoiledMachineBlack = 1;
11025     }
11026
11027     if (cps->sendICS) {
11028       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
11029       SendToProgram(buf, cps);
11030     }
11031     cps->maybeThinking = FALSE;
11032     cps->offeredDraw = 0;
11033     if (!appData.icsActive) {
11034         SendTimeControl(cps, movesPerSession, timeControl,
11035                         timeIncrement, appData.searchDepth,
11036                         searchTime);
11037     }
11038     if (appData.showThinking
11039         // [HGM] thinking: four options require thinking output to be sent
11040         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
11041                                 ) {
11042         SendToProgram("post\n", cps);
11043     }
11044     SendToProgram("hard\n", cps);
11045     if (!appData.ponderNextMove) {
11046         /* Warning: "easy" is a toggle in GNU Chess, so don't send
11047            it without being sure what state we are in first.  "hard"
11048            is not a toggle, so that one is OK.
11049          */
11050         SendToProgram("easy\n", cps);
11051     }
11052     if (cps->usePing) {
11053       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
11054       SendToProgram(buf, cps);
11055     }
11056     cps->initDone = TRUE;
11057     ClearEngineOutputPane(cps == &second);
11058 }
11059
11060
11061 char *
11062 ResendOptions (ChessProgramState *cps, int toEngine)
11063 { // send the stored value of the options
11064   int i;
11065   static char buf2[MSG_SIZ*10];
11066   char buf[MSG_SIZ], *p = buf2;
11067   Option *opt = cps->option;
11068   *p = NULLCHAR;
11069   for(i=0; i<cps->nrOptions; i++, opt++) {
11070       *buf = NULLCHAR;
11071       switch(opt->type) {
11072         case Spin:
11073         case Slider:
11074         case CheckBox:
11075             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11076             snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
11077           break;
11078         case ComboBox:
11079             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
11080             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
11081           break;
11082         default:
11083             if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
11084             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
11085           break;
11086         case Button:
11087         case SaveButton:
11088           continue;
11089       }
11090       if(*buf) {
11091         if(toEngine) {
11092           snprintf(buf2, MSG_SIZ, "option %s\n", buf);
11093           SendToProgram(buf2, cps);
11094         } else {
11095           if(p != buf2) *p++ = ',';
11096           strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
11097           while(*p) p++;
11098         }
11099       }
11100   }
11101   return buf2;
11102 }
11103
11104 void
11105 StartChessProgram (ChessProgramState *cps)
11106 {
11107     char buf[MSG_SIZ];
11108     int err;
11109
11110     if (appData.noChessProgram) return;
11111     cps->initDone = FALSE;
11112
11113     if (strcmp(cps->host, "localhost") == 0) {
11114         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
11115     } else if (*appData.remoteShell == NULLCHAR) {
11116         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
11117     } else {
11118         if (*appData.remoteUser == NULLCHAR) {
11119           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11120                     cps->program);
11121         } else {
11122           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11123                     cps->host, appData.remoteUser, cps->program);
11124         }
11125         err = StartChildProcess(buf, "", &cps->pr);
11126     }
11127
11128     if (err != 0) {
11129       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11130         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11131         if(cps != &first) return;
11132         appData.noChessProgram = TRUE;
11133         ThawUI();
11134         SetNCPMode();
11135 //      DisplayFatalError(buf, err, 1);
11136 //      cps->pr = NoProc;
11137 //      cps->isr = NULL;
11138         return;
11139     }
11140
11141     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11142     if (cps->protocolVersion > 1) {
11143       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11144       if(!cps->reload) { // do not clear options when reloading because of -xreuse
11145         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11146         cps->comboCnt = 0;  //                and values of combo boxes
11147       }
11148       SendToProgram(buf, cps);
11149       if(cps->reload) ResendOptions(cps, TRUE);
11150     } else {
11151       SendToProgram("xboard\n", cps);
11152     }
11153 }
11154
11155 void
11156 TwoMachinesEventIfReady P((void))
11157 {
11158   static int curMess = 0;
11159   if (first.lastPing != first.lastPong) {
11160     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11161     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11162     return;
11163   }
11164   if (second.lastPing != second.lastPong) {
11165     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11166     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11167     return;
11168   }
11169   DisplayMessage("", ""); curMess = 0;
11170   TwoMachinesEvent();
11171 }
11172
11173 char *
11174 MakeName (char *template)
11175 {
11176     time_t clock;
11177     struct tm *tm;
11178     static char buf[MSG_SIZ];
11179     char *p = buf;
11180     int i;
11181
11182     clock = time((time_t *)NULL);
11183     tm = localtime(&clock);
11184
11185     while(*p++ = *template++) if(p[-1] == '%') {
11186         switch(*template++) {
11187           case 0:   *p = 0; return buf;
11188           case 'Y': i = tm->tm_year+1900; break;
11189           case 'y': i = tm->tm_year-100; break;
11190           case 'M': i = tm->tm_mon+1; break;
11191           case 'd': i = tm->tm_mday; break;
11192           case 'h': i = tm->tm_hour; break;
11193           case 'm': i = tm->tm_min; break;
11194           case 's': i = tm->tm_sec; break;
11195           default:  i = 0;
11196         }
11197         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11198     }
11199     return buf;
11200 }
11201
11202 int
11203 CountPlayers (char *p)
11204 {
11205     int n = 0;
11206     while(p = strchr(p, '\n')) p++, n++; // count participants
11207     return n;
11208 }
11209
11210 FILE *
11211 WriteTourneyFile (char *results, FILE *f)
11212 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11213     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11214     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11215         // create a file with tournament description
11216         fprintf(f, "-participants {%s}\n", appData.participants);
11217         fprintf(f, "-seedBase %d\n", appData.seedBase);
11218         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11219         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11220         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11221         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11222         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11223         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11224         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11225         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11226         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11227         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11228         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11229         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11230         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11231         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11232         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11233         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11234         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11235         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11236         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11237         fprintf(f, "-smpCores %d\n", appData.smpCores);
11238         if(searchTime > 0)
11239                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11240         else {
11241                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11242                 fprintf(f, "-tc %s\n", appData.timeControl);
11243                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11244         }
11245         fprintf(f, "-results \"%s\"\n", results);
11246     }
11247     return f;
11248 }
11249
11250 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11251
11252 void
11253 Substitute (char *participants, int expunge)
11254 {
11255     int i, changed, changes=0, nPlayers=0;
11256     char *p, *q, *r, buf[MSG_SIZ];
11257     if(participants == NULL) return;
11258     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11259     r = p = participants; q = appData.participants;
11260     while(*p && *p == *q) {
11261         if(*p == '\n') r = p+1, nPlayers++;
11262         p++; q++;
11263     }
11264     if(*p) { // difference
11265         while(*p && *p++ != '\n')
11266                                  ;
11267         while(*q && *q++ != '\n')
11268                                  ;
11269       changed = nPlayers;
11270         changes = 1 + (strcmp(p, q) != 0);
11271     }
11272     if(changes == 1) { // a single engine mnemonic was changed
11273         q = r; while(*q) nPlayers += (*q++ == '\n');
11274         p = buf; while(*r && (*p = *r++) != '\n') p++;
11275         *p = NULLCHAR;
11276         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11277         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11278         if(mnemonic[i]) { // The substitute is valid
11279             FILE *f;
11280             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11281                 flock(fileno(f), LOCK_EX);
11282                 ParseArgsFromFile(f);
11283                 fseek(f, 0, SEEK_SET);
11284                 FREE(appData.participants); appData.participants = participants;
11285                 if(expunge) { // erase results of replaced engine
11286                     int len = strlen(appData.results), w, b, dummy;
11287                     for(i=0; i<len; i++) {
11288                         Pairing(i, nPlayers, &w, &b, &dummy);
11289                         if((w == changed || b == changed) && appData.results[i] == '*') {
11290                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11291                             fclose(f);
11292                             return;
11293                         }
11294                     }
11295                     for(i=0; i<len; i++) {
11296                         Pairing(i, nPlayers, &w, &b, &dummy);
11297                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11298                     }
11299                 }
11300                 WriteTourneyFile(appData.results, f);
11301                 fclose(f); // release lock
11302                 return;
11303             }
11304         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11305     }
11306     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11307     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11308     free(participants);
11309     return;
11310 }
11311
11312 int
11313 CheckPlayers (char *participants)
11314 {
11315         int i;
11316         char buf[MSG_SIZ], *p;
11317         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11318         while(p = strchr(participants, '\n')) {
11319             *p = NULLCHAR;
11320             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11321             if(!mnemonic[i]) {
11322                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11323                 *p = '\n';
11324                 DisplayError(buf, 0);
11325                 return 1;
11326             }
11327             *p = '\n';
11328             participants = p + 1;
11329         }
11330         return 0;
11331 }
11332
11333 int
11334 CreateTourney (char *name)
11335 {
11336         FILE *f;
11337         if(matchMode && strcmp(name, appData.tourneyFile)) {
11338              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11339         }
11340         if(name[0] == NULLCHAR) {
11341             if(appData.participants[0])
11342                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11343             return 0;
11344         }
11345         f = fopen(name, "r");
11346         if(f) { // file exists
11347             ASSIGN(appData.tourneyFile, name);
11348             ParseArgsFromFile(f); // parse it
11349         } else {
11350             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11351             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11352                 DisplayError(_("Not enough participants"), 0);
11353                 return 0;
11354             }
11355             if(CheckPlayers(appData.participants)) return 0;
11356             ASSIGN(appData.tourneyFile, name);
11357             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11358             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11359         }
11360         fclose(f);
11361         appData.noChessProgram = FALSE;
11362         appData.clockMode = TRUE;
11363         SetGNUMode();
11364         return 1;
11365 }
11366
11367 int
11368 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11369 {
11370     char buf[2*MSG_SIZ], *p, *q;
11371     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11372     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11373     skip = !all && group[0]; // if group requested, we start in skip mode
11374     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11375         p = names; q = buf; header = 0;
11376         while(*p && *p != '\n') *q++ = *p++;
11377         *q = 0;
11378         if(*p == '\n') p++;
11379         if(buf[0] == '#') {
11380             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11381             depth++; // we must be entering a new group
11382             if(all) continue; // suppress printing group headers when complete list requested
11383             header = 1;
11384             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11385         }
11386         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11387         if(engineList[i]) free(engineList[i]);
11388         engineList[i] = strdup(buf);
11389         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11390         if(engineMnemonic[i]) free(engineMnemonic[i]);
11391         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11392             strcat(buf, " (");
11393             sscanf(q + 8, "%s", buf + strlen(buf));
11394             strcat(buf, ")");
11395         }
11396         engineMnemonic[i] = strdup(buf);
11397         i++;
11398     }
11399     engineList[i] = engineMnemonic[i] = NULL;
11400     return i;
11401 }
11402
11403 void
11404 SaveEngineSettings (int n)
11405 {
11406     int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11407     if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11408     if(*engineListFile) ParseSettingsFile(engineListFile, &engineListFile); // update engine list
11409     p = strstr(firstChessProgramNames, currentEngine[n]);
11410     if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11411     optionSettings = ResendOptions(n ? &second : &first, FALSE);
11412     len = strlen(currentEngine[n]);
11413     q = p + len; *p = 0; // cut list into head and tail piece
11414     s = strstr(currentEngine[n], "firstOptions");
11415     if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11416         char *r = s + 14;
11417         while(*r && *r != s[13]) r++;
11418         s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11419         snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11420     } else if(*optionSettings) {
11421         snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11422     }
11423     ASSIGN(currentEngine[n], buf); // updated engine line
11424     len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11425     s = malloc(len);
11426     snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11427     FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11428     if(*engineListFile) SaveEngineList();
11429 }
11430
11431 // following implemented as macro to avoid type limitations
11432 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11433
11434 void
11435 SwapEngines (int n)
11436 {   // swap settings for first engine and other engine (so far only some selected options)
11437     int h;
11438     char *p;
11439     if(n == 0) return;
11440     SWAP(directory, p)
11441     SWAP(chessProgram, p)
11442     SWAP(isUCI, h)
11443     SWAP(hasOwnBookUCI, h)
11444     SWAP(protocolVersion, h)
11445     SWAP(reuse, h)
11446     SWAP(scoreIsAbsolute, h)
11447     SWAP(timeOdds, h)
11448     SWAP(logo, p)
11449     SWAP(pgnName, p)
11450     SWAP(pvSAN, h)
11451     SWAP(engOptions, p)
11452     SWAP(engInitString, p)
11453     SWAP(computerString, p)
11454     SWAP(features, p)
11455     SWAP(fenOverride, p)
11456     SWAP(NPS, h)
11457     SWAP(accumulateTC, h)
11458     SWAP(drawDepth, h)
11459     SWAP(host, p)
11460     SWAP(pseudo, h)
11461 }
11462
11463 int
11464 GetEngineLine (char *s, int n)
11465 {
11466     int i;
11467     char buf[MSG_SIZ];
11468     extern char *icsNames;
11469     if(!s || !*s) return 0;
11470     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11471     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11472     if(!mnemonic[i]) return 0;
11473     if(n == 11) return 1; // just testing if there was a match
11474     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11475     if(n == 1) SwapEngines(n);
11476     ParseArgsFromString(buf);
11477     if(n == 1) SwapEngines(n);
11478     if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11479     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11480         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11481         ParseArgsFromString(buf);
11482     }
11483     return 1;
11484 }
11485
11486 int
11487 SetPlayer (int player, char *p)
11488 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11489     int i;
11490     char buf[MSG_SIZ], *engineName;
11491     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11492     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11493     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11494     if(mnemonic[i]) {
11495         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11496         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11497         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11498         ParseArgsFromString(buf);
11499     } else { // no engine with this nickname is installed!
11500         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11501         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11502         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11503         ModeHighlight();
11504         DisplayError(buf, 0);
11505         return 0;
11506     }
11507     free(engineName);
11508     return i;
11509 }
11510
11511 char *recentEngines;
11512
11513 void
11514 RecentEngineEvent (int nr)
11515 {
11516     int n;
11517 //    SwapEngines(1); // bump first to second
11518 //    ReplaceEngine(&second, 1); // and load it there
11519     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11520     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11521     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11522         ReplaceEngine(&first, 0);
11523         FloatToFront(&appData.recentEngineList, command[n]);
11524         ASSIGN(currentEngine[0], command[n]);
11525     }
11526 }
11527
11528 int
11529 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11530 {   // determine players from game number
11531     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11532
11533     if(appData.tourneyType == 0) {
11534         roundsPerCycle = (nPlayers - 1) | 1;
11535         pairingsPerRound = nPlayers / 2;
11536     } else if(appData.tourneyType > 0) {
11537         roundsPerCycle = nPlayers - appData.tourneyType;
11538         pairingsPerRound = appData.tourneyType;
11539     }
11540     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11541     gamesPerCycle = gamesPerRound * roundsPerCycle;
11542     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11543     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11544     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11545     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11546     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11547     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11548
11549     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11550     if(appData.roundSync) *syncInterval = gamesPerRound;
11551
11552     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11553
11554     if(appData.tourneyType == 0) {
11555         if(curPairing == (nPlayers-1)/2 ) {
11556             *whitePlayer = curRound;
11557             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11558         } else {
11559             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11560             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11561             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11562             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11563         }
11564     } else if(appData.tourneyType > 1) {
11565         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11566         *whitePlayer = curRound + appData.tourneyType;
11567     } else if(appData.tourneyType > 0) {
11568         *whitePlayer = curPairing;
11569         *blackPlayer = curRound + appData.tourneyType;
11570     }
11571
11572     // take care of white/black alternation per round.
11573     // For cycles and games this is already taken care of by default, derived from matchGame!
11574     return curRound & 1;
11575 }
11576
11577 int
11578 NextTourneyGame (int nr, int *swapColors)
11579 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11580     char *p, *q;
11581     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11582     FILE *tf;
11583     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11584     tf = fopen(appData.tourneyFile, "r");
11585     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11586     ParseArgsFromFile(tf); fclose(tf);
11587     InitTimeControls(); // TC might be altered from tourney file
11588
11589     nPlayers = CountPlayers(appData.participants); // count participants
11590     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11591     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11592
11593     if(syncInterval) {
11594         p = q = appData.results;
11595         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11596         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11597             DisplayMessage(_("Waiting for other game(s)"),"");
11598             waitingForGame = TRUE;
11599             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11600             return 0;
11601         }
11602         waitingForGame = FALSE;
11603     }
11604
11605     if(appData.tourneyType < 0) {
11606         if(nr>=0 && !pairingReceived) {
11607             char buf[1<<16];
11608             if(pairing.pr == NoProc) {
11609                 if(!appData.pairingEngine[0]) {
11610                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11611                     return 0;
11612                 }
11613                 StartChessProgram(&pairing); // starts the pairing engine
11614             }
11615             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11616             SendToProgram(buf, &pairing);
11617             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11618             SendToProgram(buf, &pairing);
11619             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11620         }
11621         pairingReceived = 0;                              // ... so we continue here
11622         *swapColors = 0;
11623         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11624         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11625         matchGame = 1; roundNr = nr / syncInterval + 1;
11626     }
11627
11628     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11629
11630     // redefine engines, engine dir, etc.
11631     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11632     if(first.pr == NoProc) {
11633       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11634       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11635     }
11636     if(second.pr == NoProc) {
11637       SwapEngines(1);
11638       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11639       SwapEngines(1);         // and make that valid for second engine by swapping
11640       InitEngine(&second, 1);
11641     }
11642     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11643     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11644     return OK;
11645 }
11646
11647 void
11648 NextMatchGame ()
11649 {   // performs game initialization that does not invoke engines, and then tries to start the game
11650     int res, firstWhite, swapColors = 0;
11651     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11652     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
11653         char buf[MSG_SIZ];
11654         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11655         if(strcmp(buf, currentDebugFile)) { // name has changed
11656             FILE *f = fopen(buf, "w");
11657             if(f) { // if opening the new file failed, just keep using the old one
11658                 ASSIGN(currentDebugFile, buf);
11659                 fclose(debugFP);
11660                 debugFP = f;
11661             }
11662             if(appData.serverFileName) {
11663                 if(serverFP) fclose(serverFP);
11664                 serverFP = fopen(appData.serverFileName, "w");
11665                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11666                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11667             }
11668         }
11669     }
11670     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11671     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11672     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11673     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11674     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11675     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11676     Reset(FALSE, first.pr != NoProc);
11677     res = LoadGameOrPosition(matchGame); // setup game
11678     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11679     if(!res) return; // abort when bad game/pos file
11680     if(appData.epd) {// in EPD mode we make sure first engine is to move
11681         firstWhite = !(forwardMostMove & 1);
11682         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11683         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11684     }
11685     TwoMachinesEvent();
11686 }
11687
11688 void
11689 UserAdjudicationEvent (int result)
11690 {
11691     ChessMove gameResult = GameIsDrawn;
11692
11693     if( result > 0 ) {
11694         gameResult = WhiteWins;
11695     }
11696     else if( result < 0 ) {
11697         gameResult = BlackWins;
11698     }
11699
11700     if( gameMode == TwoMachinesPlay ) {
11701         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11702     }
11703 }
11704
11705
11706 // [HGM] save: calculate checksum of game to make games easily identifiable
11707 int
11708 StringCheckSum (char *s)
11709 {
11710         int i = 0;
11711         if(s==NULL) return 0;
11712         while(*s) i = i*259 + *s++;
11713         return i;
11714 }
11715
11716 int
11717 GameCheckSum ()
11718 {
11719         int i, sum=0;
11720         for(i=backwardMostMove; i<forwardMostMove; i++) {
11721                 sum += pvInfoList[i].depth;
11722                 sum += StringCheckSum(parseList[i]);
11723                 sum += StringCheckSum(commentList[i]);
11724                 sum *= 261;
11725         }
11726         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11727         return sum + StringCheckSum(commentList[i]);
11728 } // end of save patch
11729
11730 void
11731 GameEnds (ChessMove result, char *resultDetails, int whosays)
11732 {
11733     GameMode nextGameMode;
11734     int isIcsGame;
11735     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11736
11737     if(endingGame) return; /* [HGM] crash: forbid recursion */
11738     endingGame = 1;
11739     if(twoBoards) { // [HGM] dual: switch back to one board
11740         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11741         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11742     }
11743     if (appData.debugMode) {
11744       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11745               result, resultDetails ? resultDetails : "(null)", whosays);
11746     }
11747
11748     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11749
11750     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11751
11752     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11753         /* If we are playing on ICS, the server decides when the
11754            game is over, but the engine can offer to draw, claim
11755            a draw, or resign.
11756          */
11757 #if ZIPPY
11758         if (appData.zippyPlay && first.initDone) {
11759             if (result == GameIsDrawn) {
11760                 /* In case draw still needs to be claimed */
11761                 SendToICS(ics_prefix);
11762                 SendToICS("draw\n");
11763             } else if (StrCaseStr(resultDetails, "resign")) {
11764                 SendToICS(ics_prefix);
11765                 SendToICS("resign\n");
11766             }
11767         }
11768 #endif
11769         endingGame = 0; /* [HGM] crash */
11770         return;
11771     }
11772
11773     /* If we're loading the game from a file, stop */
11774     if (whosays == GE_FILE) {
11775       (void) StopLoadGameTimer();
11776       gameFileFP = NULL;
11777     }
11778
11779     /* Cancel draw offers */
11780     first.offeredDraw = second.offeredDraw = 0;
11781
11782     /* If this is an ICS game, only ICS can really say it's done;
11783        if not, anyone can. */
11784     isIcsGame = (gameMode == IcsPlayingWhite ||
11785                  gameMode == IcsPlayingBlack ||
11786                  gameMode == IcsObserving    ||
11787                  gameMode == IcsExamining);
11788
11789     if (!isIcsGame || whosays == GE_ICS) {
11790         /* OK -- not an ICS game, or ICS said it was done */
11791         StopClocks();
11792         if (!isIcsGame && !appData.noChessProgram)
11793           SetUserThinkingEnables();
11794
11795         /* [HGM] if a machine claims the game end we verify this claim */
11796         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11797             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11798                 char claimer;
11799                 ChessMove trueResult = (ChessMove) -1;
11800
11801                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11802                                             first.twoMachinesColor[0] :
11803                                             second.twoMachinesColor[0] ;
11804
11805                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11806                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11807                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11808                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11809                 } else
11810                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11811                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11812                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11813                 } else
11814                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11815                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11816                 }
11817
11818                 // now verify win claims, but not in drop games, as we don't understand those yet
11819                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11820                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11821                     (result == WhiteWins && claimer == 'w' ||
11822                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11823                       if (appData.debugMode) {
11824                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11825                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11826                       }
11827                       if(result != trueResult) {
11828                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11829                               result = claimer == 'w' ? BlackWins : WhiteWins;
11830                               resultDetails = buf;
11831                       }
11832                 } else
11833                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11834                     && (forwardMostMove <= backwardMostMove ||
11835                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11836                         (claimer=='b')==(forwardMostMove&1))
11837                                                                                   ) {
11838                       /* [HGM] verify: draws that were not flagged are false claims */
11839                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11840                       result = claimer == 'w' ? BlackWins : WhiteWins;
11841                       resultDetails = buf;
11842                 }
11843                 /* (Claiming a loss is accepted no questions asked!) */
11844             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11845                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11846                 result = GameUnfinished;
11847                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11848             }
11849             /* [HGM] bare: don't allow bare King to win */
11850             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11851                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11852                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11853                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11854                && result != GameIsDrawn)
11855             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11856                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11857                         int p = (int)boards[forwardMostMove][i][j] - color;
11858                         if(p >= 0 && p <= (int)WhiteKing) k++;
11859                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11860                 }
11861                 if (appData.debugMode) {
11862                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11863                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11864                 }
11865                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11866                         result = GameIsDrawn;
11867                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11868                         resultDetails = buf;
11869                 }
11870             }
11871         }
11872
11873
11874         if(serverMoves != NULL && !loadFlag) { char c = '=';
11875             if(result==WhiteWins) c = '+';
11876             if(result==BlackWins) c = '-';
11877             if(resultDetails != NULL)
11878                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11879         }
11880         if (resultDetails != NULL) {
11881             gameInfo.result = result;
11882             gameInfo.resultDetails = StrSave(resultDetails);
11883
11884             /* display last move only if game was not loaded from file */
11885             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11886                 DisplayMove(currentMove - 1);
11887
11888             if (forwardMostMove != 0) {
11889                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11890                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11891                                                                 ) {
11892                     if (*appData.saveGameFile != NULLCHAR) {
11893                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11894                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11895                         else
11896                         SaveGameToFile(appData.saveGameFile, TRUE);
11897                     } else if (appData.autoSaveGames) {
11898                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11899                     }
11900                     if (*appData.savePositionFile != NULLCHAR) {
11901                         SavePositionToFile(appData.savePositionFile);
11902                     }
11903                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11904                 }
11905             }
11906
11907             /* Tell program how game ended in case it is learning */
11908             /* [HGM] Moved this to after saving the PGN, just in case */
11909             /* engine died and we got here through time loss. In that */
11910             /* case we will get a fatal error writing the pipe, which */
11911             /* would otherwise lose us the PGN.                       */
11912             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11913             /* output during GameEnds should never be fatal anymore   */
11914             if (gameMode == MachinePlaysWhite ||
11915                 gameMode == MachinePlaysBlack ||
11916                 gameMode == TwoMachinesPlay ||
11917                 gameMode == IcsPlayingWhite ||
11918                 gameMode == IcsPlayingBlack ||
11919                 gameMode == BeginningOfGame) {
11920                 char buf[MSG_SIZ];
11921                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11922                         resultDetails);
11923                 if (first.pr != NoProc) {
11924                     SendToProgram(buf, &first);
11925                 }
11926                 if (second.pr != NoProc &&
11927                     gameMode == TwoMachinesPlay) {
11928                     SendToProgram(buf, &second);
11929                 }
11930             }
11931         }
11932
11933         if (appData.icsActive) {
11934             if (appData.quietPlay &&
11935                 (gameMode == IcsPlayingWhite ||
11936                  gameMode == IcsPlayingBlack)) {
11937                 SendToICS(ics_prefix);
11938                 SendToICS("set shout 1\n");
11939             }
11940             nextGameMode = IcsIdle;
11941             ics_user_moved = FALSE;
11942             /* clean up premove.  It's ugly when the game has ended and the
11943              * premove highlights are still on the board.
11944              */
11945             if (gotPremove) {
11946               gotPremove = FALSE;
11947               ClearPremoveHighlights();
11948               DrawPosition(FALSE, boards[currentMove]);
11949             }
11950             if (whosays == GE_ICS) {
11951                 switch (result) {
11952                 case WhiteWins:
11953                     if (gameMode == IcsPlayingWhite)
11954                         PlayIcsWinSound();
11955                     else if(gameMode == IcsPlayingBlack)
11956                         PlayIcsLossSound();
11957                     break;
11958                 case BlackWins:
11959                     if (gameMode == IcsPlayingBlack)
11960                         PlayIcsWinSound();
11961                     else if(gameMode == IcsPlayingWhite)
11962                         PlayIcsLossSound();
11963                     break;
11964                 case GameIsDrawn:
11965                     PlayIcsDrawSound();
11966                     break;
11967                 default:
11968                     PlayIcsUnfinishedSound();
11969                 }
11970             }
11971             if(appData.quitNext) { ExitEvent(0); return; }
11972         } else if (gameMode == EditGame ||
11973                    gameMode == PlayFromGameFile ||
11974                    gameMode == AnalyzeMode ||
11975                    gameMode == AnalyzeFile) {
11976             nextGameMode = gameMode;
11977         } else {
11978             nextGameMode = EndOfGame;
11979         }
11980         pausing = FALSE;
11981         ModeHighlight();
11982     } else {
11983         nextGameMode = gameMode;
11984     }
11985
11986     if (appData.noChessProgram) {
11987         gameMode = nextGameMode;
11988         ModeHighlight();
11989         endingGame = 0; /* [HGM] crash */
11990         return;
11991     }
11992
11993     if (first.reuse) {
11994         /* Put first chess program into idle state */
11995         if (first.pr != NoProc &&
11996             (gameMode == MachinePlaysWhite ||
11997              gameMode == MachinePlaysBlack ||
11998              gameMode == TwoMachinesPlay ||
11999              gameMode == IcsPlayingWhite ||
12000              gameMode == IcsPlayingBlack ||
12001              gameMode == BeginningOfGame)) {
12002             SendToProgram("force\n", &first);
12003             if (first.usePing) {
12004               char buf[MSG_SIZ];
12005               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
12006               SendToProgram(buf, &first);
12007             }
12008         }
12009     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
12010         /* Kill off first chess program */
12011         if (first.isr != NULL)
12012           RemoveInputSource(first.isr);
12013         first.isr = NULL;
12014
12015         if (first.pr != NoProc) {
12016             ExitAnalyzeMode();
12017             DoSleep( appData.delayBeforeQuit );
12018             SendToProgram("quit\n", &first);
12019             DestroyChildProcess(first.pr, 4 + first.useSigterm);
12020             first.reload = TRUE;
12021         }
12022         first.pr = NoProc;
12023     }
12024     if (second.reuse) {
12025         /* Put second chess program into idle state */
12026         if (second.pr != NoProc &&
12027             gameMode == TwoMachinesPlay) {
12028             SendToProgram("force\n", &second);
12029             if (second.usePing) {
12030               char buf[MSG_SIZ];
12031               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
12032               SendToProgram(buf, &second);
12033             }
12034         }
12035     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
12036         /* Kill off second chess program */
12037         if (second.isr != NULL)
12038           RemoveInputSource(second.isr);
12039         second.isr = NULL;
12040
12041         if (second.pr != NoProc) {
12042             DoSleep( appData.delayBeforeQuit );
12043             SendToProgram("quit\n", &second);
12044             DestroyChildProcess(second.pr, 4 + second.useSigterm);
12045             second.reload = TRUE;
12046         }
12047         second.pr = NoProc;
12048     }
12049
12050     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
12051         char resChar = '=';
12052         switch (result) {
12053         case WhiteWins:
12054           resChar = '+';
12055           if (first.twoMachinesColor[0] == 'w') {
12056             first.matchWins++;
12057           } else {
12058             second.matchWins++;
12059           }
12060           break;
12061         case BlackWins:
12062           resChar = '-';
12063           if (first.twoMachinesColor[0] == 'b') {
12064             first.matchWins++;
12065           } else {
12066             second.matchWins++;
12067           }
12068           break;
12069         case GameUnfinished:
12070           resChar = ' ';
12071         default:
12072           break;
12073         }
12074
12075         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
12076         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
12077             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
12078             ReserveGame(nextGame, resChar); // sets nextGame
12079             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
12080             else ranking = strdup("busy"); //suppress popup when aborted but not finished
12081         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
12082
12083         if (nextGame <= appData.matchGames && !abortMatch) {
12084             gameMode = nextGameMode;
12085             matchGame = nextGame; // this will be overruled in tourney mode!
12086             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
12087             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
12088             endingGame = 0; /* [HGM] crash */
12089             return;
12090         } else {
12091             gameMode = nextGameMode;
12092             if(appData.epd) {
12093                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
12094                 OutputKibitz(2, buf);
12095                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
12096                 OutputKibitz(2, buf);
12097                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
12098                 if(second.matchWins) OutputKibitz(2, buf);
12099                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
12100                 OutputKibitz(2, buf);
12101             }
12102             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
12103                      first.tidy, second.tidy,
12104                      first.matchWins, second.matchWins,
12105                      appData.matchGames - (first.matchWins + second.matchWins));
12106             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
12107             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
12108             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
12109             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
12110                 first.twoMachinesColor = "black\n";
12111                 second.twoMachinesColor = "white\n";
12112             } else {
12113                 first.twoMachinesColor = "white\n";
12114                 second.twoMachinesColor = "black\n";
12115             }
12116         }
12117     }
12118     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
12119         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
12120       ExitAnalyzeMode();
12121     gameMode = nextGameMode;
12122     ModeHighlight();
12123     endingGame = 0;  /* [HGM] crash */
12124     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12125         if(matchMode == TRUE) { // match through command line: exit with or without popup
12126             if(ranking) {
12127                 ToNrEvent(forwardMostMove);
12128                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12129                 else ExitEvent(0);
12130             } else DisplayFatalError(buf, 0, 0);
12131         } else { // match through menu; just stop, with or without popup
12132             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12133             ModeHighlight();
12134             if(ranking){
12135                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12136             } else DisplayNote(buf);
12137       }
12138       if(ranking) free(ranking);
12139     }
12140 }
12141
12142 /* Assumes program was just initialized (initString sent).
12143    Leaves program in force mode. */
12144 void
12145 FeedMovesToProgram (ChessProgramState *cps, int upto)
12146 {
12147     int i;
12148
12149     if (appData.debugMode)
12150       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12151               startedFromSetupPosition ? "position and " : "",
12152               backwardMostMove, upto, cps->which);
12153     if(currentlyInitializedVariant != gameInfo.variant) {
12154       char buf[MSG_SIZ];
12155         // [HGM] variantswitch: make engine aware of new variant
12156         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12157                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12158                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12159         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12160         SendToProgram(buf, cps);
12161         currentlyInitializedVariant = gameInfo.variant;
12162     }
12163     SendToProgram("force\n", cps);
12164     if (startedFromSetupPosition) {
12165         SendBoard(cps, backwardMostMove);
12166     if (appData.debugMode) {
12167         fprintf(debugFP, "feedMoves\n");
12168     }
12169     }
12170     for (i = backwardMostMove; i < upto; i++) {
12171         SendMoveToProgram(i, cps);
12172     }
12173 }
12174
12175
12176 int
12177 ResurrectChessProgram ()
12178 {
12179      /* The chess program may have exited.
12180         If so, restart it and feed it all the moves made so far. */
12181     static int doInit = 0;
12182
12183     if (appData.noChessProgram) return 1;
12184
12185     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12186         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12187         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12188         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12189     } else {
12190         if (first.pr != NoProc) return 1;
12191         StartChessProgram(&first);
12192     }
12193     InitChessProgram(&first, FALSE);
12194     FeedMovesToProgram(&first, currentMove);
12195
12196     if (!first.sendTime) {
12197         /* can't tell gnuchess what its clock should read,
12198            so we bow to its notion. */
12199         ResetClocks();
12200         timeRemaining[0][currentMove] = whiteTimeRemaining;
12201         timeRemaining[1][currentMove] = blackTimeRemaining;
12202     }
12203
12204     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12205                 appData.icsEngineAnalyze) && first.analysisSupport) {
12206       SendToProgram("analyze\n", &first);
12207       first.analyzing = TRUE;
12208     }
12209     return 1;
12210 }
12211
12212 /*
12213  * Button procedures
12214  */
12215 void
12216 Reset (int redraw, int init)
12217 {
12218     int i;
12219
12220     if (appData.debugMode) {
12221         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12222                 redraw, init, gameMode);
12223     }
12224     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12225     deadRanks = 0; // assume entire board is used
12226     handSize = 0;
12227     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12228     CleanupTail(); // [HGM] vari: delete any stored variations
12229     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12230     pausing = pauseExamInvalid = FALSE;
12231     startedFromSetupPosition = blackPlaysFirst = FALSE;
12232     firstMove = TRUE;
12233     whiteFlag = blackFlag = FALSE;
12234     userOfferedDraw = FALSE;
12235     hintRequested = bookRequested = FALSE;
12236     first.maybeThinking = FALSE;
12237     second.maybeThinking = FALSE;
12238     first.bookSuspend = FALSE; // [HGM] book
12239     second.bookSuspend = FALSE;
12240     thinkOutput[0] = NULLCHAR;
12241     lastHint[0] = NULLCHAR;
12242     ClearGameInfo(&gameInfo);
12243     gameInfo.variant = StringToVariant(appData.variant);
12244     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12245         gameInfo.variant = VariantUnknown;
12246         strncpy(engineVariant, appData.variant, MSG_SIZ);
12247     }
12248     ics_user_moved = ics_clock_paused = FALSE;
12249     ics_getting_history = H_FALSE;
12250     ics_gamenum = -1;
12251     white_holding[0] = black_holding[0] = NULLCHAR;
12252     ClearProgramStats();
12253     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12254
12255     ResetFrontEnd();
12256     ClearHighlights();
12257     flipView = appData.flipView;
12258     ClearPremoveHighlights();
12259     gotPremove = FALSE;
12260     alarmSounded = FALSE;
12261     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12262
12263     GameEnds(EndOfFile, NULL, GE_PLAYER);
12264     if(appData.serverMovesName != NULL) {
12265         /* [HGM] prepare to make moves file for broadcasting */
12266         clock_t t = clock();
12267         if(serverMoves != NULL) fclose(serverMoves);
12268         serverMoves = fopen(appData.serverMovesName, "r");
12269         if(serverMoves != NULL) {
12270             fclose(serverMoves);
12271             /* delay 15 sec before overwriting, so all clients can see end */
12272             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12273         }
12274         serverMoves = fopen(appData.serverMovesName, "w");
12275     }
12276
12277     ExitAnalyzeMode();
12278     gameMode = BeginningOfGame;
12279     ModeHighlight();
12280     if(appData.icsActive) gameInfo.variant = VariantNormal;
12281     currentMove = forwardMostMove = backwardMostMove = 0;
12282     MarkTargetSquares(1);
12283     InitPosition(redraw);
12284     for (i = 0; i < MAX_MOVES; i++) {
12285         if (commentList[i] != NULL) {
12286             free(commentList[i]);
12287             commentList[i] = NULL;
12288         }
12289     }
12290     ResetClocks();
12291     timeRemaining[0][0] = whiteTimeRemaining;
12292     timeRemaining[1][0] = blackTimeRemaining;
12293
12294     if (first.pr == NoProc) {
12295         StartChessProgram(&first);
12296     }
12297     if (init) {
12298             InitChessProgram(&first, startedFromSetupPosition);
12299     }
12300     DisplayTitle("");
12301     DisplayMessage("", "");
12302     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12303     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12304     ClearMap();        // [HGM] exclude: invalidate map
12305 }
12306
12307 void
12308 AutoPlayGameLoop ()
12309 {
12310     for (;;) {
12311         if (!AutoPlayOneMove())
12312           return;
12313         if (matchMode || appData.timeDelay == 0)
12314           continue;
12315         if (appData.timeDelay < 0)
12316           return;
12317         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12318         break;
12319     }
12320 }
12321
12322 void
12323 AnalyzeNextGame()
12324 {
12325     ReloadGame(1); // next game
12326 }
12327
12328 int
12329 AutoPlayOneMove ()
12330 {
12331     int fromX, fromY, toX, toY;
12332
12333     if (appData.debugMode) {
12334       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12335     }
12336
12337     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12338       return FALSE;
12339
12340     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12341       pvInfoList[currentMove].depth = programStats.depth;
12342       pvInfoList[currentMove].score = programStats.score;
12343       pvInfoList[currentMove].time  = 0;
12344       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12345       else { // append analysis of final position as comment
12346         char buf[MSG_SIZ];
12347         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12348         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12349       }
12350       programStats.depth = 0;
12351     }
12352
12353     if (currentMove >= forwardMostMove) {
12354       if(gameMode == AnalyzeFile) {
12355           if(appData.loadGameIndex == -1) {
12356             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12357           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12358           } else {
12359           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12360         }
12361       }
12362 //      gameMode = EndOfGame;
12363 //      ModeHighlight();
12364
12365       /* [AS] Clear current move marker at the end of a game */
12366       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12367
12368       return FALSE;
12369     }
12370
12371     toX = moveList[currentMove][2] - AAA;
12372     toY = moveList[currentMove][3] - ONE;
12373
12374     if (moveList[currentMove][1] == '@') {
12375         if (appData.highlightLastMove) {
12376             SetHighlights(-1, -1, toX, toY);
12377         }
12378     } else {
12379         fromX = moveList[currentMove][0] - AAA;
12380         fromY = moveList[currentMove][1] - ONE;
12381
12382         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12383
12384         if(moveList[currentMove][4] == ';') { // multi-leg
12385             killX = moveList[currentMove][5] - AAA;
12386             killY = moveList[currentMove][6] - ONE;
12387         }
12388         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12389         killX = killY = -1;
12390
12391         if (appData.highlightLastMove) {
12392             SetHighlights(fromX, fromY, toX, toY);
12393         }
12394     }
12395     DisplayMove(currentMove);
12396     SendMoveToProgram(currentMove++, &first);
12397     DisplayBothClocks();
12398     DrawPosition(FALSE, boards[currentMove]);
12399     // [HGM] PV info: always display, routine tests if empty
12400     DisplayComment(currentMove - 1, commentList[currentMove]);
12401     return TRUE;
12402 }
12403
12404
12405 int
12406 LoadGameOneMove (ChessMove readAhead)
12407 {
12408     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12409     char promoChar = NULLCHAR;
12410     ChessMove moveType;
12411     char move[MSG_SIZ];
12412     char *p, *q;
12413
12414     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12415         gameMode != AnalyzeMode && gameMode != Training) {
12416         gameFileFP = NULL;
12417         return FALSE;
12418     }
12419
12420     yyboardindex = forwardMostMove;
12421     if (readAhead != EndOfFile) {
12422       moveType = readAhead;
12423     } else {
12424       if (gameFileFP == NULL)
12425           return FALSE;
12426       moveType = (ChessMove) Myylex();
12427     }
12428
12429     done = FALSE;
12430     switch (moveType) {
12431       case Comment:
12432         if (appData.debugMode)
12433           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12434         p = yy_text;
12435
12436         /* append the comment but don't display it */
12437         AppendComment(currentMove, p, FALSE);
12438         return TRUE;
12439
12440       case WhiteCapturesEnPassant:
12441       case BlackCapturesEnPassant:
12442       case WhitePromotion:
12443       case BlackPromotion:
12444       case WhiteNonPromotion:
12445       case BlackNonPromotion:
12446       case NormalMove:
12447       case FirstLeg:
12448       case WhiteKingSideCastle:
12449       case WhiteQueenSideCastle:
12450       case BlackKingSideCastle:
12451       case BlackQueenSideCastle:
12452       case WhiteKingSideCastleWild:
12453       case WhiteQueenSideCastleWild:
12454       case BlackKingSideCastleWild:
12455       case BlackQueenSideCastleWild:
12456       /* PUSH Fabien */
12457       case WhiteHSideCastleFR:
12458       case WhiteASideCastleFR:
12459       case BlackHSideCastleFR:
12460       case BlackASideCastleFR:
12461       /* POP Fabien */
12462         if (appData.debugMode)
12463           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12464         fromX = currentMoveString[0] - AAA;
12465         fromY = currentMoveString[1] - ONE;
12466         toX = currentMoveString[2] - AAA;
12467         toY = currentMoveString[3] - ONE;
12468         promoChar = currentMoveString[4];
12469         if(promoChar == ';') promoChar = currentMoveString[7];
12470         break;
12471
12472       case WhiteDrop:
12473       case BlackDrop:
12474         if (appData.debugMode)
12475           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12476         fromX = moveType == WhiteDrop ?
12477           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12478         (int) CharToPiece(ToLower(currentMoveString[0]));
12479         fromY = DROP_RANK;
12480         toX = currentMoveString[2] - AAA;
12481         toY = currentMoveString[3] - ONE;
12482         break;
12483
12484       case WhiteWins:
12485       case BlackWins:
12486       case GameIsDrawn:
12487       case GameUnfinished:
12488         if (appData.debugMode)
12489           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12490         p = strchr(yy_text, '{');
12491         if (p == NULL) p = strchr(yy_text, '(');
12492         if (p == NULL) {
12493             p = yy_text;
12494             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12495         } else {
12496             q = strchr(p, *p == '{' ? '}' : ')');
12497             if (q != NULL) *q = NULLCHAR;
12498             p++;
12499         }
12500         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12501         GameEnds(moveType, p, GE_FILE);
12502         done = TRUE;
12503         if (cmailMsgLoaded) {
12504             ClearHighlights();
12505             flipView = WhiteOnMove(currentMove);
12506             if (moveType == GameUnfinished) flipView = !flipView;
12507             if (appData.debugMode)
12508               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12509         }
12510         break;
12511
12512       case EndOfFile:
12513         if (appData.debugMode)
12514           fprintf(debugFP, "Parser hit end of file\n");
12515         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12516           case MT_NONE:
12517           case MT_CHECK:
12518             break;
12519           case MT_CHECKMATE:
12520           case MT_STAINMATE:
12521             if (WhiteOnMove(currentMove)) {
12522                 GameEnds(BlackWins, "Black mates", GE_FILE);
12523             } else {
12524                 GameEnds(WhiteWins, "White mates", GE_FILE);
12525             }
12526             break;
12527           case MT_STALEMATE:
12528             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12529             break;
12530         }
12531         done = TRUE;
12532         break;
12533
12534       case MoveNumberOne:
12535         if (lastLoadGameStart == GNUChessGame) {
12536             /* GNUChessGames have numbers, but they aren't move numbers */
12537             if (appData.debugMode)
12538               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12539                       yy_text, (int) moveType);
12540             return LoadGameOneMove(EndOfFile); /* tail recursion */
12541         }
12542         /* else fall thru */
12543
12544       case XBoardGame:
12545       case GNUChessGame:
12546       case PGNTag:
12547         /* Reached start of next game in file */
12548         if (appData.debugMode)
12549           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12550         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12551           case MT_NONE:
12552           case MT_CHECK:
12553             break;
12554           case MT_CHECKMATE:
12555           case MT_STAINMATE:
12556             if (WhiteOnMove(currentMove)) {
12557                 GameEnds(BlackWins, "Black mates", GE_FILE);
12558             } else {
12559                 GameEnds(WhiteWins, "White mates", GE_FILE);
12560             }
12561             break;
12562           case MT_STALEMATE:
12563             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12564             break;
12565         }
12566         done = TRUE;
12567         break;
12568
12569       case PositionDiagram:     /* should not happen; ignore */
12570       case ElapsedTime:         /* ignore */
12571       case NAG:                 /* ignore */
12572         if (appData.debugMode)
12573           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12574                   yy_text, (int) moveType);
12575         return LoadGameOneMove(EndOfFile); /* tail recursion */
12576
12577       case IllegalMove:
12578         if (appData.testLegality) {
12579             if (appData.debugMode)
12580               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12581             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12582                     (forwardMostMove / 2) + 1,
12583                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12584             DisplayError(move, 0);
12585             done = TRUE;
12586         } else {
12587             if (appData.debugMode)
12588               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12589                       yy_text, currentMoveString);
12590             if(currentMoveString[1] == '@') {
12591                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12592                 fromY = DROP_RANK;
12593             } else {
12594                 fromX = currentMoveString[0] - AAA;
12595                 fromY = currentMoveString[1] - ONE;
12596             }
12597             toX = currentMoveString[2] - AAA;
12598             toY = currentMoveString[3] - ONE;
12599             promoChar = currentMoveString[4];
12600         }
12601         break;
12602
12603       case AmbiguousMove:
12604         if (appData.debugMode)
12605           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12606         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12607                 (forwardMostMove / 2) + 1,
12608                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12609         DisplayError(move, 0);
12610         done = TRUE;
12611         break;
12612
12613       default:
12614       case ImpossibleMove:
12615         if (appData.debugMode)
12616           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12617         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12618                 (forwardMostMove / 2) + 1,
12619                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12620         DisplayError(move, 0);
12621         done = TRUE;
12622         break;
12623     }
12624
12625     if (done) {
12626         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12627             DrawPosition(FALSE, boards[currentMove]);
12628             DisplayBothClocks();
12629             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12630               DisplayComment(currentMove - 1, commentList[currentMove]);
12631         }
12632         (void) StopLoadGameTimer();
12633         gameFileFP = NULL;
12634         cmailOldMove = forwardMostMove;
12635         return FALSE;
12636     } else {
12637         /* currentMoveString is set as a side-effect of yylex */
12638
12639         thinkOutput[0] = NULLCHAR;
12640         MakeMove(fromX, fromY, toX, toY, promoChar);
12641         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12642         currentMove = forwardMostMove;
12643         return TRUE;
12644     }
12645 }
12646
12647 /* Load the nth game from the given file */
12648 int
12649 LoadGameFromFile (char *filename, int n, char *title, int useList)
12650 {
12651     FILE *f;
12652     char buf[MSG_SIZ];
12653
12654     if (strcmp(filename, "-") == 0) {
12655         f = stdin;
12656         title = "stdin";
12657     } else {
12658         f = fopen(filename, "rb");
12659         if (f == NULL) {
12660           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12661             DisplayError(buf, errno);
12662             return FALSE;
12663         }
12664     }
12665     if (fseek(f, 0, 0) == -1) {
12666         /* f is not seekable; probably a pipe */
12667         useList = FALSE;
12668     }
12669     if (useList && n == 0) {
12670         int error = GameListBuild(f);
12671         if (error) {
12672             DisplayError(_("Cannot build game list"), error);
12673         } else if (!ListEmpty(&gameList) &&
12674                    ((ListGame *) gameList.tailPred)->number > 1) {
12675             GameListPopUp(f, title);
12676             return TRUE;
12677         }
12678         GameListDestroy();
12679         n = 1;
12680     }
12681     if (n == 0) n = 1;
12682     return LoadGame(f, n, title, FALSE);
12683 }
12684
12685
12686 void
12687 MakeRegisteredMove ()
12688 {
12689     int fromX, fromY, toX, toY;
12690     char promoChar;
12691     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12692         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12693           case CMAIL_MOVE:
12694           case CMAIL_DRAW:
12695             if (appData.debugMode)
12696               fprintf(debugFP, "Restoring %s for game %d\n",
12697                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12698
12699             thinkOutput[0] = NULLCHAR;
12700             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12701             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12702             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12703             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12704             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12705             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12706             MakeMove(fromX, fromY, toX, toY, promoChar);
12707             ShowMove(fromX, fromY, toX, toY);
12708
12709             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12710               case MT_NONE:
12711               case MT_CHECK:
12712                 break;
12713
12714               case MT_CHECKMATE:
12715               case MT_STAINMATE:
12716                 if (WhiteOnMove(currentMove)) {
12717                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12718                 } else {
12719                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12720                 }
12721                 break;
12722
12723               case MT_STALEMATE:
12724                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12725                 break;
12726             }
12727
12728             break;
12729
12730           case CMAIL_RESIGN:
12731             if (WhiteOnMove(currentMove)) {
12732                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12733             } else {
12734                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12735             }
12736             break;
12737
12738           case CMAIL_ACCEPT:
12739             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12740             break;
12741
12742           default:
12743             break;
12744         }
12745     }
12746
12747     return;
12748 }
12749
12750 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12751 int
12752 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12753 {
12754     int retVal;
12755
12756     if (gameNumber > nCmailGames) {
12757         DisplayError(_("No more games in this message"), 0);
12758         return FALSE;
12759     }
12760     if (f == lastLoadGameFP) {
12761         int offset = gameNumber - lastLoadGameNumber;
12762         if (offset == 0) {
12763             cmailMsg[0] = NULLCHAR;
12764             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12765                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12766                 nCmailMovesRegistered--;
12767             }
12768             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12769             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12770                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12771             }
12772         } else {
12773             if (! RegisterMove()) return FALSE;
12774         }
12775     }
12776
12777     retVal = LoadGame(f, gameNumber, title, useList);
12778
12779     /* Make move registered during previous look at this game, if any */
12780     MakeRegisteredMove();
12781
12782     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12783         commentList[currentMove]
12784           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12785         DisplayComment(currentMove - 1, commentList[currentMove]);
12786     }
12787
12788     return retVal;
12789 }
12790
12791 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12792 int
12793 ReloadGame (int offset)
12794 {
12795     int gameNumber = lastLoadGameNumber + offset;
12796     if (lastLoadGameFP == NULL) {
12797         DisplayError(_("No game has been loaded yet"), 0);
12798         return FALSE;
12799     }
12800     if (gameNumber <= 0) {
12801         DisplayError(_("Can't back up any further"), 0);
12802         return FALSE;
12803     }
12804     if (cmailMsgLoaded) {
12805         return CmailLoadGame(lastLoadGameFP, gameNumber,
12806                              lastLoadGameTitle, lastLoadGameUseList);
12807     } else {
12808         return LoadGame(lastLoadGameFP, gameNumber,
12809                         lastLoadGameTitle, lastLoadGameUseList);
12810     }
12811 }
12812
12813 int keys[EmptySquare+1];
12814
12815 int
12816 PositionMatches (Board b1, Board b2)
12817 {
12818     int r, f, sum=0;
12819     switch(appData.searchMode) {
12820         case 1: return CompareWithRights(b1, b2);
12821         case 2:
12822             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12823                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12824             }
12825             return TRUE;
12826         case 3:
12827             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12828               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12829                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12830             }
12831             return sum==0;
12832         case 4:
12833             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12834                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12835             }
12836             return sum==0;
12837     }
12838     return TRUE;
12839 }
12840
12841 #define Q_PROMO  4
12842 #define Q_EP     3
12843 #define Q_BCASTL 2
12844 #define Q_WCASTL 1
12845
12846 int pieceList[256], quickBoard[256];
12847 ChessSquare pieceType[256] = { EmptySquare };
12848 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12849 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12850 int soughtTotal, turn;
12851 Boolean epOK, flipSearch;
12852
12853 typedef struct {
12854     unsigned char piece, to;
12855 } Move;
12856
12857 #define DSIZE (250000)
12858
12859 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12860 Move *moveDatabase = initialSpace;
12861 unsigned int movePtr, dataSize = DSIZE;
12862
12863 int
12864 MakePieceList (Board board, int *counts)
12865 {
12866     int r, f, n=Q_PROMO, total=0;
12867     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12868     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12869         int sq = f + (r<<4);
12870         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12871             quickBoard[sq] = ++n;
12872             pieceList[n] = sq;
12873             pieceType[n] = board[r][f];
12874             counts[board[r][f]]++;
12875             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12876             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12877             total++;
12878         }
12879     }
12880     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12881     return total;
12882 }
12883
12884 void
12885 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12886 {
12887     int sq = fromX + (fromY<<4);
12888     int piece = quickBoard[sq], rook;
12889     quickBoard[sq] = 0;
12890     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12891     if(piece == pieceList[1] && fromY == toY) {
12892       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12893         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12894         moveDatabase[movePtr++].piece = Q_WCASTL;
12895         quickBoard[sq] = piece;
12896         piece = quickBoard[from]; quickBoard[from] = 0;
12897         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12898       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12899         quickBoard[sq] = 0; // remove Rook
12900         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12901         moveDatabase[movePtr++].piece = Q_WCASTL;
12902         quickBoard[sq] = pieceList[1]; // put King
12903         piece = rook;
12904         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12905       }
12906     } else
12907     if(piece == pieceList[2] && fromY == toY) {
12908       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12909         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12910         moveDatabase[movePtr++].piece = Q_BCASTL;
12911         quickBoard[sq] = piece;
12912         piece = quickBoard[from]; quickBoard[from] = 0;
12913         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12914       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12915         quickBoard[sq] = 0; // remove Rook
12916         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12917         moveDatabase[movePtr++].piece = Q_BCASTL;
12918         quickBoard[sq] = pieceList[2]; // put King
12919         piece = rook;
12920         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12921       }
12922     } else
12923     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12924         quickBoard[(fromY<<4)+toX] = 0;
12925         moveDatabase[movePtr].piece = Q_EP;
12926         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12927         moveDatabase[movePtr].to = sq;
12928     } else
12929     if(promoPiece != pieceType[piece]) {
12930         moveDatabase[movePtr++].piece = Q_PROMO;
12931         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12932     }
12933     moveDatabase[movePtr].piece = piece;
12934     quickBoard[sq] = piece;
12935     movePtr++;
12936 }
12937
12938 int
12939 PackGame (Board board)
12940 {
12941     Move *newSpace = NULL;
12942     moveDatabase[movePtr].piece = 0; // terminate previous game
12943     if(movePtr > dataSize) {
12944         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12945         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12946         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12947         if(newSpace) {
12948             int i;
12949             Move *p = moveDatabase, *q = newSpace;
12950             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12951             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12952             moveDatabase = newSpace;
12953         } else { // calloc failed, we must be out of memory. Too bad...
12954             dataSize = 0; // prevent calloc events for all subsequent games
12955             return 0;     // and signal this one isn't cached
12956         }
12957     }
12958     movePtr++;
12959     MakePieceList(board, counts);
12960     return movePtr;
12961 }
12962
12963 int
12964 QuickCompare (Board board, int *minCounts, int *maxCounts)
12965 {   // compare according to search mode
12966     int r, f;
12967     switch(appData.searchMode)
12968     {
12969       case 1: // exact position match
12970         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12971         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12972             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12973         }
12974         break;
12975       case 2: // can have extra material on empty squares
12976         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12977             if(board[r][f] == EmptySquare) continue;
12978             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12979         }
12980         break;
12981       case 3: // material with exact Pawn structure
12982         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12983             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12984             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12985         } // fall through to material comparison
12986       case 4: // exact material
12987         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12988         break;
12989       case 6: // material range with given imbalance
12990         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12991         // fall through to range comparison
12992       case 5: // material range
12993         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12994     }
12995     return TRUE;
12996 }
12997
12998 int
12999 QuickScan (Board board, Move *move)
13000 {   // reconstruct game,and compare all positions in it
13001     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
13002     do {
13003         int piece = move->piece;
13004         int to = move->to, from = pieceList[piece];
13005         if(found < 0) { // if already found just scan to game end for final piece count
13006           if(QuickCompare(soughtBoard, minSought, maxSought) ||
13007            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
13008            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
13009                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
13010             ) {
13011             static int lastCounts[EmptySquare+1];
13012             int i;
13013             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
13014             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
13015           } else stretch = 0;
13016           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
13017           if(found >= 0 && !appData.minPieces) return found;
13018         }
13019         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
13020           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
13021           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
13022             piece = (++move)->piece;
13023             from = pieceList[piece];
13024             counts[pieceType[piece]]--;
13025             pieceType[piece] = (ChessSquare) move->to;
13026             counts[move->to]++;
13027           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
13028             counts[pieceType[quickBoard[to]]]--;
13029             quickBoard[to] = 0; total--;
13030             move++;
13031             continue;
13032           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
13033             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
13034             from  = pieceList[piece]; // so this must be King
13035             quickBoard[from] = 0;
13036             pieceList[piece] = to;
13037             from = pieceList[(++move)->piece]; // for FRC this has to be done here
13038             quickBoard[from] = 0; // rook
13039             quickBoard[to] = piece;
13040             to = move->to; piece = move->piece;
13041             goto aftercastle;
13042           }
13043         }
13044         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
13045         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
13046         quickBoard[from] = 0;
13047       aftercastle:
13048         quickBoard[to] = piece;
13049         pieceList[piece] = to;
13050         cnt++; turn ^= 3;
13051         move++;
13052     } while(1);
13053 }
13054
13055 void
13056 InitSearch ()
13057 {
13058     int r, f;
13059     flipSearch = FALSE;
13060     CopyBoard(soughtBoard, boards[currentMove]);
13061     soughtTotal = MakePieceList(soughtBoard, maxSought);
13062     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
13063     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
13064     CopyBoard(reverseBoard, boards[currentMove]);
13065     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13066         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
13067         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
13068         reverseBoard[r][f] = piece;
13069     }
13070     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
13071     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
13072     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
13073                  || (boards[currentMove][CASTLING][2] == NoRights ||
13074                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
13075                  && (boards[currentMove][CASTLING][5] == NoRights ||
13076                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
13077       ) {
13078         flipSearch = TRUE;
13079         CopyBoard(flipBoard, soughtBoard);
13080         CopyBoard(rotateBoard, reverseBoard);
13081         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
13082             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
13083             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
13084         }
13085     }
13086     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
13087     if(appData.searchMode >= 5) {
13088         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
13089         MakePieceList(soughtBoard, minSought);
13090         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
13091     }
13092     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
13093         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
13094 }
13095
13096 GameInfo dummyInfo;
13097 static int creatingBook;
13098
13099 int
13100 GameContainsPosition (FILE *f, ListGame *lg)
13101 {
13102     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
13103     int fromX, fromY, toX, toY;
13104     char promoChar;
13105     static int initDone=FALSE;
13106
13107     // weed out games based on numerical tag comparison
13108     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
13109     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
13110     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
13111     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
13112     if(!initDone) {
13113         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
13114         initDone = TRUE;
13115     }
13116     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
13117     else CopyBoard(boards[scratch], initialPosition); // default start position
13118     if(lg->moves) {
13119         turn = btm + 1;
13120         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
13121         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
13122     }
13123     if(btm) plyNr++;
13124     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13125     fseek(f, lg->offset, 0);
13126     yynewfile(f);
13127     while(1) {
13128         yyboardindex = scratch;
13129         quickFlag = plyNr+1;
13130         next = Myylex();
13131         quickFlag = 0;
13132         switch(next) {
13133             case PGNTag:
13134                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13135             default:
13136                 continue;
13137
13138             case XBoardGame:
13139             case GNUChessGame:
13140                 if(plyNr) return -1; // after we have seen moves, this is for new game
13141               continue;
13142
13143             case AmbiguousMove: // we cannot reconstruct the game beyond these two
13144             case ImpossibleMove:
13145             case WhiteWins: // game ends here with these four
13146             case BlackWins:
13147             case GameIsDrawn:
13148             case GameUnfinished:
13149                 return -1;
13150
13151             case IllegalMove:
13152                 if(appData.testLegality) return -1;
13153             case WhiteCapturesEnPassant:
13154             case BlackCapturesEnPassant:
13155             case WhitePromotion:
13156             case BlackPromotion:
13157             case WhiteNonPromotion:
13158             case BlackNonPromotion:
13159             case NormalMove:
13160             case FirstLeg:
13161             case WhiteKingSideCastle:
13162             case WhiteQueenSideCastle:
13163             case BlackKingSideCastle:
13164             case BlackQueenSideCastle:
13165             case WhiteKingSideCastleWild:
13166             case WhiteQueenSideCastleWild:
13167             case BlackKingSideCastleWild:
13168             case BlackQueenSideCastleWild:
13169             case WhiteHSideCastleFR:
13170             case WhiteASideCastleFR:
13171             case BlackHSideCastleFR:
13172             case BlackASideCastleFR:
13173                 fromX = currentMoveString[0] - AAA;
13174                 fromY = currentMoveString[1] - ONE;
13175                 toX = currentMoveString[2] - AAA;
13176                 toY = currentMoveString[3] - ONE;
13177                 promoChar = currentMoveString[4];
13178                 break;
13179             case WhiteDrop:
13180             case BlackDrop:
13181                 fromX = next == WhiteDrop ?
13182                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
13183                   (int) CharToPiece(ToLower(currentMoveString[0]));
13184                 fromY = DROP_RANK;
13185                 toX = currentMoveString[2] - AAA;
13186                 toY = currentMoveString[3] - ONE;
13187                 promoChar = 0;
13188                 break;
13189         }
13190         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13191         plyNr++;
13192         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13193         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13194         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13195         if(appData.findMirror) {
13196             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13197             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13198         }
13199     }
13200 }
13201
13202 /* Load the nth game from open file f */
13203 int
13204 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13205 {
13206     ChessMove cm;
13207     char buf[MSG_SIZ];
13208     int gn = gameNumber;
13209     ListGame *lg = NULL;
13210     int numPGNTags = 0, i;
13211     int err, pos = -1;
13212     GameMode oldGameMode;
13213     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13214     char oldName[MSG_SIZ];
13215
13216     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13217
13218     if (appData.debugMode)
13219         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13220
13221     if (gameMode == Training )
13222         SetTrainingModeOff();
13223
13224     oldGameMode = gameMode;
13225     if (gameMode != BeginningOfGame) {
13226       Reset(FALSE, TRUE);
13227     }
13228     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13229
13230     gameFileFP = f;
13231     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13232         fclose(lastLoadGameFP);
13233     }
13234
13235     if (useList) {
13236         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13237
13238         if (lg) {
13239             fseek(f, lg->offset, 0);
13240             GameListHighlight(gameNumber);
13241             pos = lg->position;
13242             gn = 1;
13243         }
13244         else {
13245             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13246               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13247             else
13248             DisplayError(_("Game number out of range"), 0);
13249             return FALSE;
13250         }
13251     } else {
13252         GameListDestroy();
13253         if (fseek(f, 0, 0) == -1) {
13254             if (f == lastLoadGameFP ?
13255                 gameNumber == lastLoadGameNumber + 1 :
13256                 gameNumber == 1) {
13257                 gn = 1;
13258             } else {
13259                 DisplayError(_("Can't seek on game file"), 0);
13260                 return FALSE;
13261             }
13262         }
13263     }
13264     lastLoadGameFP = f;
13265     lastLoadGameNumber = gameNumber;
13266     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13267     lastLoadGameUseList = useList;
13268
13269     yynewfile(f);
13270
13271     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13272       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13273                 lg->gameInfo.black);
13274             DisplayTitle(buf);
13275     } else if (*title != NULLCHAR) {
13276         if (gameNumber > 1) {
13277           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13278             DisplayTitle(buf);
13279         } else {
13280             DisplayTitle(title);
13281         }
13282     }
13283
13284     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13285         gameMode = PlayFromGameFile;
13286         ModeHighlight();
13287     }
13288
13289     currentMove = forwardMostMove = backwardMostMove = 0;
13290     CopyBoard(boards[0], initialPosition);
13291     StopClocks();
13292
13293     /*
13294      * Skip the first gn-1 games in the file.
13295      * Also skip over anything that precedes an identifiable
13296      * start of game marker, to avoid being confused by
13297      * garbage at the start of the file.  Currently
13298      * recognized start of game markers are the move number "1",
13299      * the pattern "gnuchess .* game", the pattern
13300      * "^[#;%] [^ ]* game file", and a PGN tag block.
13301      * A game that starts with one of the latter two patterns
13302      * will also have a move number 1, possibly
13303      * following a position diagram.
13304      * 5-4-02: Let's try being more lenient and allowing a game to
13305      * start with an unnumbered move.  Does that break anything?
13306      */
13307     cm = lastLoadGameStart = EndOfFile;
13308     while (gn > 0) {
13309         yyboardindex = forwardMostMove;
13310         cm = (ChessMove) Myylex();
13311         switch (cm) {
13312           case EndOfFile:
13313             if (cmailMsgLoaded) {
13314                 nCmailGames = CMAIL_MAX_GAMES - gn;
13315             } else {
13316                 Reset(TRUE, TRUE);
13317                 DisplayError(_("Game not found in file"), 0);
13318             }
13319             return FALSE;
13320
13321           case GNUChessGame:
13322           case XBoardGame:
13323             gn--;
13324             lastLoadGameStart = cm;
13325             break;
13326
13327           case MoveNumberOne:
13328             switch (lastLoadGameStart) {
13329               case GNUChessGame:
13330               case XBoardGame:
13331               case PGNTag:
13332                 break;
13333               case MoveNumberOne:
13334               case EndOfFile:
13335                 gn--;           /* count this game */
13336                 lastLoadGameStart = cm;
13337                 break;
13338               default:
13339                 /* impossible */
13340                 break;
13341             }
13342             break;
13343
13344           case PGNTag:
13345             switch (lastLoadGameStart) {
13346               case GNUChessGame:
13347               case PGNTag:
13348               case MoveNumberOne:
13349               case EndOfFile:
13350                 gn--;           /* count this game */
13351                 lastLoadGameStart = cm;
13352                 break;
13353               case XBoardGame:
13354                 lastLoadGameStart = cm; /* game counted already */
13355                 break;
13356               default:
13357                 /* impossible */
13358                 break;
13359             }
13360             if (gn > 0) {
13361                 do {
13362                     yyboardindex = forwardMostMove;
13363                     cm = (ChessMove) Myylex();
13364                 } while (cm == PGNTag || cm == Comment);
13365             }
13366             break;
13367
13368           case WhiteWins:
13369           case BlackWins:
13370           case GameIsDrawn:
13371             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13372                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13373                     != CMAIL_OLD_RESULT) {
13374                     nCmailResults ++ ;
13375                     cmailResult[  CMAIL_MAX_GAMES
13376                                 - gn - 1] = CMAIL_OLD_RESULT;
13377                 }
13378             }
13379             break;
13380
13381           case NormalMove:
13382           case FirstLeg:
13383             /* Only a NormalMove can be at the start of a game
13384              * without a position diagram. */
13385             if (lastLoadGameStart == EndOfFile ) {
13386               gn--;
13387               lastLoadGameStart = MoveNumberOne;
13388             }
13389             break;
13390
13391           default:
13392             break;
13393         }
13394     }
13395
13396     if (appData.debugMode)
13397       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13398
13399     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13400
13401     if (cm == XBoardGame) {
13402         /* Skip any header junk before position diagram and/or move 1 */
13403         for (;;) {
13404             yyboardindex = forwardMostMove;
13405             cm = (ChessMove) Myylex();
13406
13407             if (cm == EndOfFile ||
13408                 cm == GNUChessGame || cm == XBoardGame) {
13409                 /* Empty game; pretend end-of-file and handle later */
13410                 cm = EndOfFile;
13411                 break;
13412             }
13413
13414             if (cm == MoveNumberOne || cm == PositionDiagram ||
13415                 cm == PGNTag || cm == Comment)
13416               break;
13417         }
13418     } else if (cm == GNUChessGame) {
13419         if (gameInfo.event != NULL) {
13420             free(gameInfo.event);
13421         }
13422         gameInfo.event = StrSave(yy_text);
13423     }
13424
13425     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13426     while (cm == PGNTag) {
13427         if (appData.debugMode)
13428           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13429         err = ParsePGNTag(yy_text, &gameInfo);
13430         if (!err) numPGNTags++;
13431
13432         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13433         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13434             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13435             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13436             InitPosition(TRUE);
13437             oldVariant = gameInfo.variant;
13438             if (appData.debugMode)
13439               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13440         }
13441
13442
13443         if (gameInfo.fen != NULL) {
13444           Board initial_position;
13445           startedFromSetupPosition = TRUE;
13446           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13447             Reset(TRUE, TRUE);
13448             DisplayError(_("Bad FEN position in file"), 0);
13449             return FALSE;
13450           }
13451           CopyBoard(boards[0], initial_position);
13452           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13453             CopyBoard(initialPosition, initial_position);
13454           if (blackPlaysFirst) {
13455             currentMove = forwardMostMove = backwardMostMove = 1;
13456             CopyBoard(boards[1], initial_position);
13457             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13458             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13459             timeRemaining[0][1] = whiteTimeRemaining;
13460             timeRemaining[1][1] = blackTimeRemaining;
13461             if (commentList[0] != NULL) {
13462               commentList[1] = commentList[0];
13463               commentList[0] = NULL;
13464             }
13465           } else {
13466             currentMove = forwardMostMove = backwardMostMove = 0;
13467           }
13468           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13469           {   int i;
13470               initialRulePlies = FENrulePlies;
13471               for( i=0; i< nrCastlingRights; i++ )
13472                   initialRights[i] = initial_position[CASTLING][i];
13473           }
13474           yyboardindex = forwardMostMove;
13475           free(gameInfo.fen);
13476           gameInfo.fen = NULL;
13477         }
13478
13479         yyboardindex = forwardMostMove;
13480         cm = (ChessMove) Myylex();
13481
13482         /* Handle comments interspersed among the tags */
13483         while (cm == Comment) {
13484             char *p;
13485             if (appData.debugMode)
13486               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13487             p = yy_text;
13488             AppendComment(currentMove, p, FALSE);
13489             yyboardindex = forwardMostMove;
13490             cm = (ChessMove) Myylex();
13491         }
13492     }
13493
13494     /* don't rely on existence of Event tag since if game was
13495      * pasted from clipboard the Event tag may not exist
13496      */
13497     if (numPGNTags > 0){
13498         char *tags;
13499         if (gameInfo.variant == VariantNormal) {
13500           VariantClass v = StringToVariant(gameInfo.event);
13501           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13502           if(v < VariantShogi) gameInfo.variant = v;
13503         }
13504         if (!matchMode) {
13505           if( appData.autoDisplayTags ) {
13506             tags = PGNTags(&gameInfo);
13507             TagsPopUp(tags, CmailMsg());
13508             free(tags);
13509           }
13510         }
13511     } else {
13512         /* Make something up, but don't display it now */
13513         SetGameInfo();
13514         TagsPopDown();
13515     }
13516
13517     if (cm == PositionDiagram) {
13518         int i, j;
13519         char *p;
13520         Board initial_position;
13521
13522         if (appData.debugMode)
13523           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13524
13525         if (!startedFromSetupPosition) {
13526             p = yy_text;
13527             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13528               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13529                 switch (*p) {
13530                   case '{':
13531                   case '[':
13532                   case '-':
13533                   case ' ':
13534                   case '\t':
13535                   case '\n':
13536                   case '\r':
13537                     break;
13538                   default:
13539                     initial_position[i][j++] = CharToPiece(*p);
13540                     break;
13541                 }
13542             while (*p == ' ' || *p == '\t' ||
13543                    *p == '\n' || *p == '\r') p++;
13544
13545             if (strncmp(p, "black", strlen("black"))==0)
13546               blackPlaysFirst = TRUE;
13547             else
13548               blackPlaysFirst = FALSE;
13549             startedFromSetupPosition = TRUE;
13550
13551             CopyBoard(boards[0], initial_position);
13552             if (blackPlaysFirst) {
13553                 currentMove = forwardMostMove = backwardMostMove = 1;
13554                 CopyBoard(boards[1], initial_position);
13555                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13556                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13557                 timeRemaining[0][1] = whiteTimeRemaining;
13558                 timeRemaining[1][1] = blackTimeRemaining;
13559                 if (commentList[0] != NULL) {
13560                     commentList[1] = commentList[0];
13561                     commentList[0] = NULL;
13562                 }
13563             } else {
13564                 currentMove = forwardMostMove = backwardMostMove = 0;
13565             }
13566         }
13567         yyboardindex = forwardMostMove;
13568         cm = (ChessMove) Myylex();
13569     }
13570
13571   if(!creatingBook) {
13572     if (first.pr == NoProc) {
13573         StartChessProgram(&first);
13574     }
13575     InitChessProgram(&first, FALSE);
13576     if(gameInfo.variant == VariantUnknown && *oldName) {
13577         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13578         gameInfo.variant = v;
13579     }
13580     SendToProgram("force\n", &first);
13581     if (startedFromSetupPosition) {
13582         SendBoard(&first, forwardMostMove);
13583     if (appData.debugMode) {
13584         fprintf(debugFP, "Load Game\n");
13585     }
13586         DisplayBothClocks();
13587     }
13588   }
13589
13590     /* [HGM] server: flag to write setup moves in broadcast file as one */
13591     loadFlag = appData.suppressLoadMoves;
13592
13593     while (cm == Comment) {
13594         char *p;
13595         if (appData.debugMode)
13596           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13597         p = yy_text;
13598         AppendComment(currentMove, p, FALSE);
13599         yyboardindex = forwardMostMove;
13600         cm = (ChessMove) Myylex();
13601     }
13602
13603     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13604         cm == WhiteWins || cm == BlackWins ||
13605         cm == GameIsDrawn || cm == GameUnfinished) {
13606         DisplayMessage("", _("No moves in game"));
13607         if (cmailMsgLoaded) {
13608             if (appData.debugMode)
13609               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13610             ClearHighlights();
13611             flipView = FALSE;
13612         }
13613         DrawPosition(FALSE, boards[currentMove]);
13614         DisplayBothClocks();
13615         gameMode = EditGame;
13616         ModeHighlight();
13617         gameFileFP = NULL;
13618         cmailOldMove = 0;
13619         return TRUE;
13620     }
13621
13622     // [HGM] PV info: routine tests if comment empty
13623     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13624         DisplayComment(currentMove - 1, commentList[currentMove]);
13625     }
13626     if (!matchMode && appData.timeDelay != 0)
13627       DrawPosition(FALSE, boards[currentMove]);
13628
13629     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13630       programStats.ok_to_send = 1;
13631     }
13632
13633     /* if the first token after the PGN tags is a move
13634      * and not move number 1, retrieve it from the parser
13635      */
13636     if (cm != MoveNumberOne)
13637         LoadGameOneMove(cm);
13638
13639     /* load the remaining moves from the file */
13640     while (LoadGameOneMove(EndOfFile)) {
13641       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13642       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13643     }
13644
13645     /* rewind to the start of the game */
13646     currentMove = backwardMostMove;
13647
13648     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13649
13650     if (oldGameMode == AnalyzeFile) {
13651       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13652       AnalyzeFileEvent();
13653     } else
13654     if (oldGameMode == AnalyzeMode) {
13655       AnalyzeFileEvent();
13656     }
13657
13658     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13659         long int w, b; // [HGM] adjourn: restore saved clock times
13660         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13661         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13662             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13663             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13664         }
13665     }
13666
13667     if(creatingBook) return TRUE;
13668     if (!matchMode && pos > 0) {
13669         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13670     } else
13671     if (matchMode || appData.timeDelay == 0) {
13672       ToEndEvent();
13673     } else if (appData.timeDelay > 0) {
13674       AutoPlayGameLoop();
13675     }
13676
13677     if (appData.debugMode)
13678         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13679
13680     loadFlag = 0; /* [HGM] true game starts */
13681     return TRUE;
13682 }
13683
13684 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13685 int
13686 ReloadPosition (int offset)
13687 {
13688     int positionNumber = lastLoadPositionNumber + offset;
13689     if (lastLoadPositionFP == NULL) {
13690         DisplayError(_("No position has been loaded yet"), 0);
13691         return FALSE;
13692     }
13693     if (positionNumber <= 0) {
13694         DisplayError(_("Can't back up any further"), 0);
13695         return FALSE;
13696     }
13697     return LoadPosition(lastLoadPositionFP, positionNumber,
13698                         lastLoadPositionTitle);
13699 }
13700
13701 /* Load the nth position from the given file */
13702 int
13703 LoadPositionFromFile (char *filename, int n, char *title)
13704 {
13705     FILE *f;
13706     char buf[MSG_SIZ];
13707
13708     if (strcmp(filename, "-") == 0) {
13709         return LoadPosition(stdin, n, "stdin");
13710     } else {
13711         f = fopen(filename, "rb");
13712         if (f == NULL) {
13713             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13714             DisplayError(buf, errno);
13715             return FALSE;
13716         } else {
13717             return LoadPosition(f, n, title);
13718         }
13719     }
13720 }
13721
13722 /* Load the nth position from the given open file, and close it */
13723 int
13724 LoadPosition (FILE *f, int positionNumber, char *title)
13725 {
13726     char *p, line[MSG_SIZ];
13727     Board initial_position;
13728     int i, j, fenMode, pn;
13729
13730     if (gameMode == Training )
13731         SetTrainingModeOff();
13732
13733     if (gameMode != BeginningOfGame) {
13734         Reset(FALSE, TRUE);
13735     }
13736     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13737         fclose(lastLoadPositionFP);
13738     }
13739     if (positionNumber == 0) positionNumber = 1;
13740     lastLoadPositionFP = f;
13741     lastLoadPositionNumber = positionNumber;
13742     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13743     if (first.pr == NoProc && !appData.noChessProgram) {
13744       StartChessProgram(&first);
13745       InitChessProgram(&first, FALSE);
13746     }
13747     pn = positionNumber;
13748     if (positionNumber < 0) {
13749         /* Negative position number means to seek to that byte offset */
13750         if (fseek(f, -positionNumber, 0) == -1) {
13751             DisplayError(_("Can't seek on position file"), 0);
13752             return FALSE;
13753         };
13754         pn = 1;
13755     } else {
13756         if (fseek(f, 0, 0) == -1) {
13757             if (f == lastLoadPositionFP ?
13758                 positionNumber == lastLoadPositionNumber + 1 :
13759                 positionNumber == 1) {
13760                 pn = 1;
13761             } else {
13762                 DisplayError(_("Can't seek on position file"), 0);
13763                 return FALSE;
13764             }
13765         }
13766     }
13767     /* See if this file is FEN or old-style xboard */
13768     if (fgets(line, MSG_SIZ, f) == NULL) {
13769         DisplayError(_("Position not found in file"), 0);
13770         return FALSE;
13771     }
13772     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13773     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13774
13775     if (pn >= 2) {
13776         if (fenMode || line[0] == '#') pn--;
13777         while (pn > 0) {
13778             /* skip positions before number pn */
13779             if (fgets(line, MSG_SIZ, f) == NULL) {
13780                 Reset(TRUE, TRUE);
13781                 DisplayError(_("Position not found in file"), 0);
13782                 return FALSE;
13783             }
13784             if (fenMode || line[0] == '#') pn--;
13785         }
13786     }
13787
13788     if (fenMode) {
13789         char *p;
13790         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13791             DisplayError(_("Bad FEN position in file"), 0);
13792             return FALSE;
13793         }
13794         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13795             sscanf(p+4, "%[^;]", bestMove);
13796         } else *bestMove = NULLCHAR;
13797         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13798             sscanf(p+4, "%[^;]", avoidMove);
13799         } else *avoidMove = NULLCHAR;
13800     } else {
13801         (void) fgets(line, MSG_SIZ, f);
13802         (void) fgets(line, MSG_SIZ, f);
13803
13804         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13805             (void) fgets(line, MSG_SIZ, f);
13806             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13807                 if (*p == ' ')
13808                   continue;
13809                 initial_position[i][j++] = CharToPiece(*p);
13810             }
13811         }
13812
13813         blackPlaysFirst = FALSE;
13814         if (!feof(f)) {
13815             (void) fgets(line, MSG_SIZ, f);
13816             if (strncmp(line, "black", strlen("black"))==0)
13817               blackPlaysFirst = TRUE;
13818         }
13819     }
13820     startedFromSetupPosition = TRUE;
13821
13822     CopyBoard(boards[0], initial_position);
13823     if (blackPlaysFirst) {
13824         currentMove = forwardMostMove = backwardMostMove = 1;
13825         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13826         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13827         CopyBoard(boards[1], initial_position);
13828         DisplayMessage("", _("Black to play"));
13829     } else {
13830         currentMove = forwardMostMove = backwardMostMove = 0;
13831         DisplayMessage("", _("White to play"));
13832     }
13833     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13834     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13835         SendToProgram("force\n", &first);
13836         SendBoard(&first, forwardMostMove);
13837     }
13838     if (appData.debugMode) {
13839 int i, j;
13840   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13841   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13842         fprintf(debugFP, "Load Position\n");
13843     }
13844
13845     if (positionNumber > 1) {
13846       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13847         DisplayTitle(line);
13848     } else {
13849         DisplayTitle(title);
13850     }
13851     gameMode = EditGame;
13852     ModeHighlight();
13853     ResetClocks();
13854     timeRemaining[0][1] = whiteTimeRemaining;
13855     timeRemaining[1][1] = blackTimeRemaining;
13856     DrawPosition(FALSE, boards[currentMove]);
13857
13858     return TRUE;
13859 }
13860
13861
13862 void
13863 CopyPlayerNameIntoFileName (char **dest, char *src)
13864 {
13865     while (*src != NULLCHAR && *src != ',') {
13866         if (*src == ' ') {
13867             *(*dest)++ = '_';
13868             src++;
13869         } else {
13870             *(*dest)++ = *src++;
13871         }
13872     }
13873 }
13874
13875 char *
13876 DefaultFileName (char *ext)
13877 {
13878     static char def[MSG_SIZ];
13879     char *p;
13880
13881     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13882         p = def;
13883         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13884         *p++ = '-';
13885         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13886         *p++ = '.';
13887         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13888     } else {
13889         def[0] = NULLCHAR;
13890     }
13891     return def;
13892 }
13893
13894 /* Save the current game to the given file */
13895 int
13896 SaveGameToFile (char *filename, int append)
13897 {
13898     FILE *f;
13899     char buf[MSG_SIZ];
13900     int result, i, t,tot=0;
13901
13902     if (strcmp(filename, "-") == 0) {
13903         return SaveGame(stdout, 0, NULL);
13904     } else {
13905         for(i=0; i<10; i++) { // upto 10 tries
13906              f = fopen(filename, append ? "a" : "w");
13907              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13908              if(f || errno != 13) break;
13909              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13910              tot += t;
13911         }
13912         if (f == NULL) {
13913             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13914             DisplayError(buf, errno);
13915             return FALSE;
13916         } else {
13917             safeStrCpy(buf, lastMsg, MSG_SIZ);
13918             DisplayMessage(_("Waiting for access to save file"), "");
13919             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13920             DisplayMessage(_("Saving game"), "");
13921             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13922             result = SaveGame(f, 0, NULL);
13923             DisplayMessage(buf, "");
13924             return result;
13925         }
13926     }
13927 }
13928
13929 char *
13930 SavePart (char *str)
13931 {
13932     static char buf[MSG_SIZ];
13933     char *p;
13934
13935     p = strchr(str, ' ');
13936     if (p == NULL) return str;
13937     strncpy(buf, str, p - str);
13938     buf[p - str] = NULLCHAR;
13939     return buf;
13940 }
13941
13942 #define PGN_MAX_LINE 75
13943
13944 #define PGN_SIDE_WHITE  0
13945 #define PGN_SIDE_BLACK  1
13946
13947 static int
13948 FindFirstMoveOutOfBook (int side)
13949 {
13950     int result = -1;
13951
13952     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13953         int index = backwardMostMove;
13954         int has_book_hit = 0;
13955
13956         if( (index % 2) != side ) {
13957             index++;
13958         }
13959
13960         while( index < forwardMostMove ) {
13961             /* Check to see if engine is in book */
13962             int depth = pvInfoList[index].depth;
13963             int score = pvInfoList[index].score;
13964             int in_book = 0;
13965
13966             if( depth <= 2 ) {
13967                 in_book = 1;
13968             }
13969             else if( score == 0 && depth == 63 ) {
13970                 in_book = 1; /* Zappa */
13971             }
13972             else if( score == 2 && depth == 99 ) {
13973                 in_book = 1; /* Abrok */
13974             }
13975
13976             has_book_hit += in_book;
13977
13978             if( ! in_book ) {
13979                 result = index;
13980
13981                 break;
13982             }
13983
13984             index += 2;
13985         }
13986     }
13987
13988     return result;
13989 }
13990
13991 void
13992 GetOutOfBookInfo (char * buf)
13993 {
13994     int oob[2];
13995     int i;
13996     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13997
13998     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13999     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
14000
14001     *buf = '\0';
14002
14003     if( oob[0] >= 0 || oob[1] >= 0 ) {
14004         for( i=0; i<2; i++ ) {
14005             int idx = oob[i];
14006
14007             if( idx >= 0 ) {
14008                 if( i > 0 && oob[0] >= 0 ) {
14009                     strcat( buf, "   " );
14010                 }
14011
14012                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
14013                 sprintf( buf+strlen(buf), "%s%.2f",
14014                     pvInfoList[idx].score >= 0 ? "+" : "",
14015                     pvInfoList[idx].score / 100.0 );
14016             }
14017         }
14018     }
14019 }
14020
14021 /* Save game in PGN style */
14022 static void
14023 SaveGamePGN2 (FILE *f)
14024 {
14025     int i, offset, linelen, newblock;
14026 //    char *movetext;
14027     char numtext[32];
14028     int movelen, numlen, blank;
14029     char move_buffer[100]; /* [AS] Buffer for move+PV info */
14030
14031     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14032
14033     PrintPGNTags(f, &gameInfo);
14034
14035     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
14036
14037     if (backwardMostMove > 0 || startedFromSetupPosition) {
14038         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
14039         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
14040         fprintf(f, "\n{--------------\n");
14041         PrintPosition(f, backwardMostMove);
14042         fprintf(f, "--------------}\n");
14043         free(fen);
14044     }
14045     else {
14046         /* [AS] Out of book annotation */
14047         if( appData.saveOutOfBookInfo ) {
14048             char buf[64];
14049
14050             GetOutOfBookInfo( buf );
14051
14052             if( buf[0] != '\0' ) {
14053                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
14054             }
14055         }
14056
14057         fprintf(f, "\n");
14058     }
14059
14060     i = backwardMostMove;
14061     linelen = 0;
14062     newblock = TRUE;
14063
14064     while (i < forwardMostMove) {
14065         /* Print comments preceding this move */
14066         if (commentList[i] != NULL) {
14067             if (linelen > 0) fprintf(f, "\n");
14068             fprintf(f, "%s", commentList[i]);
14069             linelen = 0;
14070             newblock = TRUE;
14071         }
14072
14073         /* Format move number */
14074         if ((i % 2) == 0)
14075           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
14076         else
14077           if (newblock)
14078             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
14079           else
14080             numtext[0] = NULLCHAR;
14081
14082         numlen = strlen(numtext);
14083         newblock = FALSE;
14084
14085         /* Print move number */
14086         blank = linelen > 0 && numlen > 0;
14087         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
14088             fprintf(f, "\n");
14089             linelen = 0;
14090             blank = 0;
14091         }
14092         if (blank) {
14093             fprintf(f, " ");
14094             linelen++;
14095         }
14096         fprintf(f, "%s", numtext);
14097         linelen += numlen;
14098
14099         /* Get move */
14100         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
14101         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
14102
14103         /* Print move */
14104         blank = linelen > 0 && movelen > 0;
14105         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14106             fprintf(f, "\n");
14107             linelen = 0;
14108             blank = 0;
14109         }
14110         if (blank) {
14111             fprintf(f, " ");
14112             linelen++;
14113         }
14114         fprintf(f, "%s", move_buffer);
14115         linelen += movelen;
14116
14117         /* [AS] Add PV info if present */
14118         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
14119             /* [HGM] add time */
14120             char buf[MSG_SIZ]; int seconds;
14121
14122             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14123
14124             if( seconds <= 0)
14125               buf[0] = 0;
14126             else
14127               if( seconds < 30 )
14128                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14129               else
14130                 {
14131                   seconds = (seconds + 4)/10; // round to full seconds
14132                   if( seconds < 60 )
14133                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14134                   else
14135                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14136                 }
14137
14138             if(appData.cumulativeTimePGN) {
14139                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14140             }
14141
14142             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14143                       pvInfoList[i].score >= 0 ? "+" : "",
14144                       pvInfoList[i].score / 100.0,
14145                       pvInfoList[i].depth,
14146                       buf );
14147
14148             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14149
14150             /* Print score/depth */
14151             blank = linelen > 0 && movelen > 0;
14152             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14153                 fprintf(f, "\n");
14154                 linelen = 0;
14155                 blank = 0;
14156             }
14157             if (blank) {
14158                 fprintf(f, " ");
14159                 linelen++;
14160             }
14161             fprintf(f, "%s", move_buffer);
14162             linelen += movelen;
14163         }
14164
14165         i++;
14166     }
14167
14168     /* Start a new line */
14169     if (linelen > 0) fprintf(f, "\n");
14170
14171     /* Print comments after last move */
14172     if (commentList[i] != NULL) {
14173         fprintf(f, "%s\n", commentList[i]);
14174     }
14175
14176     /* Print result */
14177     if (gameInfo.resultDetails != NULL &&
14178         gameInfo.resultDetails[0] != NULLCHAR) {
14179         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14180         if(gameInfo.result == GameUnfinished && appData.clockMode &&
14181            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14182             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14183         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14184     } else {
14185         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14186     }
14187 }
14188
14189 /* Save game in PGN style and close the file */
14190 int
14191 SaveGamePGN (FILE *f)
14192 {
14193     SaveGamePGN2(f);
14194     fclose(f);
14195     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14196     return TRUE;
14197 }
14198
14199 /* Save game in old style and close the file */
14200 int
14201 SaveGameOldStyle (FILE *f)
14202 {
14203     int i, offset;
14204     time_t tm;
14205
14206     tm = time((time_t *) NULL);
14207
14208     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14209     PrintOpponents(f);
14210
14211     if (backwardMostMove > 0 || startedFromSetupPosition) {
14212         fprintf(f, "\n[--------------\n");
14213         PrintPosition(f, backwardMostMove);
14214         fprintf(f, "--------------]\n");
14215     } else {
14216         fprintf(f, "\n");
14217     }
14218
14219     i = backwardMostMove;
14220     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14221
14222     while (i < forwardMostMove) {
14223         if (commentList[i] != NULL) {
14224             fprintf(f, "[%s]\n", commentList[i]);
14225         }
14226
14227         if ((i % 2) == 1) {
14228             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14229             i++;
14230         } else {
14231             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14232             i++;
14233             if (commentList[i] != NULL) {
14234                 fprintf(f, "\n");
14235                 continue;
14236             }
14237             if (i >= forwardMostMove) {
14238                 fprintf(f, "\n");
14239                 break;
14240             }
14241             fprintf(f, "%s\n", parseList[i]);
14242             i++;
14243         }
14244     }
14245
14246     if (commentList[i] != NULL) {
14247         fprintf(f, "[%s]\n", commentList[i]);
14248     }
14249
14250     /* This isn't really the old style, but it's close enough */
14251     if (gameInfo.resultDetails != NULL &&
14252         gameInfo.resultDetails[0] != NULLCHAR) {
14253         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14254                 gameInfo.resultDetails);
14255     } else {
14256         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14257     }
14258
14259     fclose(f);
14260     return TRUE;
14261 }
14262
14263 /* Save the current game to open file f and close the file */
14264 int
14265 SaveGame (FILE *f, int dummy, char *dummy2)
14266 {
14267     if (gameMode == EditPosition) EditPositionDone(TRUE);
14268     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14269     if (appData.oldSaveStyle)
14270       return SaveGameOldStyle(f);
14271     else
14272       return SaveGamePGN(f);
14273 }
14274
14275 /* Save the current position to the given file */
14276 int
14277 SavePositionToFile (char *filename)
14278 {
14279     FILE *f;
14280     char buf[MSG_SIZ];
14281
14282     if (strcmp(filename, "-") == 0) {
14283         return SavePosition(stdout, 0, NULL);
14284     } else {
14285         f = fopen(filename, "a");
14286         if (f == NULL) {
14287             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14288             DisplayError(buf, errno);
14289             return FALSE;
14290         } else {
14291             safeStrCpy(buf, lastMsg, MSG_SIZ);
14292             DisplayMessage(_("Waiting for access to save file"), "");
14293             flock(fileno(f), LOCK_EX); // [HGM] lock
14294             DisplayMessage(_("Saving position"), "");
14295             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14296             SavePosition(f, 0, NULL);
14297             DisplayMessage(buf, "");
14298             return TRUE;
14299         }
14300     }
14301 }
14302
14303 /* Save the current position to the given open file and close the file */
14304 int
14305 SavePosition (FILE *f, int dummy, char *dummy2)
14306 {
14307     time_t tm;
14308     char *fen;
14309
14310     if (gameMode == EditPosition) EditPositionDone(TRUE);
14311     if (appData.oldSaveStyle) {
14312         tm = time((time_t *) NULL);
14313
14314         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14315         PrintOpponents(f);
14316         fprintf(f, "[--------------\n");
14317         PrintPosition(f, currentMove);
14318         fprintf(f, "--------------]\n");
14319     } else {
14320         fen = PositionToFEN(currentMove, NULL, 1);
14321         fprintf(f, "%s\n", fen);
14322         free(fen);
14323     }
14324     fclose(f);
14325     return TRUE;
14326 }
14327
14328 void
14329 ReloadCmailMsgEvent (int unregister)
14330 {
14331 #if !WIN32
14332     static char *inFilename = NULL;
14333     static char *outFilename;
14334     int i;
14335     struct stat inbuf, outbuf;
14336     int status;
14337
14338     /* Any registered moves are unregistered if unregister is set, */
14339     /* i.e. invoked by the signal handler */
14340     if (unregister) {
14341         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14342             cmailMoveRegistered[i] = FALSE;
14343             if (cmailCommentList[i] != NULL) {
14344                 free(cmailCommentList[i]);
14345                 cmailCommentList[i] = NULL;
14346             }
14347         }
14348         nCmailMovesRegistered = 0;
14349     }
14350
14351     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14352         cmailResult[i] = CMAIL_NOT_RESULT;
14353     }
14354     nCmailResults = 0;
14355
14356     if (inFilename == NULL) {
14357         /* Because the filenames are static they only get malloced once  */
14358         /* and they never get freed                                      */
14359         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14360         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14361
14362         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14363         sprintf(outFilename, "%s.out", appData.cmailGameName);
14364     }
14365
14366     status = stat(outFilename, &outbuf);
14367     if (status < 0) {
14368         cmailMailedMove = FALSE;
14369     } else {
14370         status = stat(inFilename, &inbuf);
14371         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14372     }
14373
14374     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14375        counts the games, notes how each one terminated, etc.
14376
14377        It would be nice to remove this kludge and instead gather all
14378        the information while building the game list.  (And to keep it
14379        in the game list nodes instead of having a bunch of fixed-size
14380        parallel arrays.)  Note this will require getting each game's
14381        termination from the PGN tags, as the game list builder does
14382        not process the game moves.  --mann
14383        */
14384     cmailMsgLoaded = TRUE;
14385     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14386
14387     /* Load first game in the file or popup game menu */
14388     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14389
14390 #endif /* !WIN32 */
14391     return;
14392 }
14393
14394 int
14395 RegisterMove ()
14396 {
14397     FILE *f;
14398     char string[MSG_SIZ];
14399
14400     if (   cmailMailedMove
14401         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14402         return TRUE;            /* Allow free viewing  */
14403     }
14404
14405     /* Unregister move to ensure that we don't leave RegisterMove        */
14406     /* with the move registered when the conditions for registering no   */
14407     /* longer hold                                                       */
14408     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14409         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14410         nCmailMovesRegistered --;
14411
14412         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14413           {
14414               free(cmailCommentList[lastLoadGameNumber - 1]);
14415               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14416           }
14417     }
14418
14419     if (cmailOldMove == -1) {
14420         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14421         return FALSE;
14422     }
14423
14424     if (currentMove > cmailOldMove + 1) {
14425         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14426         return FALSE;
14427     }
14428
14429     if (currentMove < cmailOldMove) {
14430         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14431         return FALSE;
14432     }
14433
14434     if (forwardMostMove > currentMove) {
14435         /* Silently truncate extra moves */
14436         TruncateGame();
14437     }
14438
14439     if (   (currentMove == cmailOldMove + 1)
14440         || (   (currentMove == cmailOldMove)
14441             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14442                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14443         if (gameInfo.result != GameUnfinished) {
14444             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14445         }
14446
14447         if (commentList[currentMove] != NULL) {
14448             cmailCommentList[lastLoadGameNumber - 1]
14449               = StrSave(commentList[currentMove]);
14450         }
14451         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14452
14453         if (appData.debugMode)
14454           fprintf(debugFP, "Saving %s for game %d\n",
14455                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14456
14457         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14458
14459         f = fopen(string, "w");
14460         if (appData.oldSaveStyle) {
14461             SaveGameOldStyle(f); /* also closes the file */
14462
14463             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14464             f = fopen(string, "w");
14465             SavePosition(f, 0, NULL); /* also closes the file */
14466         } else {
14467             fprintf(f, "{--------------\n");
14468             PrintPosition(f, currentMove);
14469             fprintf(f, "--------------}\n\n");
14470
14471             SaveGame(f, 0, NULL); /* also closes the file*/
14472         }
14473
14474         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14475         nCmailMovesRegistered ++;
14476     } else if (nCmailGames == 1) {
14477         DisplayError(_("You have not made a move yet"), 0);
14478         return FALSE;
14479     }
14480
14481     return TRUE;
14482 }
14483
14484 void
14485 MailMoveEvent ()
14486 {
14487 #if !WIN32
14488     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14489     FILE *commandOutput;
14490     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14491     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14492     int nBuffers;
14493     int i;
14494     int archived;
14495     char *arcDir;
14496
14497     if (! cmailMsgLoaded) {
14498         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14499         return;
14500     }
14501
14502     if (nCmailGames == nCmailResults) {
14503         DisplayError(_("No unfinished games"), 0);
14504         return;
14505     }
14506
14507 #if CMAIL_PROHIBIT_REMAIL
14508     if (cmailMailedMove) {
14509       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);
14510         DisplayError(msg, 0);
14511         return;
14512     }
14513 #endif
14514
14515     if (! (cmailMailedMove || RegisterMove())) return;
14516
14517     if (   cmailMailedMove
14518         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14519       snprintf(string, MSG_SIZ, partCommandString,
14520                appData.debugMode ? " -v" : "", appData.cmailGameName);
14521         commandOutput = popen(string, "r");
14522
14523         if (commandOutput == NULL) {
14524             DisplayError(_("Failed to invoke cmail"), 0);
14525         } else {
14526             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14527                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14528             }
14529             if (nBuffers > 1) {
14530                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14531                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14532                 nBytes = MSG_SIZ - 1;
14533             } else {
14534                 (void) memcpy(msg, buffer, nBytes);
14535             }
14536             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14537
14538             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14539                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14540
14541                 archived = TRUE;
14542                 for (i = 0; i < nCmailGames; i ++) {
14543                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14544                         archived = FALSE;
14545                     }
14546                 }
14547                 if (   archived
14548                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14549                         != NULL)) {
14550                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14551                            arcDir,
14552                            appData.cmailGameName,
14553                            gameInfo.date);
14554                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14555                     cmailMsgLoaded = FALSE;
14556                 }
14557             }
14558
14559             DisplayInformation(msg);
14560             pclose(commandOutput);
14561         }
14562     } else {
14563         if ((*cmailMsg) != '\0') {
14564             DisplayInformation(cmailMsg);
14565         }
14566     }
14567
14568     return;
14569 #endif /* !WIN32 */
14570 }
14571
14572 char *
14573 CmailMsg ()
14574 {
14575 #if WIN32
14576     return NULL;
14577 #else
14578     int  prependComma = 0;
14579     char number[5];
14580     char string[MSG_SIZ];       /* Space for game-list */
14581     int  i;
14582
14583     if (!cmailMsgLoaded) return "";
14584
14585     if (cmailMailedMove) {
14586       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14587     } else {
14588         /* Create a list of games left */
14589       snprintf(string, MSG_SIZ, "[");
14590         for (i = 0; i < nCmailGames; i ++) {
14591             if (! (   cmailMoveRegistered[i]
14592                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14593                 if (prependComma) {
14594                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14595                 } else {
14596                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14597                     prependComma = 1;
14598                 }
14599
14600                 strcat(string, number);
14601             }
14602         }
14603         strcat(string, "]");
14604
14605         if (nCmailMovesRegistered + nCmailResults == 0) {
14606             switch (nCmailGames) {
14607               case 1:
14608                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14609                 break;
14610
14611               case 2:
14612                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14613                 break;
14614
14615               default:
14616                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14617                          nCmailGames);
14618                 break;
14619             }
14620         } else {
14621             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14622               case 1:
14623                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14624                          string);
14625                 break;
14626
14627               case 0:
14628                 if (nCmailResults == nCmailGames) {
14629                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14630                 } else {
14631                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14632                 }
14633                 break;
14634
14635               default:
14636                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14637                          string);
14638             }
14639         }
14640     }
14641     return cmailMsg;
14642 #endif /* WIN32 */
14643 }
14644
14645 void
14646 ResetGameEvent ()
14647 {
14648     if (gameMode == Training)
14649       SetTrainingModeOff();
14650
14651     Reset(TRUE, TRUE);
14652     cmailMsgLoaded = FALSE;
14653     if (appData.icsActive) {
14654       SendToICS(ics_prefix);
14655       SendToICS("refresh\n");
14656     }
14657 }
14658
14659 void
14660 ExitEvent (int status)
14661 {
14662     exiting++;
14663     if (exiting > 2) {
14664       /* Give up on clean exit */
14665       exit(status);
14666     }
14667     if (exiting > 1) {
14668       /* Keep trying for clean exit */
14669       return;
14670     }
14671
14672     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14673     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14674
14675     if (telnetISR != NULL) {
14676       RemoveInputSource(telnetISR);
14677     }
14678     if (icsPR != NoProc) {
14679       DestroyChildProcess(icsPR, TRUE);
14680     }
14681
14682     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14683     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14684
14685     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14686     /* make sure this other one finishes before killing it!                  */
14687     if(endingGame) { int count = 0;
14688         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14689         while(endingGame && count++ < 10) DoSleep(1);
14690         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14691     }
14692
14693     /* Kill off chess programs */
14694     if (first.pr != NoProc) {
14695         ExitAnalyzeMode();
14696
14697         DoSleep( appData.delayBeforeQuit );
14698         SendToProgram("quit\n", &first);
14699         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14700     }
14701     if (second.pr != NoProc) {
14702         DoSleep( appData.delayBeforeQuit );
14703         SendToProgram("quit\n", &second);
14704         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14705     }
14706     if (first.isr != NULL) {
14707         RemoveInputSource(first.isr);
14708     }
14709     if (second.isr != NULL) {
14710         RemoveInputSource(second.isr);
14711     }
14712
14713     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14714     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14715
14716     ShutDownFrontEnd();
14717     exit(status);
14718 }
14719
14720 void
14721 PauseEngine (ChessProgramState *cps)
14722 {
14723     SendToProgram("pause\n", cps);
14724     cps->pause = 2;
14725 }
14726
14727 void
14728 UnPauseEngine (ChessProgramState *cps)
14729 {
14730     SendToProgram("resume\n", cps);
14731     cps->pause = 1;
14732 }
14733
14734 void
14735 PauseEvent ()
14736 {
14737     if (appData.debugMode)
14738         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14739     if (pausing) {
14740         pausing = FALSE;
14741         ModeHighlight();
14742         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14743             StartClocks();
14744             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14745                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14746                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14747             }
14748             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14749             HandleMachineMove(stashedInputMove, stalledEngine);
14750             stalledEngine = NULL;
14751             return;
14752         }
14753         if (gameMode == MachinePlaysWhite ||
14754             gameMode == TwoMachinesPlay   ||
14755             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14756             if(first.pause)  UnPauseEngine(&first);
14757             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14758             if(second.pause) UnPauseEngine(&second);
14759             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14760             StartClocks();
14761         } else {
14762             DisplayBothClocks();
14763         }
14764         if (gameMode == PlayFromGameFile) {
14765             if (appData.timeDelay >= 0)
14766                 AutoPlayGameLoop();
14767         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14768             Reset(FALSE, TRUE);
14769             SendToICS(ics_prefix);
14770             SendToICS("refresh\n");
14771         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14772             ForwardInner(forwardMostMove);
14773         }
14774         pauseExamInvalid = FALSE;
14775     } else {
14776         switch (gameMode) {
14777           default:
14778             return;
14779           case IcsExamining:
14780             pauseExamForwardMostMove = forwardMostMove;
14781             pauseExamInvalid = FALSE;
14782             /* fall through */
14783           case IcsObserving:
14784           case IcsPlayingWhite:
14785           case IcsPlayingBlack:
14786             pausing = TRUE;
14787             ModeHighlight();
14788             return;
14789           case PlayFromGameFile:
14790             (void) StopLoadGameTimer();
14791             pausing = TRUE;
14792             ModeHighlight();
14793             break;
14794           case BeginningOfGame:
14795             if (appData.icsActive) return;
14796             /* else fall through */
14797           case MachinePlaysWhite:
14798           case MachinePlaysBlack:
14799           case TwoMachinesPlay:
14800             if (forwardMostMove == 0)
14801               return;           /* don't pause if no one has moved */
14802             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14803                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14804                 if(onMove->pause) {           // thinking engine can be paused
14805                     PauseEngine(onMove);      // do it
14806                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14807                         PauseEngine(onMove->other);
14808                     else
14809                         SendToProgram("easy\n", onMove->other);
14810                     StopClocks();
14811                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14812             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14813                 if(first.pause) {
14814                     PauseEngine(&first);
14815                     StopClocks();
14816                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14817             } else { // human on move, pause pondering by either method
14818                 if(first.pause)
14819                     PauseEngine(&first);
14820                 else if(appData.ponderNextMove)
14821                     SendToProgram("easy\n", &first);
14822                 StopClocks();
14823             }
14824             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14825           case AnalyzeMode:
14826             pausing = TRUE;
14827             ModeHighlight();
14828             break;
14829         }
14830     }
14831 }
14832
14833 void
14834 EditCommentEvent ()
14835 {
14836     char title[MSG_SIZ];
14837
14838     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14839       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14840     } else {
14841       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14842                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14843                parseList[currentMove - 1]);
14844     }
14845
14846     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14847 }
14848
14849
14850 void
14851 EditTagsEvent ()
14852 {
14853     char *tags = PGNTags(&gameInfo);
14854     bookUp = FALSE;
14855     EditTagsPopUp(tags, NULL);
14856     free(tags);
14857 }
14858
14859 void
14860 StartSecond ()
14861 {
14862     if(WaitForEngine(&second, StartSecond)) return;
14863     InitChessProgram(&second, FALSE);
14864     FeedMovesToProgram(&second, currentMove);
14865
14866     SendToProgram("analyze\n", &second);
14867     second.analyzing = TRUE;
14868     ThawUI();
14869 }
14870
14871 void
14872 ToggleSecond ()
14873 {
14874   if(second.analyzing) {
14875     SendToProgram("exit\n", &second);
14876     second.analyzing = FALSE;
14877   } else {
14878     StartSecond();
14879   }
14880 }
14881
14882 /* Toggle ShowThinking */
14883 void
14884 ToggleShowThinking()
14885 {
14886   appData.showThinking = !appData.showThinking;
14887   ShowThinkingEvent();
14888 }
14889
14890 int
14891 AnalyzeModeEvent ()
14892 {
14893     char buf[MSG_SIZ];
14894
14895     if (!first.analysisSupport) {
14896       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14897       DisplayError(buf, 0);
14898       return 0;
14899     }
14900     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14901     if (appData.icsActive) {
14902         if (gameMode != IcsObserving) {
14903           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14904             DisplayError(buf, 0);
14905             /* secure check */
14906             if (appData.icsEngineAnalyze) {
14907                 if (appData.debugMode)
14908                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14909                 ExitAnalyzeMode();
14910                 ModeHighlight();
14911             }
14912             return 0;
14913         }
14914         /* if enable, user wants to disable icsEngineAnalyze */
14915         if (appData.icsEngineAnalyze) {
14916                 ExitAnalyzeMode();
14917                 ModeHighlight();
14918                 return 0;
14919         }
14920         appData.icsEngineAnalyze = TRUE;
14921         if (appData.debugMode)
14922             fprintf(debugFP, "ICS engine analyze starting... \n");
14923     }
14924
14925     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14926     if (appData.noChessProgram || gameMode == AnalyzeMode)
14927       return 0;
14928
14929     if (gameMode != AnalyzeFile) {
14930         if (!appData.icsEngineAnalyze) {
14931                EditGameEvent();
14932                if (gameMode != EditGame) return 0;
14933         }
14934         if (!appData.showThinking) ToggleShowThinking();
14935         ResurrectChessProgram();
14936         SendToProgram("analyze\n", &first);
14937         first.analyzing = TRUE;
14938         /*first.maybeThinking = TRUE;*/
14939         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14940         EngineOutputPopUp();
14941     }
14942     if (!appData.icsEngineAnalyze) {
14943         gameMode = AnalyzeMode;
14944         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14945     }
14946     pausing = FALSE;
14947     ModeHighlight();
14948     SetGameInfo();
14949
14950     StartAnalysisClock();
14951     GetTimeMark(&lastNodeCountTime);
14952     lastNodeCount = 0;
14953     return 1;
14954 }
14955
14956 void
14957 AnalyzeFileEvent ()
14958 {
14959     if (appData.noChessProgram || gameMode == AnalyzeFile)
14960       return;
14961
14962     if (!first.analysisSupport) {
14963       char buf[MSG_SIZ];
14964       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14965       DisplayError(buf, 0);
14966       return;
14967     }
14968
14969     if (gameMode != AnalyzeMode) {
14970         keepInfo = 1; // mere annotating should not alter PGN tags
14971         EditGameEvent();
14972         keepInfo = 0;
14973         if (gameMode != EditGame) return;
14974         if (!appData.showThinking) ToggleShowThinking();
14975         ResurrectChessProgram();
14976         SendToProgram("analyze\n", &first);
14977         first.analyzing = TRUE;
14978         /*first.maybeThinking = TRUE;*/
14979         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14980         EngineOutputPopUp();
14981     }
14982     gameMode = AnalyzeFile;
14983     pausing = FALSE;
14984     ModeHighlight();
14985
14986     StartAnalysisClock();
14987     GetTimeMark(&lastNodeCountTime);
14988     lastNodeCount = 0;
14989     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14990     AnalysisPeriodicEvent(1);
14991 }
14992
14993 void
14994 MachineWhiteEvent ()
14995 {
14996     char buf[MSG_SIZ];
14997     char *bookHit = NULL;
14998
14999     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
15000       return;
15001
15002
15003     if (gameMode == PlayFromGameFile ||
15004         gameMode == TwoMachinesPlay  ||
15005         gameMode == Training         ||
15006         gameMode == AnalyzeMode      ||
15007         gameMode == EndOfGame)
15008         EditGameEvent();
15009
15010     if (gameMode == EditPosition)
15011         EditPositionDone(TRUE);
15012
15013     if (!WhiteOnMove(currentMove)) {
15014         DisplayError(_("It is not White's turn"), 0);
15015         return;
15016     }
15017
15018     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15019       ExitAnalyzeMode();
15020
15021     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15022         gameMode == AnalyzeFile)
15023         TruncateGame();
15024
15025     ResurrectChessProgram();    /* in case it isn't running */
15026     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
15027         gameMode = MachinePlaysWhite;
15028         ResetClocks();
15029     } else
15030     gameMode = MachinePlaysWhite;
15031     pausing = FALSE;
15032     ModeHighlight();
15033     SetGameInfo();
15034     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15035     DisplayTitle(buf);
15036     if (first.sendName) {
15037       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
15038       SendToProgram(buf, &first);
15039     }
15040     if (first.sendTime) {
15041       if (first.useColors) {
15042         SendToProgram("black\n", &first); /*gnu kludge*/
15043       }
15044       SendTimeRemaining(&first, TRUE);
15045     }
15046     if (first.useColors) {
15047       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
15048     }
15049     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15050     SetMachineThinkingEnables();
15051     first.maybeThinking = TRUE;
15052     StartClocks();
15053     firstMove = FALSE;
15054
15055     if (appData.autoFlipView && !flipView) {
15056       flipView = !flipView;
15057       DrawPosition(FALSE, NULL);
15058       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15059     }
15060
15061     if(bookHit) { // [HGM] book: simulate book reply
15062         static char bookMove[MSG_SIZ]; // a bit generous?
15063
15064         programStats.nodes = programStats.depth = programStats.time =
15065         programStats.score = programStats.got_only_move = 0;
15066         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15067
15068         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15069         strcat(bookMove, bookHit);
15070         savedMessage = bookMove; // args for deferred call
15071         savedState = &first;
15072         ScheduleDelayedEvent(DeferredBookMove, 1);
15073     }
15074 }
15075
15076 void
15077 MachineBlackEvent ()
15078 {
15079   char buf[MSG_SIZ];
15080   char *bookHit = NULL;
15081
15082     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
15083         return;
15084
15085
15086     if (gameMode == PlayFromGameFile ||
15087         gameMode == TwoMachinesPlay  ||
15088         gameMode == Training         ||
15089         gameMode == AnalyzeMode      ||
15090         gameMode == EndOfGame)
15091         EditGameEvent();
15092
15093     if (gameMode == EditPosition)
15094         EditPositionDone(TRUE);
15095
15096     if (WhiteOnMove(currentMove)) {
15097         DisplayError(_("It is not Black's turn"), 0);
15098         return;
15099     }
15100
15101     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
15102       ExitAnalyzeMode();
15103
15104     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15105         gameMode == AnalyzeFile)
15106         TruncateGame();
15107
15108     ResurrectChessProgram();    /* in case it isn't running */
15109     gameMode = MachinePlaysBlack;
15110     pausing = FALSE;
15111     ModeHighlight();
15112     SetGameInfo();
15113     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15114     DisplayTitle(buf);
15115     if (first.sendName) {
15116       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
15117       SendToProgram(buf, &first);
15118     }
15119     if (first.sendTime) {
15120       if (first.useColors) {
15121         SendToProgram("white\n", &first); /*gnu kludge*/
15122       }
15123       SendTimeRemaining(&first, FALSE);
15124     }
15125     if (first.useColors) {
15126       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
15127     }
15128     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
15129     SetMachineThinkingEnables();
15130     first.maybeThinking = TRUE;
15131     StartClocks();
15132
15133     if (appData.autoFlipView && flipView) {
15134       flipView = !flipView;
15135       DrawPosition(FALSE, NULL);
15136       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15137     }
15138     if(bookHit) { // [HGM] book: simulate book reply
15139         static char bookMove[MSG_SIZ]; // a bit generous?
15140
15141         programStats.nodes = programStats.depth = programStats.time =
15142         programStats.score = programStats.got_only_move = 0;
15143         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15144
15145         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15146         strcat(bookMove, bookHit);
15147         savedMessage = bookMove; // args for deferred call
15148         savedState = &first;
15149         ScheduleDelayedEvent(DeferredBookMove, 1);
15150     }
15151 }
15152
15153
15154 void
15155 DisplayTwoMachinesTitle ()
15156 {
15157     char buf[MSG_SIZ];
15158     if (appData.matchGames > 0) {
15159         if(appData.tourneyFile[0]) {
15160           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15161                    gameInfo.white, _("vs."), gameInfo.black,
15162                    nextGame+1, appData.matchGames+1,
15163                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15164         } else
15165         if (first.twoMachinesColor[0] == 'w') {
15166           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15167                    gameInfo.white, _("vs."),  gameInfo.black,
15168                    first.matchWins, second.matchWins,
15169                    matchGame - 1 - (first.matchWins + second.matchWins));
15170         } else {
15171           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15172                    gameInfo.white, _("vs."), gameInfo.black,
15173                    second.matchWins, first.matchWins,
15174                    matchGame - 1 - (first.matchWins + second.matchWins));
15175         }
15176     } else {
15177       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15178     }
15179     DisplayTitle(buf);
15180 }
15181
15182 void
15183 SettingsMenuIfReady ()
15184 {
15185   if (second.lastPing != second.lastPong) {
15186     DisplayMessage("", _("Waiting for second chess program"));
15187     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15188     return;
15189   }
15190   ThawUI();
15191   DisplayMessage("", "");
15192   SettingsPopUp(&second);
15193 }
15194
15195 int
15196 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15197 {
15198     char buf[MSG_SIZ];
15199     if (cps->pr == NoProc) {
15200         StartChessProgram(cps);
15201         if (cps->protocolVersion == 1) {
15202           retry();
15203           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15204         } else {
15205           /* kludge: allow timeout for initial "feature" command */
15206           if(retry != TwoMachinesEventIfReady) FreezeUI();
15207           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15208           DisplayMessage("", buf);
15209           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15210         }
15211         return 1;
15212     }
15213     return 0;
15214 }
15215
15216 void
15217 TwoMachinesEvent P((void))
15218 {
15219     int i, move = forwardMostMove;
15220     char buf[MSG_SIZ];
15221     ChessProgramState *onmove;
15222     char *bookHit = NULL;
15223     static int stalling = 0;
15224     TimeMark now;
15225     long wait;
15226
15227     if (appData.noChessProgram) return;
15228
15229     switch (gameMode) {
15230       case TwoMachinesPlay:
15231         return;
15232       case MachinePlaysWhite:
15233       case MachinePlaysBlack:
15234         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15235             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15236             return;
15237         }
15238         /* fall through */
15239       case BeginningOfGame:
15240       case PlayFromGameFile:
15241       case EndOfGame:
15242         EditGameEvent();
15243         if (gameMode != EditGame) return;
15244         break;
15245       case EditPosition:
15246         EditPositionDone(TRUE);
15247         break;
15248       case AnalyzeMode:
15249       case AnalyzeFile:
15250         ExitAnalyzeMode();
15251         break;
15252       case EditGame:
15253       default:
15254         break;
15255     }
15256
15257 //    forwardMostMove = currentMove;
15258     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15259     startingEngine = TRUE;
15260
15261     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15262
15263     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15264     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15265       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15266       return;
15267     }
15268   if(!appData.epd) {
15269     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15270
15271     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15272                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15273         startingEngine = matchMode = FALSE;
15274         DisplayError("second engine does not play this", 0);
15275         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15276         EditGameEvent(); // switch back to EditGame mode
15277         return;
15278     }
15279
15280     if(!stalling) {
15281       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15282       SendToProgram("force\n", &second);
15283       stalling = 1;
15284       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15285       return;
15286     }
15287   }
15288     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15289     if(appData.matchPause>10000 || appData.matchPause<10)
15290                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15291     wait = SubtractTimeMarks(&now, &pauseStart);
15292     if(wait < appData.matchPause) {
15293         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15294         return;
15295     }
15296     // we are now committed to starting the game
15297     stalling = 0;
15298     DisplayMessage("", "");
15299   if(!appData.epd) {
15300     if (startedFromSetupPosition) {
15301         SendBoard(&second, backwardMostMove);
15302     if (appData.debugMode) {
15303         fprintf(debugFP, "Two Machines\n");
15304     }
15305     }
15306     for (i = backwardMostMove; i < forwardMostMove; i++) {
15307         SendMoveToProgram(i, &second);
15308     }
15309   }
15310
15311     gameMode = TwoMachinesPlay;
15312     pausing = startingEngine = FALSE;
15313     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15314     SetGameInfo();
15315     DisplayTwoMachinesTitle();
15316     firstMove = TRUE;
15317     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15318         onmove = &first;
15319     } else {
15320         onmove = &second;
15321     }
15322     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15323     SendToProgram(first.computerString, &first);
15324     if (first.sendName) {
15325       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15326       SendToProgram(buf, &first);
15327     }
15328   if(!appData.epd) {
15329     SendToProgram(second.computerString, &second);
15330     if (second.sendName) {
15331       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15332       SendToProgram(buf, &second);
15333     }
15334   }
15335
15336     if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15337         ResetClocks();
15338         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15339         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15340     }
15341     if (onmove->sendTime) {
15342       if (onmove->useColors) {
15343         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15344       }
15345       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15346     }
15347     if (onmove->useColors) {
15348       SendToProgram(onmove->twoMachinesColor, onmove);
15349     }
15350     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15351 //    SendToProgram("go\n", onmove);
15352     onmove->maybeThinking = TRUE;
15353     SetMachineThinkingEnables();
15354
15355     StartClocks();
15356
15357     if(bookHit) { // [HGM] book: simulate book reply
15358         static char bookMove[MSG_SIZ]; // a bit generous?
15359
15360         programStats.nodes = programStats.depth = programStats.time =
15361         programStats.score = programStats.got_only_move = 0;
15362         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15363
15364         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15365         strcat(bookMove, bookHit);
15366         savedMessage = bookMove; // args for deferred call
15367         savedState = onmove;
15368         ScheduleDelayedEvent(DeferredBookMove, 1);
15369     }
15370 }
15371
15372 void
15373 TrainingEvent ()
15374 {
15375     if (gameMode == Training) {
15376       SetTrainingModeOff();
15377       gameMode = PlayFromGameFile;
15378       DisplayMessage("", _("Training mode off"));
15379     } else {
15380       gameMode = Training;
15381       animateTraining = appData.animate;
15382
15383       /* make sure we are not already at the end of the game */
15384       if (currentMove < forwardMostMove) {
15385         SetTrainingModeOn();
15386         DisplayMessage("", _("Training mode on"));
15387       } else {
15388         gameMode = PlayFromGameFile;
15389         DisplayError(_("Already at end of game"), 0);
15390       }
15391     }
15392     ModeHighlight();
15393 }
15394
15395 void
15396 IcsClientEvent ()
15397 {
15398     if (!appData.icsActive) return;
15399     switch (gameMode) {
15400       case IcsPlayingWhite:
15401       case IcsPlayingBlack:
15402       case IcsObserving:
15403       case IcsIdle:
15404       case BeginningOfGame:
15405       case IcsExamining:
15406         return;
15407
15408       case EditGame:
15409         break;
15410
15411       case EditPosition:
15412         EditPositionDone(TRUE);
15413         break;
15414
15415       case AnalyzeMode:
15416       case AnalyzeFile:
15417         ExitAnalyzeMode();
15418         break;
15419
15420       default:
15421         EditGameEvent();
15422         break;
15423     }
15424
15425     gameMode = IcsIdle;
15426     ModeHighlight();
15427     return;
15428 }
15429
15430 void
15431 EditGameEvent ()
15432 {
15433     int i;
15434
15435     switch (gameMode) {
15436       case Training:
15437         SetTrainingModeOff();
15438         break;
15439       case MachinePlaysWhite:
15440       case MachinePlaysBlack:
15441       case BeginningOfGame:
15442         SendToProgram("force\n", &first);
15443         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15444             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15445                 char buf[MSG_SIZ];
15446                 abortEngineThink = TRUE;
15447                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15448                 SendToProgram(buf, &first);
15449                 DisplayMessage("Aborting engine think", "");
15450                 FreezeUI();
15451             }
15452         }
15453         SetUserThinkingEnables();
15454         break;
15455       case PlayFromGameFile:
15456         (void) StopLoadGameTimer();
15457         if (gameFileFP != NULL) {
15458             gameFileFP = NULL;
15459         }
15460         break;
15461       case EditPosition:
15462         EditPositionDone(TRUE);
15463         break;
15464       case AnalyzeMode:
15465       case AnalyzeFile:
15466         ExitAnalyzeMode();
15467         SendToProgram("force\n", &first);
15468         break;
15469       case TwoMachinesPlay:
15470         GameEnds(EndOfFile, NULL, GE_PLAYER);
15471         ResurrectChessProgram();
15472         SetUserThinkingEnables();
15473         break;
15474       case EndOfGame:
15475         ResurrectChessProgram();
15476         break;
15477       case IcsPlayingBlack:
15478       case IcsPlayingWhite:
15479         DisplayError(_("Warning: You are still playing a game"), 0);
15480         break;
15481       case IcsObserving:
15482         DisplayError(_("Warning: You are still observing a game"), 0);
15483         break;
15484       case IcsExamining:
15485         DisplayError(_("Warning: You are still examining a game"), 0);
15486         break;
15487       case IcsIdle:
15488         break;
15489       case EditGame:
15490       default:
15491         return;
15492     }
15493
15494     pausing = FALSE;
15495     StopClocks();
15496     first.offeredDraw = second.offeredDraw = 0;
15497
15498     if (gameMode == PlayFromGameFile) {
15499         whiteTimeRemaining = timeRemaining[0][currentMove];
15500         blackTimeRemaining = timeRemaining[1][currentMove];
15501         DisplayTitle("");
15502     }
15503
15504     if (gameMode == MachinePlaysWhite ||
15505         gameMode == MachinePlaysBlack ||
15506         gameMode == TwoMachinesPlay ||
15507         gameMode == EndOfGame) {
15508         i = forwardMostMove;
15509         while (i > currentMove) {
15510             SendToProgram("undo\n", &first);
15511             i--;
15512         }
15513         if(!adjustedClock) {
15514         whiteTimeRemaining = timeRemaining[0][currentMove];
15515         blackTimeRemaining = timeRemaining[1][currentMove];
15516         DisplayBothClocks();
15517         }
15518         if (whiteFlag || blackFlag) {
15519             whiteFlag = blackFlag = 0;
15520         }
15521         DisplayTitle("");
15522     }
15523
15524     gameMode = EditGame;
15525     ModeHighlight();
15526     SetGameInfo();
15527 }
15528
15529 void
15530 EditPositionEvent ()
15531 {
15532     int i;
15533     if (gameMode == EditPosition) {
15534         EditGameEvent();
15535         return;
15536     }
15537
15538     EditGameEvent();
15539     if (gameMode != EditGame) return;
15540
15541     gameMode = EditPosition;
15542     ModeHighlight();
15543     SetGameInfo();
15544     CopyBoard(rightsBoard, nullBoard);
15545     if (currentMove > 0)
15546       CopyBoard(boards[0], boards[currentMove]);
15547     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15548       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15549
15550     blackPlaysFirst = !WhiteOnMove(currentMove);
15551     ResetClocks();
15552     currentMove = forwardMostMove = backwardMostMove = 0;
15553     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15554     DisplayMove(-1);
15555     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15556 }
15557
15558 void
15559 ExitAnalyzeMode ()
15560 {
15561     /* [DM] icsEngineAnalyze - possible call from other functions */
15562     if (appData.icsEngineAnalyze) {
15563         appData.icsEngineAnalyze = FALSE;
15564
15565         DisplayMessage("",_("Close ICS engine analyze..."));
15566     }
15567     if (first.analysisSupport && first.analyzing) {
15568       SendToBoth("exit\n");
15569       first.analyzing = second.analyzing = FALSE;
15570     }
15571     thinkOutput[0] = NULLCHAR;
15572 }
15573
15574 void
15575 EditPositionDone (Boolean fakeRights)
15576 {
15577     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15578
15579     startedFromSetupPosition = TRUE;
15580     InitChessProgram(&first, FALSE);
15581     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15582       int r, f;
15583       boards[0][EP_STATUS] = EP_NONE;
15584       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15585       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15586         if(rightsBoard[r][f]) {
15587           ChessSquare p = boards[0][r][f];
15588           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15589           else if(p == king) boards[0][CASTLING][2] = f;
15590           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15591           else rightsBoard[r][f] = 2; // mark for second pass
15592         }
15593       }
15594       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15595         if(rightsBoard[r][f] == 2) {
15596           ChessSquare p = boards[0][r][f];
15597           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15598           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15599         }
15600       }
15601     }
15602     SendToProgram("force\n", &first);
15603     if (blackPlaysFirst) {
15604         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15605         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15606         currentMove = forwardMostMove = backwardMostMove = 1;
15607         CopyBoard(boards[1], boards[0]);
15608     } else {
15609         currentMove = forwardMostMove = backwardMostMove = 0;
15610     }
15611     SendBoard(&first, forwardMostMove);
15612     if (appData.debugMode) {
15613         fprintf(debugFP, "EditPosDone\n");
15614     }
15615     DisplayTitle("");
15616     DisplayMessage("", "");
15617     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15618     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15619     gameMode = EditGame;
15620     ModeHighlight();
15621     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15622     ClearHighlights(); /* [AS] */
15623 }
15624
15625 /* Pause for `ms' milliseconds */
15626 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15627 void
15628 TimeDelay (long ms)
15629 {
15630     TimeMark m1, m2;
15631
15632     GetTimeMark(&m1);
15633     do {
15634         GetTimeMark(&m2);
15635     } while (SubtractTimeMarks(&m2, &m1) < ms);
15636 }
15637
15638 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15639 void
15640 SendMultiLineToICS (char *buf)
15641 {
15642     char temp[MSG_SIZ+1], *p;
15643     int len;
15644
15645     len = strlen(buf);
15646     if (len > MSG_SIZ)
15647       len = MSG_SIZ;
15648
15649     strncpy(temp, buf, len);
15650     temp[len] = 0;
15651
15652     p = temp;
15653     while (*p) {
15654         if (*p == '\n' || *p == '\r')
15655           *p = ' ';
15656         ++p;
15657     }
15658
15659     strcat(temp, "\n");
15660     SendToICS(temp);
15661     SendToPlayer(temp, strlen(temp));
15662 }
15663
15664 void
15665 SetWhiteToPlayEvent ()
15666 {
15667     if (gameMode == EditPosition) {
15668         blackPlaysFirst = FALSE;
15669         DisplayBothClocks();    /* works because currentMove is 0 */
15670     } else if (gameMode == IcsExamining) {
15671         SendToICS(ics_prefix);
15672         SendToICS("tomove white\n");
15673     }
15674 }
15675
15676 void
15677 SetBlackToPlayEvent ()
15678 {
15679     if (gameMode == EditPosition) {
15680         blackPlaysFirst = TRUE;
15681         currentMove = 1;        /* kludge */
15682         DisplayBothClocks();
15683         currentMove = 0;
15684     } else if (gameMode == IcsExamining) {
15685         SendToICS(ics_prefix);
15686         SendToICS("tomove black\n");
15687     }
15688 }
15689
15690 void
15691 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15692 {
15693     char buf[MSG_SIZ];
15694     ChessSquare piece = boards[0][y][x];
15695     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15696     static int lastVariant;
15697     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15698
15699     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15700
15701     switch (selection) {
15702       case ClearBoard:
15703         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15704         MarkTargetSquares(1);
15705         CopyBoard(currentBoard, boards[0]);
15706         CopyBoard(menuBoard, initialPosition);
15707         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15708             SendToICS(ics_prefix);
15709             SendToICS("bsetup clear\n");
15710         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15711             SendToICS(ics_prefix);
15712             SendToICS("clearboard\n");
15713         } else {
15714             int nonEmpty = 0;
15715             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15716                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15717                 for (y = 0; y < BOARD_HEIGHT; y++) {
15718                     if (gameMode == IcsExamining) {
15719                         if (boards[currentMove][y][x] != EmptySquare) {
15720                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15721                                     AAA + x, ONE + y);
15722                             SendToICS(buf);
15723                         }
15724                     } else if(boards[0][y][x] != DarkSquare) {
15725                         if(boards[0][y][x] != p) nonEmpty++;
15726                         boards[0][y][x] = p;
15727                     }
15728                 }
15729             }
15730             CopyBoard(rightsBoard, nullBoard);
15731             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15732                 int r, i;
15733                 for(r = 0; r < BOARD_HEIGHT; r++) {
15734                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15735                     ChessSquare p = menuBoard[r][x];
15736                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15737                   }
15738                 }
15739                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15740                 DisplayMessage("Clicking clock again restores position", "");
15741                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15742                 if(!nonEmpty) { // asked to clear an empty board
15743                     CopyBoard(boards[0], menuBoard);
15744                 } else
15745                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15746                     CopyBoard(boards[0], initialPosition);
15747                 } else
15748                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15749                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15750                     CopyBoard(boards[0], erasedBoard);
15751                 } else
15752                     CopyBoard(erasedBoard, currentBoard);
15753
15754                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15755                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15756             }
15757         }
15758         if (gameMode == EditPosition) {
15759             DrawPosition(FALSE, boards[0]);
15760         }
15761         break;
15762
15763       case WhitePlay:
15764         SetWhiteToPlayEvent();
15765         break;
15766
15767       case BlackPlay:
15768         SetBlackToPlayEvent();
15769         break;
15770
15771       case EmptySquare:
15772         if (gameMode == IcsExamining) {
15773             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15774             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15775             SendToICS(buf);
15776         } else {
15777             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15778                 if(x == BOARD_LEFT-2) {
15779                     if(y < handSize-1-gameInfo.holdingsSize) break;
15780                     boards[0][y][1] = 0;
15781                 } else
15782                 if(x == BOARD_RGHT+1) {
15783                     if(y >= gameInfo.holdingsSize) break;
15784                     boards[0][y][BOARD_WIDTH-2] = 0;
15785                 } else break;
15786             }
15787             boards[0][y][x] = EmptySquare;
15788             DrawPosition(FALSE, boards[0]);
15789         }
15790         break;
15791
15792       case PromotePiece:
15793         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15794            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15795             selection = (ChessSquare) (PROMOTED(piece));
15796         } else if(piece == EmptySquare) selection = WhiteSilver;
15797         else selection = (ChessSquare)((int)piece - 1);
15798         goto defaultlabel;
15799
15800       case DemotePiece:
15801         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15802            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15803             selection = (ChessSquare) (DEMOTED(piece));
15804         } else if(piece == EmptySquare) selection = BlackSilver;
15805         else selection = (ChessSquare)((int)piece + 1);
15806         goto defaultlabel;
15807
15808       case WhiteQueen:
15809       case BlackQueen:
15810         if(gameInfo.variant == VariantShatranj ||
15811            gameInfo.variant == VariantXiangqi  ||
15812            gameInfo.variant == VariantCourier  ||
15813            gameInfo.variant == VariantASEAN    ||
15814            gameInfo.variant == VariantMakruk     )
15815             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15816         goto defaultlabel;
15817
15818       case WhiteRook:
15819         baseRank = 0;
15820       case BlackRook:
15821         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15822         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15823         goto defaultlabel;
15824
15825       case WhiteKing:
15826         baseRank = 0;
15827       case BlackKing:
15828         if(gameInfo.variant == VariantXiangqi)
15829             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15830         if(gameInfo.variant == VariantKnightmate)
15831             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15832         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15833       default:
15834         defaultlabel:
15835         if (gameMode == IcsExamining) {
15836             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15837             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15838                      PieceToChar(selection), AAA + x, ONE + y);
15839             SendToICS(buf);
15840         } else {
15841             rightsBoard[y][x] = hasRights;
15842             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15843                 int n;
15844                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15845                     n = PieceToNumber(selection - BlackPawn);
15846                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15847                     boards[0][handSize-1-n][0] = selection;
15848                     boards[0][handSize-1-n][1]++;
15849                 } else
15850                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15851                     n = PieceToNumber(selection);
15852                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15853                     boards[0][n][BOARD_WIDTH-1] = selection;
15854                     boards[0][n][BOARD_WIDTH-2]++;
15855                 }
15856             } else
15857             boards[0][y][x] = selection;
15858             DrawPosition(TRUE, boards[0]);
15859             ClearHighlights();
15860             fromX = fromY = -1;
15861         }
15862         break;
15863     }
15864 }
15865
15866
15867 void
15868 DropMenuEvent (ChessSquare selection, int x, int y)
15869 {
15870     ChessMove moveType;
15871
15872     switch (gameMode) {
15873       case IcsPlayingWhite:
15874       case MachinePlaysBlack:
15875         if (!WhiteOnMove(currentMove)) {
15876             DisplayMoveError(_("It is Black's turn"));
15877             return;
15878         }
15879         moveType = WhiteDrop;
15880         break;
15881       case IcsPlayingBlack:
15882       case MachinePlaysWhite:
15883         if (WhiteOnMove(currentMove)) {
15884             DisplayMoveError(_("It is White's turn"));
15885             return;
15886         }
15887         moveType = BlackDrop;
15888         break;
15889       case EditGame:
15890         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15891         break;
15892       default:
15893         return;
15894     }
15895
15896     if (moveType == BlackDrop && selection < BlackPawn) {
15897       selection = (ChessSquare) ((int) selection
15898                                  + (int) BlackPawn - (int) WhitePawn);
15899     }
15900     if (boards[currentMove][y][x] != EmptySquare) {
15901         DisplayMoveError(_("That square is occupied"));
15902         return;
15903     }
15904
15905     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15906 }
15907
15908 void
15909 AcceptEvent ()
15910 {
15911     /* Accept a pending offer of any kind from opponent */
15912
15913     if (appData.icsActive) {
15914         SendToICS(ics_prefix);
15915         SendToICS("accept\n");
15916     } else if (cmailMsgLoaded) {
15917         if (currentMove == cmailOldMove &&
15918             commentList[cmailOldMove] != NULL &&
15919             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15920                    "Black offers a draw" : "White offers a draw")) {
15921             TruncateGame();
15922             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15923             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15924         } else {
15925             DisplayError(_("There is no pending offer on this move"), 0);
15926             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15927         }
15928     } else {
15929         /* Not used for offers from chess program */
15930     }
15931 }
15932
15933 void
15934 DeclineEvent ()
15935 {
15936     /* Decline a pending offer of any kind from opponent */
15937
15938     if (appData.icsActive) {
15939         SendToICS(ics_prefix);
15940         SendToICS("decline\n");
15941     } else if (cmailMsgLoaded) {
15942         if (currentMove == cmailOldMove &&
15943             commentList[cmailOldMove] != NULL &&
15944             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15945                    "Black offers a draw" : "White offers a draw")) {
15946 #ifdef NOTDEF
15947             AppendComment(cmailOldMove, "Draw declined", TRUE);
15948             DisplayComment(cmailOldMove - 1, "Draw declined");
15949 #endif /*NOTDEF*/
15950         } else {
15951             DisplayError(_("There is no pending offer on this move"), 0);
15952         }
15953     } else {
15954         /* Not used for offers from chess program */
15955     }
15956 }
15957
15958 void
15959 RematchEvent ()
15960 {
15961     /* Issue ICS rematch command */
15962     if (appData.icsActive) {
15963         SendToICS(ics_prefix);
15964         SendToICS("rematch\n");
15965     }
15966 }
15967
15968 void
15969 CallFlagEvent ()
15970 {
15971     /* Call your opponent's flag (claim a win on time) */
15972     if (appData.icsActive) {
15973         SendToICS(ics_prefix);
15974         SendToICS("flag\n");
15975     } else {
15976         switch (gameMode) {
15977           default:
15978             return;
15979           case MachinePlaysWhite:
15980             if (whiteFlag) {
15981                 if (blackFlag)
15982                   GameEnds(GameIsDrawn, "Both players ran out of time",
15983                            GE_PLAYER);
15984                 else
15985                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15986             } else {
15987                 DisplayError(_("Your opponent is not out of time"), 0);
15988             }
15989             break;
15990           case MachinePlaysBlack:
15991             if (blackFlag) {
15992                 if (whiteFlag)
15993                   GameEnds(GameIsDrawn, "Both players ran out of time",
15994                            GE_PLAYER);
15995                 else
15996                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15997             } else {
15998                 DisplayError(_("Your opponent is not out of time"), 0);
15999             }
16000             break;
16001         }
16002     }
16003 }
16004
16005 void
16006 ClockClick (int which)
16007 {       // [HGM] code moved to back-end from winboard.c
16008         if(which) { // black clock
16009           if (gameMode == EditPosition || gameMode == IcsExamining) {
16010             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
16011             SetBlackToPlayEvent();
16012           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
16013                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
16014           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
16015           } else if (shiftKey) {
16016             AdjustClock(which, -1);
16017           } else if (gameMode == IcsPlayingWhite ||
16018                      gameMode == MachinePlaysBlack) {
16019             CallFlagEvent();
16020           }
16021         } else { // white clock
16022           if (gameMode == EditPosition || gameMode == IcsExamining) {
16023             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
16024             SetWhiteToPlayEvent();
16025           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
16026                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
16027           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
16028           } else if (shiftKey) {
16029             AdjustClock(which, -1);
16030           } else if (gameMode == IcsPlayingBlack ||
16031                    gameMode == MachinePlaysWhite) {
16032             CallFlagEvent();
16033           }
16034         }
16035 }
16036
16037 void
16038 DrawEvent ()
16039 {
16040     /* Offer draw or accept pending draw offer from opponent */
16041
16042     if (appData.icsActive) {
16043         /* Note: tournament rules require draw offers to be
16044            made after you make your move but before you punch
16045            your clock.  Currently ICS doesn't let you do that;
16046            instead, you immediately punch your clock after making
16047            a move, but you can offer a draw at any time. */
16048
16049         SendToICS(ics_prefix);
16050         SendToICS("draw\n");
16051         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
16052     } else if (cmailMsgLoaded) {
16053         if (currentMove == cmailOldMove &&
16054             commentList[cmailOldMove] != NULL &&
16055             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
16056                    "Black offers a draw" : "White offers a draw")) {
16057             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
16058             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
16059         } else if (currentMove == cmailOldMove + 1) {
16060             char *offer = WhiteOnMove(cmailOldMove) ?
16061               "White offers a draw" : "Black offers a draw";
16062             AppendComment(currentMove, offer, TRUE);
16063             DisplayComment(currentMove - 1, offer);
16064             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
16065         } else {
16066             DisplayError(_("You must make your move before offering a draw"), 0);
16067             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
16068         }
16069     } else if (first.offeredDraw) {
16070         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
16071     } else {
16072         if (first.sendDrawOffers) {
16073             SendToProgram("draw\n", &first);
16074             userOfferedDraw = TRUE;
16075         }
16076     }
16077 }
16078
16079 void
16080 AdjournEvent ()
16081 {
16082     /* Offer Adjourn or accept pending Adjourn offer from opponent */
16083
16084     if (appData.icsActive) {
16085         SendToICS(ics_prefix);
16086         SendToICS("adjourn\n");
16087     } else {
16088         /* Currently GNU Chess doesn't offer or accept Adjourns */
16089     }
16090 }
16091
16092
16093 void
16094 AbortEvent ()
16095 {
16096     /* Offer Abort or accept pending Abort offer from opponent */
16097
16098     if (appData.icsActive) {
16099         SendToICS(ics_prefix);
16100         SendToICS("abort\n");
16101     } else {
16102         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
16103     }
16104 }
16105
16106 void
16107 ResignEvent ()
16108 {
16109     /* Resign.  You can do this even if it's not your turn. */
16110
16111     if (appData.icsActive) {
16112         SendToICS(ics_prefix);
16113         SendToICS("resign\n");
16114     } else {
16115         switch (gameMode) {
16116           case MachinePlaysWhite:
16117             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16118             break;
16119           case MachinePlaysBlack:
16120             GameEnds(BlackWins, "White resigns", GE_PLAYER);
16121             break;
16122           case EditGame:
16123             if (cmailMsgLoaded) {
16124                 TruncateGame();
16125                 if (WhiteOnMove(cmailOldMove)) {
16126                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
16127                 } else {
16128                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
16129                 }
16130                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16131             }
16132             break;
16133           default:
16134             break;
16135         }
16136     }
16137 }
16138
16139
16140 void
16141 StopObservingEvent ()
16142 {
16143     /* Stop observing current games */
16144     SendToICS(ics_prefix);
16145     SendToICS("unobserve\n");
16146 }
16147
16148 void
16149 StopExaminingEvent ()
16150 {
16151     /* Stop observing current game */
16152     SendToICS(ics_prefix);
16153     SendToICS("unexamine\n");
16154 }
16155
16156 void
16157 ForwardInner (int target)
16158 {
16159     int limit; int oldSeekGraphUp = seekGraphUp;
16160
16161     if (appData.debugMode)
16162         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16163                 target, currentMove, forwardMostMove);
16164
16165     if (gameMode == EditPosition)
16166       return;
16167
16168     seekGraphUp = FALSE;
16169     MarkTargetSquares(1);
16170     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16171
16172     if (gameMode == PlayFromGameFile && !pausing)
16173       PauseEvent();
16174
16175     if (gameMode == IcsExamining && pausing)
16176       limit = pauseExamForwardMostMove;
16177     else
16178       limit = forwardMostMove;
16179
16180     if (target > limit) target = limit;
16181
16182     if (target > 0 && moveList[target - 1][0]) {
16183         int fromX, fromY, toX, toY;
16184         toX = moveList[target - 1][2] - AAA;
16185         toY = moveList[target - 1][3] - ONE;
16186         if (moveList[target - 1][1] == '@') {
16187             if (appData.highlightLastMove) {
16188                 SetHighlights(-1, -1, toX, toY);
16189             }
16190         } else {
16191             fromX = moveList[target - 1][0] - AAA;
16192             fromY = moveList[target - 1][1] - ONE;
16193             if (target == currentMove + 1) {
16194                 if(moveList[target - 1][4] == ';') { // multi-leg
16195                     killX = moveList[target - 1][5] - AAA;
16196                     killY = moveList[target - 1][6] - ONE;
16197                 }
16198                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16199                 killX = killY = -1;
16200             }
16201             if (appData.highlightLastMove) {
16202                 SetHighlights(fromX, fromY, toX, toY);
16203             }
16204         }
16205     }
16206     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16207         gameMode == Training || gameMode == PlayFromGameFile ||
16208         gameMode == AnalyzeFile) {
16209         while (currentMove < target) {
16210             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16211             SendMoveToProgram(currentMove++, &first);
16212         }
16213     } else {
16214         currentMove = target;
16215     }
16216
16217     if (gameMode == EditGame || gameMode == EndOfGame) {
16218         whiteTimeRemaining = timeRemaining[0][currentMove];
16219         blackTimeRemaining = timeRemaining[1][currentMove];
16220     }
16221     DisplayBothClocks();
16222     DisplayMove(currentMove - 1);
16223     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16224     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16225     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16226         DisplayComment(currentMove - 1, commentList[currentMove]);
16227     }
16228     ClearMap(); // [HGM] exclude: invalidate map
16229 }
16230
16231
16232 void
16233 ForwardEvent ()
16234 {
16235     if (gameMode == IcsExamining && !pausing) {
16236         SendToICS(ics_prefix);
16237         SendToICS("forward\n");
16238     } else {
16239         ForwardInner(currentMove + 1);
16240     }
16241 }
16242
16243 void
16244 ToEndEvent ()
16245 {
16246     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16247         /* to optimze, we temporarily turn off analysis mode while we feed
16248          * the remaining moves to the engine. Otherwise we get analysis output
16249          * after each move.
16250          */
16251         if (first.analysisSupport) {
16252           SendToProgram("exit\nforce\n", &first);
16253           first.analyzing = FALSE;
16254         }
16255     }
16256
16257     if (gameMode == IcsExamining && !pausing) {
16258         SendToICS(ics_prefix);
16259         SendToICS("forward 999999\n");
16260     } else {
16261         ForwardInner(forwardMostMove);
16262     }
16263
16264     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16265         /* we have fed all the moves, so reactivate analysis mode */
16266         SendToProgram("analyze\n", &first);
16267         first.analyzing = TRUE;
16268         /*first.maybeThinking = TRUE;*/
16269         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16270     }
16271 }
16272
16273 void
16274 BackwardInner (int target)
16275 {
16276     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16277
16278     if (appData.debugMode)
16279         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16280                 target, currentMove, forwardMostMove);
16281
16282     if (gameMode == EditPosition) return;
16283     seekGraphUp = FALSE;
16284     MarkTargetSquares(1);
16285     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16286     if (currentMove <= backwardMostMove) {
16287         ClearHighlights();
16288         DrawPosition(full_redraw, boards[currentMove]);
16289         return;
16290     }
16291     if (gameMode == PlayFromGameFile && !pausing)
16292       PauseEvent();
16293
16294     if (moveList[target][0]) {
16295         int fromX, fromY, toX, toY;
16296         toX = moveList[target][2] - AAA;
16297         toY = moveList[target][3] - ONE;
16298         if (moveList[target][1] == '@') {
16299             if (appData.highlightLastMove) {
16300                 SetHighlights(-1, -1, toX, toY);
16301             }
16302         } else {
16303             fromX = moveList[target][0] - AAA;
16304             fromY = moveList[target][1] - ONE;
16305             if (target == currentMove - 1) {
16306                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16307             }
16308             if (appData.highlightLastMove) {
16309                 SetHighlights(fromX, fromY, toX, toY);
16310             }
16311         }
16312     }
16313     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16314         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16315         while (currentMove > target) {
16316             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16317                 // null move cannot be undone. Reload program with move history before it.
16318                 int i;
16319                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16320                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16321                 }
16322                 SendBoard(&first, i);
16323               if(second.analyzing) SendBoard(&second, i);
16324                 for(currentMove=i; currentMove<target; currentMove++) {
16325                     SendMoveToProgram(currentMove, &first);
16326                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16327                 }
16328                 break;
16329             }
16330             SendToBoth("undo\n");
16331             currentMove--;
16332         }
16333     } else {
16334         currentMove = target;
16335     }
16336
16337     if (gameMode == EditGame || gameMode == EndOfGame) {
16338         whiteTimeRemaining = timeRemaining[0][currentMove];
16339         blackTimeRemaining = timeRemaining[1][currentMove];
16340     }
16341     DisplayBothClocks();
16342     DisplayMove(currentMove - 1);
16343     DrawPosition(full_redraw, boards[currentMove]);
16344     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16345     // [HGM] PV info: routine tests if comment empty
16346     DisplayComment(currentMove - 1, commentList[currentMove]);
16347     ClearMap(); // [HGM] exclude: invalidate map
16348 }
16349
16350 void
16351 BackwardEvent ()
16352 {
16353     if (gameMode == IcsExamining && !pausing) {
16354         SendToICS(ics_prefix);
16355         SendToICS("backward\n");
16356     } else {
16357         BackwardInner(currentMove - 1);
16358     }
16359 }
16360
16361 void
16362 ToStartEvent ()
16363 {
16364     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16365         /* to optimize, we temporarily turn off analysis mode while we undo
16366          * all the moves. Otherwise we get analysis output after each undo.
16367          */
16368         if (first.analysisSupport) {
16369           SendToProgram("exit\nforce\n", &first);
16370           first.analyzing = FALSE;
16371         }
16372     }
16373
16374     if (gameMode == IcsExamining && !pausing) {
16375         SendToICS(ics_prefix);
16376         SendToICS("backward 999999\n");
16377     } else {
16378         BackwardInner(backwardMostMove);
16379     }
16380
16381     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16382         /* we have fed all the moves, so reactivate analysis mode */
16383         SendToProgram("analyze\n", &first);
16384         first.analyzing = TRUE;
16385         /*first.maybeThinking = TRUE;*/
16386         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16387     }
16388 }
16389
16390 void
16391 ToNrEvent (int to)
16392 {
16393   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16394   if (to >= forwardMostMove) to = forwardMostMove;
16395   if (to <= backwardMostMove) to = backwardMostMove;
16396   if (to < currentMove) {
16397     BackwardInner(to);
16398   } else {
16399     ForwardInner(to);
16400   }
16401 }
16402
16403 void
16404 RevertEvent (Boolean annotate)
16405 {
16406     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16407         return;
16408     }
16409     if (gameMode != IcsExamining) {
16410         DisplayError(_("You are not examining a game"), 0);
16411         return;
16412     }
16413     if (pausing) {
16414         DisplayError(_("You can't revert while pausing"), 0);
16415         return;
16416     }
16417     SendToICS(ics_prefix);
16418     SendToICS("revert\n");
16419 }
16420
16421 void
16422 RetractMoveEvent ()
16423 {
16424     switch (gameMode) {
16425       case MachinePlaysWhite:
16426       case MachinePlaysBlack:
16427         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16428             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16429             return;
16430         }
16431         if (forwardMostMove < 2) return;
16432         currentMove = forwardMostMove = forwardMostMove - 2;
16433         whiteTimeRemaining = timeRemaining[0][currentMove];
16434         blackTimeRemaining = timeRemaining[1][currentMove];
16435         DisplayBothClocks();
16436         DisplayMove(currentMove - 1);
16437         ClearHighlights();/*!! could figure this out*/
16438         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16439         SendToProgram("remove\n", &first);
16440         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16441         break;
16442
16443       case BeginningOfGame:
16444       default:
16445         break;
16446
16447       case IcsPlayingWhite:
16448       case IcsPlayingBlack:
16449         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16450             SendToICS(ics_prefix);
16451             SendToICS("takeback 2\n");
16452         } else {
16453             SendToICS(ics_prefix);
16454             SendToICS("takeback 1\n");
16455         }
16456         break;
16457     }
16458 }
16459
16460 void
16461 MoveNowEvent ()
16462 {
16463     ChessProgramState *cps;
16464
16465     switch (gameMode) {
16466       case MachinePlaysWhite:
16467         if (!WhiteOnMove(forwardMostMove)) {
16468             DisplayError(_("It is your turn"), 0);
16469             return;
16470         }
16471         cps = &first;
16472         break;
16473       case MachinePlaysBlack:
16474         if (WhiteOnMove(forwardMostMove)) {
16475             DisplayError(_("It is your turn"), 0);
16476             return;
16477         }
16478         cps = &first;
16479         break;
16480       case TwoMachinesPlay:
16481         if (WhiteOnMove(forwardMostMove) ==
16482             (first.twoMachinesColor[0] == 'w')) {
16483             cps = &first;
16484         } else {
16485             cps = &second;
16486         }
16487         break;
16488       case BeginningOfGame:
16489       default:
16490         return;
16491     }
16492     SendToProgram("?\n", cps);
16493 }
16494
16495 void
16496 TruncateGameEvent ()
16497 {
16498     EditGameEvent();
16499     if (gameMode != EditGame) return;
16500     TruncateGame();
16501 }
16502
16503 void
16504 TruncateGame ()
16505 {
16506     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16507     if (forwardMostMove > currentMove) {
16508         if (gameInfo.resultDetails != NULL) {
16509             free(gameInfo.resultDetails);
16510             gameInfo.resultDetails = NULL;
16511             gameInfo.result = GameUnfinished;
16512         }
16513         forwardMostMove = currentMove;
16514         HistorySet(parseList, backwardMostMove, forwardMostMove,
16515                    currentMove-1);
16516     }
16517 }
16518
16519 void
16520 HintEvent ()
16521 {
16522     if (appData.noChessProgram) return;
16523     switch (gameMode) {
16524       case MachinePlaysWhite:
16525         if (WhiteOnMove(forwardMostMove)) {
16526             DisplayError(_("Wait until your turn."), 0);
16527             return;
16528         }
16529         break;
16530       case BeginningOfGame:
16531       case MachinePlaysBlack:
16532         if (!WhiteOnMove(forwardMostMove)) {
16533             DisplayError(_("Wait until your turn."), 0);
16534             return;
16535         }
16536         break;
16537       default:
16538         DisplayError(_("No hint available"), 0);
16539         return;
16540     }
16541     SendToProgram("hint\n", &first);
16542     hintRequested = TRUE;
16543 }
16544
16545 int
16546 SaveSelected (FILE *g, int dummy, char *dummy2)
16547 {
16548     ListGame * lg = (ListGame *) gameList.head;
16549     int nItem, cnt=0;
16550     FILE *f;
16551
16552     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16553         DisplayError(_("Game list not loaded or empty"), 0);
16554         return 0;
16555     }
16556
16557     creatingBook = TRUE; // suppresses stuff during load game
16558
16559     /* Get list size */
16560     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16561         if(lg->position >= 0) { // selected?
16562             LoadGame(f, nItem, "", TRUE);
16563             SaveGamePGN2(g); // leaves g open
16564             cnt++; DoEvents();
16565         }
16566         lg = (ListGame *) lg->node.succ;
16567     }
16568
16569     fclose(g);
16570     creatingBook = FALSE;
16571
16572     return cnt;
16573 }
16574
16575 void
16576 CreateBookEvent ()
16577 {
16578     ListGame * lg = (ListGame *) gameList.head;
16579     FILE *f, *g;
16580     int nItem;
16581     static int secondTime = FALSE;
16582
16583     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16584         DisplayError(_("Game list not loaded or empty"), 0);
16585         return;
16586     }
16587
16588     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16589         fclose(g);
16590         secondTime++;
16591         DisplayNote(_("Book file exists! Try again for overwrite."));
16592         return;
16593     }
16594
16595     creatingBook = TRUE;
16596     secondTime = FALSE;
16597
16598     /* Get list size */
16599     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16600         if(lg->position >= 0) {
16601             LoadGame(f, nItem, "", TRUE);
16602             AddGameToBook(TRUE);
16603             DoEvents();
16604         }
16605         lg = (ListGame *) lg->node.succ;
16606     }
16607
16608     creatingBook = FALSE;
16609     FlushBook();
16610 }
16611
16612 void
16613 BookEvent ()
16614 {
16615     if (appData.noChessProgram) return;
16616     switch (gameMode) {
16617       case MachinePlaysWhite:
16618         if (WhiteOnMove(forwardMostMove)) {
16619             DisplayError(_("Wait until your turn."), 0);
16620             return;
16621         }
16622         break;
16623       case BeginningOfGame:
16624       case MachinePlaysBlack:
16625         if (!WhiteOnMove(forwardMostMove)) {
16626             DisplayError(_("Wait until your turn."), 0);
16627             return;
16628         }
16629         break;
16630       case EditPosition:
16631         EditPositionDone(TRUE);
16632         break;
16633       case TwoMachinesPlay:
16634         return;
16635       default:
16636         break;
16637     }
16638     SendToProgram("bk\n", &first);
16639     bookOutput[0] = NULLCHAR;
16640     bookRequested = TRUE;
16641 }
16642
16643 void
16644 AboutGameEvent ()
16645 {
16646     char *tags = PGNTags(&gameInfo);
16647     TagsPopUp(tags, CmailMsg());
16648     free(tags);
16649 }
16650
16651 /* end button procedures */
16652
16653 void
16654 PrintPosition (FILE *fp, int move)
16655 {
16656     int i, j;
16657
16658     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16659         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16660             char c = PieceToChar(boards[move][i][j]);
16661             fputc(c == '?' ? '.' : c, fp);
16662             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16663         }
16664     }
16665     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16666       fprintf(fp, "white to play\n");
16667     else
16668       fprintf(fp, "black to play\n");
16669 }
16670
16671 void
16672 PrintOpponents (FILE *fp)
16673 {
16674     if (gameInfo.white != NULL) {
16675         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16676     } else {
16677         fprintf(fp, "\n");
16678     }
16679 }
16680
16681 /* Find last component of program's own name, using some heuristics */
16682 void
16683 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16684 {
16685     char *p, *q, c;
16686     int local = (strcmp(host, "localhost") == 0);
16687     while (!local && (p = strchr(prog, ';')) != NULL) {
16688         p++;
16689         while (*p == ' ') p++;
16690         prog = p;
16691     }
16692     if (*prog == '"' || *prog == '\'') {
16693         q = strchr(prog + 1, *prog);
16694     } else {
16695         q = strchr(prog, ' ');
16696     }
16697     if (q == NULL) q = prog + strlen(prog);
16698     p = q;
16699     while (p >= prog && *p != '/' && *p != '\\') p--;
16700     p++;
16701     if(p == prog && *p == '"') p++;
16702     c = *q; *q = 0;
16703     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16704     memcpy(buf, p, q - p);
16705     buf[q - p] = NULLCHAR;
16706     if (!local) {
16707         strcat(buf, "@");
16708         strcat(buf, host);
16709     }
16710 }
16711
16712 char *
16713 TimeControlTagValue ()
16714 {
16715     char buf[MSG_SIZ];
16716     if (!appData.clockMode) {
16717       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16718     } else if (movesPerSession > 0) {
16719       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16720     } else if (timeIncrement == 0) {
16721       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16722     } else {
16723       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16724     }
16725     return StrSave(buf);
16726 }
16727
16728 void
16729 SetGameInfo ()
16730 {
16731     /* This routine is used only for certain modes */
16732     VariantClass v = gameInfo.variant;
16733     ChessMove r = GameUnfinished;
16734     char *p = NULL;
16735
16736     if(keepInfo) return;
16737
16738     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16739         r = gameInfo.result;
16740         p = gameInfo.resultDetails;
16741         gameInfo.resultDetails = NULL;
16742     }
16743     ClearGameInfo(&gameInfo);
16744     gameInfo.variant = v;
16745
16746     switch (gameMode) {
16747       case MachinePlaysWhite:
16748         gameInfo.event = StrSave( appData.pgnEventHeader );
16749         gameInfo.site = StrSave(HostName());
16750         gameInfo.date = PGNDate();
16751         gameInfo.round = StrSave("-");
16752         gameInfo.white = StrSave(first.tidy);
16753         gameInfo.black = StrSave(UserName());
16754         gameInfo.timeControl = TimeControlTagValue();
16755         break;
16756
16757       case MachinePlaysBlack:
16758         gameInfo.event = StrSave( appData.pgnEventHeader );
16759         gameInfo.site = StrSave(HostName());
16760         gameInfo.date = PGNDate();
16761         gameInfo.round = StrSave("-");
16762         gameInfo.white = StrSave(UserName());
16763         gameInfo.black = StrSave(first.tidy);
16764         gameInfo.timeControl = TimeControlTagValue();
16765         break;
16766
16767       case TwoMachinesPlay:
16768         gameInfo.event = StrSave( appData.pgnEventHeader );
16769         gameInfo.site = StrSave(HostName());
16770         gameInfo.date = PGNDate();
16771         if (roundNr > 0) {
16772             char buf[MSG_SIZ];
16773             snprintf(buf, MSG_SIZ, "%d", roundNr);
16774             gameInfo.round = StrSave(buf);
16775         } else {
16776             gameInfo.round = StrSave("-");
16777         }
16778         if (first.twoMachinesColor[0] == 'w') {
16779             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16780             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16781         } else {
16782             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16783             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16784         }
16785         gameInfo.timeControl = TimeControlTagValue();
16786         break;
16787
16788       case EditGame:
16789         gameInfo.event = StrSave("Edited game");
16790         gameInfo.site = StrSave(HostName());
16791         gameInfo.date = PGNDate();
16792         gameInfo.round = StrSave("-");
16793         gameInfo.white = StrSave("-");
16794         gameInfo.black = StrSave("-");
16795         gameInfo.result = r;
16796         gameInfo.resultDetails = p;
16797         break;
16798
16799       case EditPosition:
16800         gameInfo.event = StrSave("Edited position");
16801         gameInfo.site = StrSave(HostName());
16802         gameInfo.date = PGNDate();
16803         gameInfo.round = StrSave("-");
16804         gameInfo.white = StrSave("-");
16805         gameInfo.black = StrSave("-");
16806         break;
16807
16808       case IcsPlayingWhite:
16809       case IcsPlayingBlack:
16810       case IcsObserving:
16811       case IcsExamining:
16812         break;
16813
16814       case PlayFromGameFile:
16815         gameInfo.event = StrSave("Game from non-PGN file");
16816         gameInfo.site = StrSave(HostName());
16817         gameInfo.date = PGNDate();
16818         gameInfo.round = StrSave("-");
16819         gameInfo.white = StrSave("?");
16820         gameInfo.black = StrSave("?");
16821         break;
16822
16823       default:
16824         break;
16825     }
16826 }
16827
16828 void
16829 ReplaceComment (int index, char *text)
16830 {
16831     int len;
16832     char *p;
16833     float score;
16834
16835     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16836        pvInfoList[index-1].depth == len &&
16837        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16838        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16839     while (*text == '\n') text++;
16840     len = strlen(text);
16841     while (len > 0 && text[len - 1] == '\n') len--;
16842
16843     if (commentList[index] != NULL)
16844       free(commentList[index]);
16845
16846     if (len == 0) {
16847         commentList[index] = NULL;
16848         return;
16849     }
16850   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16851       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16852       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16853     commentList[index] = (char *) malloc(len + 2);
16854     strncpy(commentList[index], text, len);
16855     commentList[index][len] = '\n';
16856     commentList[index][len + 1] = NULLCHAR;
16857   } else {
16858     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16859     char *p;
16860     commentList[index] = (char *) malloc(len + 7);
16861     safeStrCpy(commentList[index], "{\n", 3);
16862     safeStrCpy(commentList[index]+2, text, len+1);
16863     commentList[index][len+2] = NULLCHAR;
16864     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16865     strcat(commentList[index], "\n}\n");
16866   }
16867 }
16868
16869 void
16870 CrushCRs (char *text)
16871 {
16872   char *p = text;
16873   char *q = text;
16874   char ch;
16875
16876   do {
16877     ch = *p++;
16878     if (ch == '\r') continue;
16879     *q++ = ch;
16880   } while (ch != '\0');
16881 }
16882
16883 void
16884 AppendComment (int index, char *text, Boolean addBraces)
16885 /* addBraces  tells if we should add {} */
16886 {
16887     int oldlen, len;
16888     char *old;
16889
16890 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16891     if(addBraces == 3) addBraces = 0; else // force appending literally
16892     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16893
16894     CrushCRs(text);
16895     while (*text == '\n') text++;
16896     len = strlen(text);
16897     while (len > 0 && text[len - 1] == '\n') len--;
16898     text[len] = NULLCHAR;
16899
16900     if (len == 0) return;
16901
16902     if (commentList[index] != NULL) {
16903       Boolean addClosingBrace = addBraces;
16904         old = commentList[index];
16905         oldlen = strlen(old);
16906         while(commentList[index][oldlen-1] ==  '\n')
16907           commentList[index][--oldlen] = NULLCHAR;
16908         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16909         safeStrCpy(commentList[index], old, oldlen + len + 6);
16910         free(old);
16911         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16912         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16913           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16914           while (*text == '\n') { text++; len--; }
16915           commentList[index][--oldlen] = NULLCHAR;
16916       }
16917         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16918         else          strcat(commentList[index], "\n");
16919         strcat(commentList[index], text);
16920         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16921         else          strcat(commentList[index], "\n");
16922     } else {
16923         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16924         if(addBraces)
16925           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16926         else commentList[index][0] = NULLCHAR;
16927         strcat(commentList[index], text);
16928         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16929         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16930     }
16931 }
16932
16933 static char *
16934 FindStr (char * text, char * sub_text)
16935 {
16936     char * result = strstr( text, sub_text );
16937
16938     if( result != NULL ) {
16939         result += strlen( sub_text );
16940     }
16941
16942     return result;
16943 }
16944
16945 /* [AS] Try to extract PV info from PGN comment */
16946 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16947 char *
16948 GetInfoFromComment (int index, char * text)
16949 {
16950     char * sep = text, *p;
16951
16952     if( text != NULL && index > 0 ) {
16953         int score = 0;
16954         int depth = 0;
16955         int time = -1, sec = 0, deci;
16956         char * s_eval = FindStr( text, "[%eval " );
16957         char * s_emt = FindStr( text, "[%emt " );
16958 #if 0
16959         if( s_eval != NULL || s_emt != NULL ) {
16960 #else
16961         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16962 #endif
16963             /* New style */
16964             char delim;
16965
16966             if( s_eval != NULL ) {
16967                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16968                     return text;
16969                 }
16970
16971                 if( delim != ']' ) {
16972                     return text;
16973                 }
16974             }
16975
16976             if( s_emt != NULL ) {
16977             }
16978                 return text;
16979         }
16980         else {
16981             /* We expect something like: [+|-]nnn.nn/dd */
16982             int score_lo = 0;
16983
16984             if(*text != '{') return text; // [HGM] braces: must be normal comment
16985
16986             sep = strchr( text, '/' );
16987             if( sep == NULL || sep < (text+4) ) {
16988                 return text;
16989             }
16990
16991             p = text;
16992             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16993             if(p[1] == '(') { // comment starts with PV
16994                p = strchr(p, ')'); // locate end of PV
16995                if(p == NULL || sep < p+5) return text;
16996                // at this point we have something like "{(.*) +0.23/6 ..."
16997                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16998                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16999                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
17000             }
17001             time = -1; sec = -1; deci = -1;
17002             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
17003                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
17004                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
17005                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
17006                 return text;
17007             }
17008
17009             if( score_lo < 0 || score_lo >= 100 ) {
17010                 return text;
17011             }
17012
17013             if(sec >= 0) time = 600*time + 10*sec; else
17014             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
17015
17016             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
17017
17018             /* [HGM] PV time: now locate end of PV info */
17019             while( *++sep >= '0' && *sep <= '9'); // strip depth
17020             if(time >= 0)
17021             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
17022             if(sec >= 0)
17023             while( *++sep >= '0' && *sep <= '9'); // strip seconds
17024             if(deci >= 0)
17025             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
17026             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
17027         }
17028
17029         if( depth <= 0 ) {
17030             return text;
17031         }
17032
17033         if( time < 0 ) {
17034             time = -1;
17035         }
17036
17037         pvInfoList[index-1].depth = depth;
17038         pvInfoList[index-1].score = score;
17039         pvInfoList[index-1].time  = 10*time; // centi-sec
17040         if(*sep == '}') *sep = 0; else *--sep = '{';
17041         if(p != text) {
17042             while(*p++ = *sep++)
17043                                 ;
17044             sep = text;
17045         } // squeeze out space between PV and comment, and return both
17046     }
17047     return sep;
17048 }
17049
17050 void
17051 SendToProgram (char *message, ChessProgramState *cps)
17052 {
17053     int count, outCount, error;
17054     char buf[MSG_SIZ];
17055
17056     if (cps->pr == NoProc) return;
17057     Attention(cps);
17058
17059     if (appData.debugMode) {
17060         TimeMark now;
17061         GetTimeMark(&now);
17062         fprintf(debugFP, "%ld >%-6s: %s",
17063                 SubtractTimeMarks(&now, &programStartTime),
17064                 cps->which, message);
17065         if(serverFP)
17066             fprintf(serverFP, "%ld >%-6s: %s",
17067                 SubtractTimeMarks(&now, &programStartTime),
17068                 cps->which, message), fflush(serverFP);
17069     }
17070
17071     count = strlen(message);
17072     outCount = OutputToProcess(cps->pr, message, count, &error);
17073     if (outCount < count && !exiting
17074                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
17075       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
17076       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
17077         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17078             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17079                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17080                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17081                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17082             } else {
17083                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17084                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17085                 gameInfo.result = res;
17086             }
17087             gameInfo.resultDetails = StrSave(buf);
17088         }
17089         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17090         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17091     }
17092 }
17093
17094 void
17095 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
17096 {
17097     char *end_str;
17098     char buf[MSG_SIZ];
17099     ChessProgramState *cps = (ChessProgramState *)closure;
17100
17101     if (isr != cps->isr) return; /* Killed intentionally */
17102     if (count <= 0) {
17103         if (count == 0) {
17104             RemoveInputSource(cps->isr);
17105             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
17106                     _(cps->which), cps->program);
17107             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
17108             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
17109                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
17110                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
17111                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
17112                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
17113                 } else {
17114                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
17115                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
17116                     gameInfo.result = res;
17117                 }
17118                 gameInfo.resultDetails = StrSave(buf);
17119             }
17120             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
17121             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
17122         } else {
17123             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
17124                     _(cps->which), cps->program);
17125             RemoveInputSource(cps->isr);
17126
17127             /* [AS] Program is misbehaving badly... kill it */
17128             if( count == -2 ) {
17129                 DestroyChildProcess( cps->pr, 9 );
17130                 cps->pr = NoProc;
17131             }
17132
17133             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17134         }
17135         return;
17136     }
17137
17138     if ((end_str = strchr(message, '\r')) != NULL)
17139       *end_str = NULLCHAR;
17140     if ((end_str = strchr(message, '\n')) != NULL)
17141       *end_str = NULLCHAR;
17142
17143     if (appData.debugMode) {
17144         TimeMark now; int print = 1;
17145         char *quote = ""; char c; int i;
17146
17147         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17148                 char start = message[0];
17149                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17150                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17151                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
17152                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17153                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17154                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
17155                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17156                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
17157                    sscanf(message, "hint: %c", &c)!=1 &&
17158                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
17159                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17160                     print = (appData.engineComments >= 2);
17161                 }
17162                 message[0] = start; // restore original message
17163         }
17164         if(print) {
17165                 GetTimeMark(&now);
17166                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17167                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17168                         quote,
17169                         message);
17170                 if(serverFP)
17171                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
17172                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17173                         quote,
17174                         message), fflush(serverFP);
17175         }
17176     }
17177
17178     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17179     if (appData.icsEngineAnalyze) {
17180         if (strstr(message, "whisper") != NULL ||
17181              strstr(message, "kibitz") != NULL ||
17182             strstr(message, "tellics") != NULL) return;
17183     }
17184
17185     HandleMachineMove(message, cps);
17186 }
17187
17188
17189 void
17190 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17191 {
17192     char buf[MSG_SIZ];
17193     int seconds;
17194
17195     if( timeControl_2 > 0 ) {
17196         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17197             tc = timeControl_2;
17198         }
17199     }
17200     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17201     inc /= cps->timeOdds;
17202     st  /= cps->timeOdds;
17203
17204     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17205
17206     if (st > 0) {
17207       /* Set exact time per move, normally using st command */
17208       if (cps->stKludge) {
17209         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17210         seconds = st % 60;
17211         if (seconds == 0) {
17212           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17213         } else {
17214           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17215         }
17216       } else {
17217         snprintf(buf, MSG_SIZ, "st %d\n", st);
17218       }
17219     } else {
17220       /* Set conventional or incremental time control, using level command */
17221       if (seconds == 0) {
17222         /* Note old gnuchess bug -- minutes:seconds used to not work.
17223            Fixed in later versions, but still avoid :seconds
17224            when seconds is 0. */
17225         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17226       } else {
17227         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17228                  seconds, inc/1000.);
17229       }
17230     }
17231     SendToProgram(buf, cps);
17232
17233     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17234     /* Orthogonally, limit search to given depth */
17235     if (sd > 0) {
17236       if (cps->sdKludge) {
17237         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17238       } else {
17239         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17240       }
17241       SendToProgram(buf, cps);
17242     }
17243
17244     if(cps->nps >= 0) { /* [HGM] nps */
17245         if(cps->supportsNPS == FALSE)
17246           cps->nps = -1; // don't use if engine explicitly says not supported!
17247         else {
17248           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17249           SendToProgram(buf, cps);
17250         }
17251     }
17252 }
17253
17254 ChessProgramState *
17255 WhitePlayer ()
17256 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17257 {
17258     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17259        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17260         return &second;
17261     return &first;
17262 }
17263
17264 void
17265 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17266 {
17267     char message[MSG_SIZ];
17268     long time, otime;
17269
17270     /* Note: this routine must be called when the clocks are stopped
17271        or when they have *just* been set or switched; otherwise
17272        it will be off by the time since the current tick started.
17273     */
17274     if (machineWhite) {
17275         time = whiteTimeRemaining / 10;
17276         otime = blackTimeRemaining / 10;
17277     } else {
17278         time = blackTimeRemaining / 10;
17279         otime = whiteTimeRemaining / 10;
17280     }
17281     /* [HGM] translate opponent's time by time-odds factor */
17282     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17283
17284     if (time <= 0) time = 1;
17285     if (otime <= 0) otime = 1;
17286
17287     snprintf(message, MSG_SIZ, "time %ld\n", time);
17288     SendToProgram(message, cps);
17289
17290     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17291     SendToProgram(message, cps);
17292 }
17293
17294 char *
17295 EngineDefinedVariant (ChessProgramState *cps, int n)
17296 {   // return name of n-th unknown variant that engine supports
17297     static char buf[MSG_SIZ];
17298     char *p, *s = cps->variants;
17299     if(!s) return NULL;
17300     do { // parse string from variants feature
17301       VariantClass v;
17302         p = strchr(s, ',');
17303         if(p) *p = NULLCHAR;
17304       v = StringToVariant(s);
17305       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17306         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17307             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17308                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17309                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17310                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17311             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17312         }
17313         if(p) *p++ = ',';
17314         if(n < 0) return buf;
17315     } while(s = p);
17316     return NULL;
17317 }
17318
17319 int
17320 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17321 {
17322   char buf[MSG_SIZ];
17323   int len = strlen(name);
17324   int val;
17325
17326   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17327     (*p) += len + 1;
17328     sscanf(*p, "%d", &val);
17329     *loc = (val != 0);
17330     while (**p && **p != ' ')
17331       (*p)++;
17332     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17333     SendToProgram(buf, cps);
17334     return TRUE;
17335   }
17336   return FALSE;
17337 }
17338
17339 int
17340 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17341 {
17342   char buf[MSG_SIZ];
17343   int len = strlen(name);
17344   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17345     (*p) += len + 1;
17346     sscanf(*p, "%d", loc);
17347     while (**p && **p != ' ') (*p)++;
17348     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17349     SendToProgram(buf, cps);
17350     return TRUE;
17351   }
17352   return FALSE;
17353 }
17354
17355 int
17356 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17357 {
17358   char buf[MSG_SIZ];
17359   int len = strlen(name);
17360   if (strncmp((*p), name, len) == 0
17361       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17362     (*p) += len + 2;
17363     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
17364     FREE(*loc); *loc = malloc(len);
17365     strncpy(*loc, *p, len);
17366     sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17367     while (**p && **p != '\"') (*p)++;
17368     if (**p == '\"') (*p)++;
17369     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17370     SendToProgram(buf, cps);
17371     return TRUE;
17372   }
17373   return FALSE;
17374 }
17375
17376 int
17377 ParseOption (Option *opt, ChessProgramState *cps)
17378 // [HGM] options: process the string that defines an engine option, and determine
17379 // name, type, default value, and allowed value range
17380 {
17381         char *p, *q, buf[MSG_SIZ];
17382         int n, min = (-1)<<31, max = 1<<31, def;
17383
17384         opt->target = &opt->value;   // OK for spin/slider and checkbox
17385         if(p = strstr(opt->name, " -spin ")) {
17386             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17387             if(max < min) max = min; // enforce consistency
17388             if(def < min) def = min;
17389             if(def > max) def = max;
17390             opt->value = def;
17391             opt->min = min;
17392             opt->max = max;
17393             opt->type = Spin;
17394         } else if((p = strstr(opt->name, " -slider "))) {
17395             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17396             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17397             if(max < min) max = min; // enforce consistency
17398             if(def < min) def = min;
17399             if(def > max) def = max;
17400             opt->value = def;
17401             opt->min = min;
17402             opt->max = max;
17403             opt->type = Spin; // Slider;
17404         } else if((p = strstr(opt->name, " -string "))) {
17405             opt->textValue = p+9;
17406             opt->type = TextBox;
17407             opt->target = &opt->textValue;
17408         } else if((p = strstr(opt->name, " -file "))) {
17409             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17410             opt->target = opt->textValue = p+7;
17411             opt->type = FileName; // FileName;
17412             opt->target = &opt->textValue;
17413         } else if((p = strstr(opt->name, " -path "))) {
17414             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17415             opt->target = opt->textValue = p+7;
17416             opt->type = PathName; // PathName;
17417             opt->target = &opt->textValue;
17418         } else if(p = strstr(opt->name, " -check ")) {
17419             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17420             opt->value = (def != 0);
17421             opt->type = CheckBox;
17422         } else if(p = strstr(opt->name, " -combo ")) {
17423             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17424             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17425             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17426             opt->value = n = 0;
17427             while(q = StrStr(q, " /// ")) {
17428                 n++; *q = 0;    // count choices, and null-terminate each of them
17429                 q += 5;
17430                 if(*q == '*') { // remember default, which is marked with * prefix
17431                     q++;
17432                     opt->value = n;
17433                 }
17434                 cps->comboList[cps->comboCnt++] = q;
17435             }
17436             cps->comboList[cps->comboCnt++] = NULL;
17437             opt->max = n + 1;
17438             opt->type = ComboBox;
17439         } else if(p = strstr(opt->name, " -button")) {
17440             opt->type = Button;
17441         } else if(p = strstr(opt->name, " -save")) {
17442             opt->type = SaveButton;
17443         } else return FALSE;
17444         *p = 0; // terminate option name
17445         *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17446         if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17447         // now look if the command-line options define a setting for this engine option.
17448         if(cps->optionSettings && cps->optionSettings[0])
17449             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17450         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17451           snprintf(buf, MSG_SIZ, "option %s", p);
17452                 if(p = strstr(buf, ",")) *p = 0;
17453                 if(q = strchr(buf, '=')) switch(opt->type) {
17454                     case ComboBox:
17455                         for(n=0; n<opt->max; n++)
17456                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17457                         break;
17458                     case TextBox:
17459                     case FileName:
17460                     case PathName:
17461                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17462                         break;
17463                     case Spin:
17464                     case CheckBox:
17465                         opt->value = atoi(q+1);
17466                     default:
17467                         break;
17468                 }
17469                 strcat(buf, "\n");
17470                 SendToProgram(buf, cps);
17471         }
17472         return TRUE;
17473 }
17474
17475 void
17476 FeatureDone (ChessProgramState *cps, int val)
17477 {
17478   DelayedEventCallback cb = GetDelayedEvent();
17479   if ((cb == InitBackEnd3 && cps == &first) ||
17480       (cb == SettingsMenuIfReady && cps == &second) ||
17481       (cb == LoadEngine) || (cb == StartSecond) ||
17482       (cb == TwoMachinesEventIfReady)) {
17483     CancelDelayedEvent();
17484     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17485   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17486   cps->initDone = val;
17487   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17488 }
17489
17490 /* Parse feature command from engine */
17491 void
17492 ParseFeatures (char *args, ChessProgramState *cps)
17493 {
17494   char *p = args;
17495   char *q = NULL;
17496   int val;
17497   char buf[MSG_SIZ];
17498
17499   for (;;) {
17500     while (*p == ' ') p++;
17501     if (*p == NULLCHAR) return;
17502
17503     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17504     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17505     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17506     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17507     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17508     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17509     if (BoolFeature(&p, "reuse", &val, cps)) {
17510       /* Engine can disable reuse, but can't enable it if user said no */
17511       if (!val) cps->reuse = FALSE;
17512       continue;
17513     }
17514     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17515     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17516       if (gameMode == TwoMachinesPlay) {
17517         DisplayTwoMachinesTitle();
17518       } else {
17519         DisplayTitle("");
17520       }
17521       continue;
17522     }
17523     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17524     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17525     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17526     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17527     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17528     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17529     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17530     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17531     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17532     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17533     if (IntFeature(&p, "done", &val, cps)) {
17534       FeatureDone(cps, val);
17535       continue;
17536     }
17537     /* Added by Tord: */
17538     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17539     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17540     /* End of additions by Tord */
17541
17542     /* [HGM] added features: */
17543     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17544     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17545     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17546     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17547     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17548     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17549     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17550     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17551         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17552         if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17553         FREE(cps->option[cps->nrOptions].name);
17554         cps->option[cps->nrOptions].name = q; q = NULL;
17555         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17556           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17557             SendToProgram(buf, cps);
17558             continue;
17559         }
17560         if(cps->nrOptions >= MAX_OPTIONS) {
17561             cps->nrOptions--;
17562             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17563             DisplayError(buf, 0);
17564         }
17565         continue;
17566     }
17567     /* End of additions by HGM */
17568
17569     /* unknown feature: complain and skip */
17570     q = p;
17571     while (*q && *q != '=') q++;
17572     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17573     SendToProgram(buf, cps);
17574     p = q;
17575     if (*p == '=') {
17576       p++;
17577       if (*p == '\"') {
17578         p++;
17579         while (*p && *p != '\"') p++;
17580         if (*p == '\"') p++;
17581       } else {
17582         while (*p && *p != ' ') p++;
17583       }
17584     }
17585   }
17586
17587 }
17588
17589 void
17590 PeriodicUpdatesEvent (int newState)
17591 {
17592     if (newState == appData.periodicUpdates)
17593       return;
17594
17595     appData.periodicUpdates=newState;
17596
17597     /* Display type changes, so update it now */
17598 //    DisplayAnalysis();
17599
17600     /* Get the ball rolling again... */
17601     if (newState) {
17602         AnalysisPeriodicEvent(1);
17603         StartAnalysisClock();
17604     }
17605 }
17606
17607 void
17608 PonderNextMoveEvent (int newState)
17609 {
17610     if (newState == appData.ponderNextMove) return;
17611     if (gameMode == EditPosition) EditPositionDone(TRUE);
17612     if (newState) {
17613         SendToProgram("hard\n", &first);
17614         if (gameMode == TwoMachinesPlay) {
17615             SendToProgram("hard\n", &second);
17616         }
17617     } else {
17618         SendToProgram("easy\n", &first);
17619         thinkOutput[0] = NULLCHAR;
17620         if (gameMode == TwoMachinesPlay) {
17621             SendToProgram("easy\n", &second);
17622         }
17623     }
17624     appData.ponderNextMove = newState;
17625 }
17626
17627 void
17628 NewSettingEvent (int option, int *feature, char *command, int value)
17629 {
17630     char buf[MSG_SIZ];
17631
17632     if (gameMode == EditPosition) EditPositionDone(TRUE);
17633     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17634     if(feature == NULL || *feature) SendToProgram(buf, &first);
17635     if (gameMode == TwoMachinesPlay) {
17636         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17637     }
17638 }
17639
17640 void
17641 ShowThinkingEvent ()
17642 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17643 {
17644     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17645     int newState = appData.showThinking
17646         // [HGM] thinking: other features now need thinking output as well
17647         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17648
17649     if (oldState == newState) return;
17650     oldState = newState;
17651     if (gameMode == EditPosition) EditPositionDone(TRUE);
17652     if (oldState) {
17653         SendToProgram("post\n", &first);
17654         if (gameMode == TwoMachinesPlay) {
17655             SendToProgram("post\n", &second);
17656         }
17657     } else {
17658         SendToProgram("nopost\n", &first);
17659         thinkOutput[0] = NULLCHAR;
17660         if (gameMode == TwoMachinesPlay) {
17661             SendToProgram("nopost\n", &second);
17662         }
17663     }
17664 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17665 }
17666
17667 void
17668 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17669 {
17670   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17671   if (pr == NoProc) return;
17672   AskQuestion(title, question, replyPrefix, pr);
17673 }
17674
17675 void
17676 TypeInEvent (char firstChar)
17677 {
17678     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17679         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17680         gameMode == AnalyzeMode || gameMode == EditGame ||
17681         gameMode == EditPosition || gameMode == IcsExamining ||
17682         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17683         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17684                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17685                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17686         gameMode == Training) PopUpMoveDialog(firstChar);
17687 }
17688
17689 void
17690 TypeInDoneEvent (char *move)
17691 {
17692         Board board;
17693         int n, fromX, fromY, toX, toY;
17694         char promoChar;
17695         ChessMove moveType;
17696
17697         // [HGM] FENedit
17698         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17699                 EditPositionPasteFEN(move);
17700                 return;
17701         }
17702         // [HGM] movenum: allow move number to be typed in any mode
17703         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17704           ToNrEvent(2*n-1);
17705           return;
17706         }
17707         // undocumented kludge: allow command-line option to be typed in!
17708         // (potentially fatal, and does not implement the effect of the option.)
17709         // should only be used for options that are values on which future decisions will be made,
17710         // and definitely not on options that would be used during initialization.
17711         if(strstr(move, "!!! -") == move) {
17712             ParseArgsFromString(move+4);
17713             return;
17714         }
17715
17716       if (gameMode != EditGame && currentMove != forwardMostMove &&
17717         gameMode != Training) {
17718         DisplayMoveError(_("Displayed move is not current"));
17719       } else {
17720         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17721           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17722         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17723         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17724           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17725           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17726         } else {
17727           DisplayMoveError(_("Could not parse move"));
17728         }
17729       }
17730 }
17731
17732 void
17733 DisplayMove (int moveNumber)
17734 {
17735     char message[MSG_SIZ];
17736     char res[MSG_SIZ];
17737     char cpThinkOutput[MSG_SIZ];
17738
17739     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17740
17741     if (moveNumber == forwardMostMove - 1 ||
17742         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17743
17744         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17745
17746         if (strchr(cpThinkOutput, '\n')) {
17747             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17748         }
17749     } else {
17750         *cpThinkOutput = NULLCHAR;
17751     }
17752
17753     /* [AS] Hide thinking from human user */
17754     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17755         *cpThinkOutput = NULLCHAR;
17756         if( thinkOutput[0] != NULLCHAR ) {
17757             int i;
17758
17759             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17760                 cpThinkOutput[i] = '.';
17761             }
17762             cpThinkOutput[i] = NULLCHAR;
17763             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17764         }
17765     }
17766
17767     if (moveNumber == forwardMostMove - 1 &&
17768         gameInfo.resultDetails != NULL) {
17769         if (gameInfo.resultDetails[0] == NULLCHAR) {
17770           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17771         } else {
17772           snprintf(res, MSG_SIZ, " {%s} %s",
17773                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17774         }
17775     } else {
17776         res[0] = NULLCHAR;
17777     }
17778
17779     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17780         DisplayMessage(res, cpThinkOutput);
17781     } else {
17782       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17783                 WhiteOnMove(moveNumber) ? " " : ".. ",
17784                 parseList[moveNumber], res);
17785         DisplayMessage(message, cpThinkOutput);
17786     }
17787 }
17788
17789 void
17790 DisplayComment (int moveNumber, char *text)
17791 {
17792     char title[MSG_SIZ];
17793
17794     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17795       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17796     } else {
17797       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17798               WhiteOnMove(moveNumber) ? " " : ".. ",
17799               parseList[moveNumber]);
17800     }
17801     if (text != NULL && (appData.autoDisplayComment || commentUp))
17802         CommentPopUp(title, text);
17803 }
17804
17805 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17806  * might be busy thinking or pondering.  It can be omitted if your
17807  * gnuchess is configured to stop thinking immediately on any user
17808  * input.  However, that gnuchess feature depends on the FIONREAD
17809  * ioctl, which does not work properly on some flavors of Unix.
17810  */
17811 void
17812 Attention (ChessProgramState *cps)
17813 {
17814 #if ATTENTION
17815     if (!cps->useSigint) return;
17816     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17817     switch (gameMode) {
17818       case MachinePlaysWhite:
17819       case MachinePlaysBlack:
17820       case TwoMachinesPlay:
17821       case IcsPlayingWhite:
17822       case IcsPlayingBlack:
17823       case AnalyzeMode:
17824       case AnalyzeFile:
17825         /* Skip if we know it isn't thinking */
17826         if (!cps->maybeThinking) return;
17827         if (appData.debugMode)
17828           fprintf(debugFP, "Interrupting %s\n", cps->which);
17829         InterruptChildProcess(cps->pr);
17830         cps->maybeThinking = FALSE;
17831         break;
17832       default:
17833         break;
17834     }
17835 #endif /*ATTENTION*/
17836 }
17837
17838 int
17839 CheckFlags ()
17840 {
17841     if (whiteTimeRemaining <= 0) {
17842         if (!whiteFlag) {
17843             whiteFlag = TRUE;
17844             if (appData.icsActive) {
17845                 if (appData.autoCallFlag &&
17846                     gameMode == IcsPlayingBlack && !blackFlag) {
17847                   SendToICS(ics_prefix);
17848                   SendToICS("flag\n");
17849                 }
17850             } else {
17851                 if (blackFlag) {
17852                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17853                 } else {
17854                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17855                     if (appData.autoCallFlag) {
17856                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17857                         return TRUE;
17858                     }
17859                 }
17860             }
17861         }
17862     }
17863     if (blackTimeRemaining <= 0) {
17864         if (!blackFlag) {
17865             blackFlag = TRUE;
17866             if (appData.icsActive) {
17867                 if (appData.autoCallFlag &&
17868                     gameMode == IcsPlayingWhite && !whiteFlag) {
17869                   SendToICS(ics_prefix);
17870                   SendToICS("flag\n");
17871                 }
17872             } else {
17873                 if (whiteFlag) {
17874                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17875                 } else {
17876                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17877                     if (appData.autoCallFlag) {
17878                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17879                         return TRUE;
17880                     }
17881                 }
17882             }
17883         }
17884     }
17885     return FALSE;
17886 }
17887
17888 void
17889 CheckTimeControl ()
17890 {
17891     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17892         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17893
17894     /*
17895      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17896      */
17897     if ( !WhiteOnMove(forwardMostMove) ) {
17898         /* White made time control */
17899         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17900         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17901         /* [HGM] time odds: correct new time quota for time odds! */
17902                                             / WhitePlayer()->timeOdds;
17903         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17904     } else {
17905         lastBlack -= blackTimeRemaining;
17906         /* Black made time control */
17907         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17908                                             / WhitePlayer()->other->timeOdds;
17909         lastWhite = whiteTimeRemaining;
17910     }
17911 }
17912
17913 void
17914 DisplayBothClocks ()
17915 {
17916     int wom = gameMode == EditPosition ?
17917       !blackPlaysFirst : WhiteOnMove(currentMove);
17918     DisplayWhiteClock(whiteTimeRemaining, wom);
17919     DisplayBlackClock(blackTimeRemaining, !wom);
17920 }
17921
17922
17923 /* Timekeeping seems to be a portability nightmare.  I think everyone
17924    has ftime(), but I'm really not sure, so I'm including some ifdefs
17925    to use other calls if you don't.  Clocks will be less accurate if
17926    you have neither ftime nor gettimeofday.
17927 */
17928
17929 /* VS 2008 requires the #include outside of the function */
17930 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17931 #include <sys/timeb.h>
17932 #endif
17933
17934 /* Get the current time as a TimeMark */
17935 void
17936 GetTimeMark (TimeMark *tm)
17937 {
17938 #if HAVE_GETTIMEOFDAY
17939
17940     struct timeval timeVal;
17941     struct timezone timeZone;
17942
17943     gettimeofday(&timeVal, &timeZone);
17944     tm->sec = (long) timeVal.tv_sec;
17945     tm->ms = (int) (timeVal.tv_usec / 1000L);
17946
17947 #else /*!HAVE_GETTIMEOFDAY*/
17948 #if HAVE_FTIME
17949
17950 // include <sys/timeb.h> / moved to just above start of function
17951     struct timeb timeB;
17952
17953     ftime(&timeB);
17954     tm->sec = (long) timeB.time;
17955     tm->ms = (int) timeB.millitm;
17956
17957 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17958     tm->sec = (long) time(NULL);
17959     tm->ms = 0;
17960 #endif
17961 #endif
17962 }
17963
17964 /* Return the difference in milliseconds between two
17965    time marks.  We assume the difference will fit in a long!
17966 */
17967 long
17968 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17969 {
17970     return 1000L*(tm2->sec - tm1->sec) +
17971            (long) (tm2->ms - tm1->ms);
17972 }
17973
17974
17975 /*
17976  * Code to manage the game clocks.
17977  *
17978  * In tournament play, black starts the clock and then white makes a move.
17979  * We give the human user a slight advantage if he is playing white---the
17980  * clocks don't run until he makes his first move, so it takes zero time.
17981  * Also, we don't account for network lag, so we could get out of sync
17982  * with GNU Chess's clock -- but then, referees are always right.
17983  */
17984
17985 static TimeMark tickStartTM;
17986 static long intendedTickLength;
17987
17988 long
17989 NextTickLength (long timeRemaining)
17990 {
17991     long nominalTickLength, nextTickLength;
17992
17993     if (timeRemaining > 0L && timeRemaining <= 10000L)
17994       nominalTickLength = 100L;
17995     else
17996       nominalTickLength = 1000L;
17997     nextTickLength = timeRemaining % nominalTickLength;
17998     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17999
18000     return nextTickLength;
18001 }
18002
18003 /* Adjust clock one minute up or down */
18004 void
18005 AdjustClock (Boolean which, int dir)
18006 {
18007     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
18008     if(which) blackTimeRemaining += 60000*dir;
18009     else      whiteTimeRemaining += 60000*dir;
18010     DisplayBothClocks();
18011     adjustedClock = TRUE;
18012 }
18013
18014 /* Stop clocks and reset to a fresh time control */
18015 void
18016 ResetClocks ()
18017 {
18018     (void) StopClockTimer();
18019     if (appData.icsActive) {
18020         whiteTimeRemaining = blackTimeRemaining = 0;
18021     } else if (searchTime) {
18022         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18023         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18024     } else { /* [HGM] correct new time quote for time odds */
18025         whiteTC = blackTC = fullTimeControlString;
18026         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
18027         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
18028     }
18029     if (whiteFlag || blackFlag) {
18030         DisplayTitle("");
18031         whiteFlag = blackFlag = FALSE;
18032     }
18033     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
18034     DisplayBothClocks();
18035     adjustedClock = FALSE;
18036 }
18037
18038 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
18039
18040 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
18041
18042 /* Decrement running clock by amount of time that has passed */
18043 void
18044 DecrementClocks ()
18045 {
18046     long tRemaining;
18047     long lastTickLength, fudge;
18048     TimeMark now;
18049
18050     if (!appData.clockMode) return;
18051     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
18052
18053     GetTimeMark(&now);
18054
18055     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18056
18057     /* Fudge if we woke up a little too soon */
18058     fudge = intendedTickLength - lastTickLength;
18059     if (fudge < 0 || fudge > FUDGE) fudge = 0;
18060
18061     if (WhiteOnMove(forwardMostMove)) {
18062         if(whiteNPS >= 0) lastTickLength = 0;
18063          tRemaining = whiteTimeRemaining -= lastTickLength;
18064         if( tRemaining < 0 && !appData.icsActive) {
18065             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
18066             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
18067                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
18068                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
18069             }
18070         }
18071         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
18072         DisplayWhiteClock(whiteTimeRemaining - fudge,
18073                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18074         timeSuffix = 0;
18075     } else {
18076         if(blackNPS >= 0) lastTickLength = 0;
18077          tRemaining = blackTimeRemaining -= lastTickLength;
18078         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
18079             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
18080             if(suddenDeath) {
18081                 blackStartMove = forwardMostMove;
18082                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
18083             }
18084         }
18085         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
18086         DisplayBlackClock(blackTimeRemaining - fudge,
18087                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
18088         timeSuffix = 0;
18089     }
18090     if (CheckFlags()) return;
18091
18092     if(twoBoards) { // count down secondary board's clocks as well
18093         activePartnerTime -= lastTickLength;
18094         partnerUp = 1;
18095         if(activePartner == 'W')
18096             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
18097         else
18098             DisplayBlackClock(activePartnerTime, TRUE);
18099         partnerUp = 0;
18100     }
18101
18102     tickStartTM = now;
18103     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
18104     StartClockTimer(intendedTickLength);
18105
18106     /* if the time remaining has fallen below the alarm threshold, sound the
18107      * alarm. if the alarm has sounded and (due to a takeback or time control
18108      * with increment) the time remaining has increased to a level above the
18109      * threshold, reset the alarm so it can sound again.
18110      */
18111
18112     if (appData.icsActive && appData.icsAlarm) {
18113
18114         /* make sure we are dealing with the user's clock */
18115         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
18116                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
18117            )) return;
18118
18119         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
18120             alarmSounded = FALSE;
18121         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
18122             PlayAlarmSound();
18123             alarmSounded = TRUE;
18124         }
18125     }
18126 }
18127
18128
18129 /* A player has just moved, so stop the previously running
18130    clock and (if in clock mode) start the other one.
18131    We redisplay both clocks in case we're in ICS mode, because
18132    ICS gives us an update to both clocks after every move.
18133    Note that this routine is called *after* forwardMostMove
18134    is updated, so the last fractional tick must be subtracted
18135    from the color that is *not* on move now.
18136 */
18137 void
18138 SwitchClocks (int newMoveNr)
18139 {
18140     long lastTickLength;
18141     TimeMark now;
18142     int flagged = FALSE;
18143
18144     GetTimeMark(&now);
18145
18146     if (StopClockTimer() && appData.clockMode) {
18147         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18148         if (!WhiteOnMove(forwardMostMove)) {
18149             if(blackNPS >= 0) lastTickLength = 0;
18150             blackTimeRemaining -= lastTickLength;
18151            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18152 //         if(pvInfoList[forwardMostMove].time == -1)
18153                  pvInfoList[forwardMostMove].time =               // use GUI time
18154                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18155         } else {
18156            if(whiteNPS >= 0) lastTickLength = 0;
18157            whiteTimeRemaining -= lastTickLength;
18158            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18159 //         if(pvInfoList[forwardMostMove].time == -1)
18160                  pvInfoList[forwardMostMove].time =
18161                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18162         }
18163         flagged = CheckFlags();
18164     }
18165     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18166     CheckTimeControl();
18167
18168     if (flagged || !appData.clockMode) return;
18169
18170     switch (gameMode) {
18171       case MachinePlaysBlack:
18172       case MachinePlaysWhite:
18173       case BeginningOfGame:
18174         if (pausing) return;
18175         break;
18176
18177       case EditGame:
18178       case PlayFromGameFile:
18179       case IcsExamining:
18180         return;
18181
18182       default:
18183         break;
18184     }
18185
18186     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18187         if(WhiteOnMove(forwardMostMove))
18188              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18189         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18190     }
18191
18192     tickStartTM = now;
18193     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18194       whiteTimeRemaining : blackTimeRemaining);
18195     StartClockTimer(intendedTickLength);
18196 }
18197
18198
18199 /* Stop both clocks */
18200 void
18201 StopClocks ()
18202 {
18203     long lastTickLength;
18204     TimeMark now;
18205
18206     if (!StopClockTimer()) return;
18207     if (!appData.clockMode) return;
18208
18209     GetTimeMark(&now);
18210
18211     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18212     if (WhiteOnMove(forwardMostMove)) {
18213         if(whiteNPS >= 0) lastTickLength = 0;
18214         whiteTimeRemaining -= lastTickLength;
18215         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18216     } else {
18217         if(blackNPS >= 0) lastTickLength = 0;
18218         blackTimeRemaining -= lastTickLength;
18219         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18220     }
18221     CheckFlags();
18222 }
18223
18224 /* Start clock of player on move.  Time may have been reset, so
18225    if clock is already running, stop and restart it. */
18226 void
18227 StartClocks ()
18228 {
18229     (void) StopClockTimer(); /* in case it was running already */
18230     DisplayBothClocks();
18231     if (CheckFlags()) return;
18232
18233     if (!appData.clockMode) return;
18234     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18235
18236     GetTimeMark(&tickStartTM);
18237     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18238       whiteTimeRemaining : blackTimeRemaining);
18239
18240    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18241     whiteNPS = blackNPS = -1;
18242     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18243        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18244         whiteNPS = first.nps;
18245     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18246        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18247         blackNPS = first.nps;
18248     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18249         whiteNPS = second.nps;
18250     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18251         blackNPS = second.nps;
18252     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18253
18254     StartClockTimer(intendedTickLength);
18255 }
18256
18257 char *
18258 TimeString (long ms)
18259 {
18260     long second, minute, hour, day;
18261     char *sign = "";
18262     static char buf[40], moveTime[8];
18263
18264     if (ms > 0 && ms <= 9900) {
18265       /* convert milliseconds to tenths, rounding up */
18266       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18267
18268       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18269       return buf;
18270     }
18271
18272     /* convert milliseconds to seconds, rounding up */
18273     /* use floating point to avoid strangeness of integer division
18274        with negative dividends on many machines */
18275     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18276
18277     if (second < 0) {
18278         sign = "-";
18279         second = -second;
18280     }
18281
18282     day = second / (60 * 60 * 24);
18283     second = second % (60 * 60 * 24);
18284     hour = second / (60 * 60);
18285     second = second % (60 * 60);
18286     minute = second / 60;
18287     second = second % 60;
18288
18289     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18290     else *moveTime = NULLCHAR;
18291
18292     if (day > 0)
18293       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18294               sign, day, hour, minute, second, moveTime);
18295     else if (hour > 0)
18296       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18297     else
18298       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18299
18300     return buf;
18301 }
18302
18303
18304 /*
18305  * This is necessary because some C libraries aren't ANSI C compliant yet.
18306  */
18307 char *
18308 StrStr (char *string, char *match)
18309 {
18310     int i, length;
18311
18312     length = strlen(match);
18313
18314     for (i = strlen(string) - length; i >= 0; i--, string++)
18315       if (!strncmp(match, string, length))
18316         return string;
18317
18318     return NULL;
18319 }
18320
18321 char *
18322 StrCaseStr (char *string, char *match)
18323 {
18324     int i, j, length;
18325
18326     length = strlen(match);
18327
18328     for (i = strlen(string) - length; i >= 0; i--, string++) {
18329         for (j = 0; j < length; j++) {
18330             if (ToLower(match[j]) != ToLower(string[j]))
18331               break;
18332         }
18333         if (j == length) return string;
18334     }
18335
18336     return NULL;
18337 }
18338
18339 #ifndef _amigados
18340 int
18341 StrCaseCmp (char *s1, char *s2)
18342 {
18343     char c1, c2;
18344
18345     for (;;) {
18346         c1 = ToLower(*s1++);
18347         c2 = ToLower(*s2++);
18348         if (c1 > c2) return 1;
18349         if (c1 < c2) return -1;
18350         if (c1 == NULLCHAR) return 0;
18351     }
18352 }
18353
18354
18355 int
18356 ToLower (int c)
18357 {
18358     return isupper(c) ? tolower(c) : c;
18359 }
18360
18361
18362 int
18363 ToUpper (int c)
18364 {
18365     return islower(c) ? toupper(c) : c;
18366 }
18367 #endif /* !_amigados    */
18368
18369 char *
18370 StrSave (char *s)
18371 {
18372   char *ret;
18373
18374   if ((ret = (char *) malloc(strlen(s) + 1)))
18375     {
18376       safeStrCpy(ret, s, strlen(s)+1);
18377     }
18378   return ret;
18379 }
18380
18381 char *
18382 StrSavePtr (char *s, char **savePtr)
18383 {
18384     if (*savePtr) {
18385         free(*savePtr);
18386     }
18387     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18388       safeStrCpy(*savePtr, s, strlen(s)+1);
18389     }
18390     return(*savePtr);
18391 }
18392
18393 char *
18394 PGNDate ()
18395 {
18396     time_t clock;
18397     struct tm *tm;
18398     char buf[MSG_SIZ];
18399
18400     clock = time((time_t *)NULL);
18401     tm = localtime(&clock);
18402     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18403             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18404     return StrSave(buf);
18405 }
18406
18407
18408 char *
18409 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18410 {
18411     int i, j, fromX, fromY, toX, toY;
18412     int whiteToPlay, haveRights = nrCastlingRights;
18413     char buf[MSG_SIZ];
18414     char *p, *q;
18415     int emptycount;
18416     ChessSquare piece;
18417
18418     whiteToPlay = (gameMode == EditPosition) ?
18419       !blackPlaysFirst : (move % 2 == 0);
18420     p = buf;
18421
18422     /* Piece placement data */
18423     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18424         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18425         emptycount = 0;
18426         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18427             if (boards[move][i][j] == EmptySquare) {
18428                 emptycount++;
18429             } else { ChessSquare piece = boards[move][i][j];
18430                 if (emptycount > 0) {
18431                     if(emptycount<10) /* [HGM] can be >= 10 */
18432                         *p++ = '0' + emptycount;
18433                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18434                     emptycount = 0;
18435                 }
18436                 if(PieceToChar(piece) == '+') {
18437                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18438                     *p++ = '+';
18439                     piece = (ChessSquare)(CHUDEMOTED(piece));
18440                 }
18441                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18442                 if(*p = PieceSuffix(piece)) p++;
18443                 if(p[-1] == '~') {
18444                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18445                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18446                     *p++ = '~';
18447                 }
18448             }
18449         }
18450         if (emptycount > 0) {
18451             if(emptycount<10) /* [HGM] can be >= 10 */
18452                 *p++ = '0' + emptycount;
18453             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18454             emptycount = 0;
18455         }
18456         *p++ = '/';
18457     }
18458     *(p - 1) = ' ';
18459
18460     /* [HGM] print Crazyhouse or Shogi holdings */
18461     if( gameInfo.holdingsWidth ) {
18462         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18463         q = p;
18464         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18465             piece = boards[move][i][BOARD_WIDTH-1];
18466             if( piece != EmptySquare )
18467               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18468                   *p++ = PieceToChar(piece);
18469         }
18470         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18471             piece = boards[move][handSize-i-1][0];
18472             if( piece != EmptySquare )
18473               for(j=0; j<(int) boards[move][handSize-i-1][1]; j++)
18474                   *p++ = PieceToChar(piece);
18475         }
18476
18477         if( q == p ) *p++ = '-';
18478         *p++ = ']';
18479         *p++ = ' ';
18480     }
18481
18482     /* Active color */
18483     *p++ = whiteToPlay ? 'w' : 'b';
18484     *p++ = ' ';
18485
18486   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18487     haveRights = 0; q = p;
18488     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18489       piece = boards[move][0][i];
18490       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18491         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18492       }
18493     }
18494     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18495       piece = boards[move][BOARD_HEIGHT-1][i];
18496       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18497         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18498       }
18499     }
18500     if(p == q) *p++ = '-';
18501     *p++ = ' ';
18502   }
18503
18504   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18505     while(*p++ = *q++)
18506                       ;
18507     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18508   } else {
18509   if(haveRights) {
18510      int handW=0, handB=0;
18511      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18512         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18513         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18514      }
18515      q = p;
18516      if(appData.fischerCastling) {
18517         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18518            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18519                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18520         } else {
18521        /* [HGM] write directly from rights */
18522            if(boards[move][CASTLING][2] != NoRights &&
18523               boards[move][CASTLING][0] != NoRights   )
18524                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18525            if(boards[move][CASTLING][2] != NoRights &&
18526               boards[move][CASTLING][1] != NoRights   )
18527                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18528         }
18529         if(handB) {
18530            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18531                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18532         } else {
18533            if(boards[move][CASTLING][5] != NoRights &&
18534               boards[move][CASTLING][3] != NoRights   )
18535                 *p++ = boards[move][CASTLING][3] + AAA;
18536            if(boards[move][CASTLING][5] != NoRights &&
18537               boards[move][CASTLING][4] != NoRights   )
18538                 *p++ = boards[move][CASTLING][4] + AAA;
18539         }
18540      } else {
18541
18542         /* [HGM] write true castling rights */
18543         if( nrCastlingRights == 6 ) {
18544             int q, k=0;
18545             if(boards[move][CASTLING][0] != NoRights &&
18546                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18547             q = (boards[move][CASTLING][1] != NoRights &&
18548                  boards[move][CASTLING][2] != NoRights  );
18549             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18550                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18551                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18552                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18553             }
18554             if(q) *p++ = 'Q';
18555             k = 0;
18556             if(boards[move][CASTLING][3] != NoRights &&
18557                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18558             q = (boards[move][CASTLING][4] != NoRights &&
18559                  boards[move][CASTLING][5] != NoRights  );
18560             if(handB) {
18561                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18562                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18563                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18564             }
18565             if(q) *p++ = 'q';
18566         }
18567      }
18568      if (q == p) *p++ = '-'; /* No castling rights */
18569      *p++ = ' ';
18570   }
18571
18572   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18573      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18574      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18575     /* En passant target square */
18576     if (move > backwardMostMove) {
18577         fromX = moveList[move - 1][0] - AAA;
18578         fromY = moveList[move - 1][1] - ONE;
18579         toX = moveList[move - 1][2] - AAA;
18580         toY = moveList[move - 1][3] - ONE;
18581         if ((whiteToPlay ? toY < fromY - 1 : toY > fromY + 1) &&
18582             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) ) {
18583             /* 2-square pawn move just happened */
18584             *p++ = (3*toX + 5*fromX + 4)/8 + AAA;
18585             *p++ = (3*toY + 5*fromY + 4)/8 + ONE;
18586             if(gameInfo.variant == VariantBerolina) {
18587                 *p++ = toX + AAA;
18588                 *p++ = toY + ONE;
18589             }
18590         } else {
18591             *p++ = '-';
18592         }
18593     } else if(move == backwardMostMove) {
18594         // [HGM] perhaps we should always do it like this, and forget the above?
18595         if((signed char)boards[move][EP_STATUS] >= 0) {
18596             *p++ = boards[move][EP_STATUS] + AAA;
18597             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18598         } else {
18599             *p++ = '-';
18600         }
18601     } else {
18602         *p++ = '-';
18603     }
18604     *p++ = ' ';
18605   }
18606   }
18607
18608     i = boards[move][CHECK_COUNT];
18609     if(i) {
18610         sprintf(p, "%d+%d ", i&255, i>>8);
18611         while(*p) p++;
18612     }
18613
18614     if(moveCounts)
18615     {   int i = 0, j=move;
18616
18617         /* [HGM] find reversible plies */
18618         if (appData.debugMode) { int k;
18619             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18620             for(k=backwardMostMove; k<=forwardMostMove; k++)
18621                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18622
18623         }
18624
18625         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18626         if( j == backwardMostMove ) i += initialRulePlies;
18627         sprintf(p, "%d ", i);
18628         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18629
18630         /* Fullmove number */
18631         sprintf(p, "%d", (move / 2) + 1);
18632     } else *--p = NULLCHAR;
18633
18634     return StrSave(buf);
18635 }
18636
18637 Boolean
18638 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18639 {
18640     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18641     char *p, c;
18642     int emptycount, virgin[BOARD_FILES];
18643     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18644
18645     p = fen;
18646
18647     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18648
18649     /* Piece placement data */
18650     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18651         j = 0;
18652         for (;;) {
18653             if (*p == '/' || *p == ' ' || *p == '[' ) {
18654                 if(j > w) w = j;
18655                 emptycount = gameInfo.boardWidth - j;
18656                 while (emptycount--)
18657                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18658                 if (*p == '/') p++;
18659                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18660                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18661                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18662                     }
18663                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18664                 }
18665                 break;
18666 #if(BOARD_FILES >= 10)*0
18667             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18668                 p++; emptycount=10;
18669                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18670                 while (emptycount--)
18671                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18672 #endif
18673             } else if (*p == '*') {
18674                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18675             } else if (isdigit(*p)) {
18676                 emptycount = *p++ - '0';
18677                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18678                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18679                 while (emptycount--)
18680                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18681             } else if (*p == '<') {
18682                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18683                 else if (i != 0 || !shuffle) return FALSE;
18684                 p++;
18685             } else if (shuffle && *p == '>') {
18686                 p++; // for now ignore closing shuffle range, and assume rank-end
18687             } else if (*p == '?') {
18688                 if (j >= gameInfo.boardWidth) return FALSE;
18689                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18690                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18691             } else if (*p == '+' || isalpha(*p)) {
18692                 char *q, *s = SUFFIXES;
18693                 if (j >= gameInfo.boardWidth) return FALSE;
18694                 if(*p=='+') {
18695                     char c = *++p;
18696                     if(q = strchr(s, p[1])) p++;
18697                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18698                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18699                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18700                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18701                 } else {
18702                     char c = *p++;
18703                     if(q = strchr(s, *p)) p++;
18704                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18705                 }
18706
18707                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18708                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18709                     piece = (ChessSquare) (PROMOTED(piece));
18710                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18711                     p++;
18712                 }
18713                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18714                 if(piece == king) wKingRank = i;
18715                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18716             } else {
18717                 return FALSE;
18718             }
18719         }
18720     }
18721     while (*p == '/' || *p == ' ') p++;
18722
18723     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18724
18725     /* [HGM] by default clear Crazyhouse holdings, if present */
18726     if(gameInfo.holdingsWidth) {
18727        for(i=0; i<handSize; i++) {
18728            board[i][0]             = EmptySquare; /* black holdings */
18729            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18730            board[i][1]             = (ChessSquare) 0; /* black counts */
18731            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18732        }
18733     }
18734
18735     /* [HGM] look for Crazyhouse holdings here */
18736     while(*p==' ') p++;
18737     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18738         int swap=0, wcnt=0, bcnt=0;
18739         if(*p == '[') p++;
18740         if(*p == '<') swap++, p++;
18741         if(*p == '-' ) p++; /* empty holdings */ else {
18742             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18743             /* if we would allow FEN reading to set board size, we would   */
18744             /* have to add holdings and shift the board read so far here   */
18745             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18746                 p++;
18747                 if((int) piece >= (int) BlackPawn ) {
18748                     i = (int)piece - (int)BlackPawn;
18749                     i = PieceToNumber((ChessSquare)i);
18750                     if( i >= gameInfo.holdingsSize ) return FALSE;
18751                     board[handSize-1-i][0] = piece; /* black holdings */
18752                     board[handSize-1-i][1]++;       /* black counts   */
18753                     bcnt++;
18754                 } else {
18755                     i = (int)piece - (int)WhitePawn;
18756                     i = PieceToNumber((ChessSquare)i);
18757                     if( i >= gameInfo.holdingsSize ) return FALSE;
18758                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18759                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18760                     wcnt++;
18761                 }
18762             }
18763             if(subst) { // substitute back-rank question marks by holdings pieces
18764                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18765                     int k, m, n = bcnt + 1;
18766                     if(board[0][j] == ClearBoard) {
18767                         if(!wcnt) return FALSE;
18768                         n = rand() % wcnt;
18769                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18770                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18771                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18772                             break;
18773                         }
18774                     }
18775                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18776                         if(!bcnt) return FALSE;
18777                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18778                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[handSize-1-k][1]) < 0) {
18779                             board[BOARD_HEIGHT-1][j] = board[handSize-1-k][0]; bcnt--;
18780                             if(--board[handSize-1-k][1] == 0) board[handSize-1-k][0] = EmptySquare;
18781                             break;
18782                         }
18783                     }
18784                 }
18785                 subst = 0;
18786             }
18787         }
18788         if(*p == ']') p++;
18789     }
18790
18791     if(subst) return FALSE; // substitution requested, but no holdings
18792
18793     while(*p == ' ') p++;
18794
18795     /* Active color */
18796     c = *p++;
18797     if(appData.colorNickNames) {
18798       if( c == appData.colorNickNames[0] ) c = 'w'; else
18799       if( c == appData.colorNickNames[1] ) c = 'b';
18800     }
18801     switch (c) {
18802       case 'w':
18803         *blackPlaysFirst = FALSE;
18804         break;
18805       case 'b':
18806         *blackPlaysFirst = TRUE;
18807         break;
18808       default:
18809         return FALSE;
18810     }
18811
18812     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18813     /* return the extra info in global variiables             */
18814
18815     while(*p==' ') p++;
18816
18817     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18818         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18819         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18820     }
18821
18822     /* set defaults in case FEN is incomplete */
18823     board[EP_STATUS] = EP_UNKNOWN;
18824     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18825     for(i=0; i<nrCastlingRights; i++ ) {
18826         board[CASTLING][i] =
18827             appData.fischerCastling ? NoRights : initialRights[i];
18828     }   /* assume possible unless obviously impossible */
18829     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18830     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18831     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18832                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18833     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18834     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18835     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18836                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18837     FENrulePlies = 0;
18838
18839     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18840       char *q = p;
18841       int w=0, b=0;
18842       while(isalpha(*p)) {
18843         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18844         if(islower(*p)) b |= 1 << (*p++ - 'a');
18845       }
18846       if(*p == '-') p++;
18847       if(p != q) {
18848         board[TOUCHED_W] = ~w;
18849         board[TOUCHED_B] = ~b;
18850         while(*p == ' ') p++;
18851       }
18852     } else
18853
18854     if(nrCastlingRights) {
18855       int fischer = 0;
18856       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18857       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18858           /* castling indicator present, so default becomes no castlings */
18859           for(i=0; i<nrCastlingRights; i++ ) {
18860                  board[CASTLING][i] = NoRights;
18861           }
18862       }
18863       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18864              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18865              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18866              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18867         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18868
18869         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18870             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18871             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18872         }
18873         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18874             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18875         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18876                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18877         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18878                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18879         switch(c) {
18880           case'K':
18881               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18882               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18883               board[CASTLING][2] = whiteKingFile;
18884               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18885               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18886               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18887               break;
18888           case'Q':
18889               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18890               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18891               board[CASTLING][2] = whiteKingFile;
18892               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18893               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18894               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18895               break;
18896           case'k':
18897               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18898               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18899               board[CASTLING][5] = blackKingFile;
18900               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18901               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18902               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18903               break;
18904           case'q':
18905               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18906               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18907               board[CASTLING][5] = blackKingFile;
18908               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18909               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18910               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18911           case '-':
18912               break;
18913           default: /* FRC castlings */
18914               if(c >= 'a') { /* black rights */
18915                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18916                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18917                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18918                   if(i == BOARD_RGHT) break;
18919                   board[CASTLING][5] = i;
18920                   c -= AAA;
18921                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18922                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18923                   if(c > i)
18924                       board[CASTLING][3] = c;
18925                   else
18926                       board[CASTLING][4] = c;
18927               } else { /* white rights */
18928                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18929                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18930                     if(board[0][i] == WhiteKing) break;
18931                   if(i == BOARD_RGHT) break;
18932                   board[CASTLING][2] = i;
18933                   c -= AAA - 'a' + 'A';
18934                   if(board[0][c] >= WhiteKing) break;
18935                   if(c > i)
18936                       board[CASTLING][0] = c;
18937                   else
18938                       board[CASTLING][1] = c;
18939               }
18940         }
18941       }
18942       for(i=0; i<nrCastlingRights; i++)
18943         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18944       if(gameInfo.variant == VariantSChess)
18945         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18946       if(fischer && shuffle) appData.fischerCastling = TRUE;
18947     if (appData.debugMode) {
18948         fprintf(debugFP, "FEN castling rights:");
18949         for(i=0; i<nrCastlingRights; i++)
18950         fprintf(debugFP, " %d", board[CASTLING][i]);
18951         fprintf(debugFP, "\n");
18952     }
18953
18954       while(*p==' ') p++;
18955     }
18956
18957     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18958
18959     /* read e.p. field in games that know e.p. capture */
18960     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18961        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18962        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18963       if(*p=='-') {
18964         p++; board[EP_STATUS] = EP_NONE;
18965       } else {
18966          int d, r, c = *p - AAA;
18967
18968          if(c >= BOARD_LEFT && c < BOARD_RGHT) {
18969              p++;
18970              board[EP_STATUS] = board[EP_FILE] = c; r = 0;
18971              if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18972              d = (r < BOARD_HEIGHT << 1 ? 1 : -1); // assume double-push (P next to e.p. square nearer center)
18973              if(board[r+d][c] == EmptySquare) d *= 2; // but if no Pawn there, triple push
18974              board[LAST_TO] = 256*(r + d) + c;
18975              c = *p++ - AAA;
18976              if(c >= BOARD_LEFT && c < BOARD_RGHT) { // mover explicitly mentioned
18977                  if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18978                  board[LAST_TO] = 256*r + c;
18979                  if(!(board[EP_RANK]-r & 1)) board[EP_RANK] |= 128;
18980              }
18981          }
18982       }
18983     }
18984
18985     while(*p == ' ') p++;
18986
18987     board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18988     if(sscanf(p, "%d+%d", &i, &j) == 2) {
18989         board[CHECK_COUNT] = i + 256*j;
18990         while(*p && *p != ' ') p++;
18991     }
18992
18993     c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18994     if(c > 0) {
18995         FENrulePlies = i; /* 50-move ply counter */
18996         /* (The move number is still ignored)    */
18997         if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18998     }
18999
19000     return TRUE;
19001 }
19002
19003 void
19004 EditPositionPasteFEN (char *fen)
19005 {
19006   if (fen != NULL) {
19007     Board initial_position;
19008
19009     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
19010       DisplayError(_("Bad FEN position in clipboard"), 0);
19011       return ;
19012     } else {
19013       int savedBlackPlaysFirst = blackPlaysFirst;
19014       EditPositionEvent();
19015       blackPlaysFirst = savedBlackPlaysFirst;
19016       CopyBoard(boards[0], initial_position);
19017       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
19018       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
19019       DisplayBothClocks();
19020       DrawPosition(FALSE, boards[currentMove]);
19021     }
19022   }
19023 }
19024
19025 static char cseq[12] = "\\   ";
19026
19027 Boolean
19028 set_cont_sequence (char *new_seq)
19029 {
19030     int len;
19031     Boolean ret;
19032
19033     // handle bad attempts to set the sequence
19034         if (!new_seq)
19035                 return 0; // acceptable error - no debug
19036
19037     len = strlen(new_seq);
19038     ret = (len > 0) && (len < sizeof(cseq));
19039     if (ret)
19040       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
19041     else if (appData.debugMode)
19042       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
19043     return ret;
19044 }
19045
19046 /*
19047     reformat a source message so words don't cross the width boundary.  internal
19048     newlines are not removed.  returns the wrapped size (no null character unless
19049     included in source message).  If dest is NULL, only calculate the size required
19050     for the dest buffer.  lp argument indicats line position upon entry, and it's
19051     passed back upon exit.
19052 */
19053 int
19054 wrap (char *dest, char *src, int count, int width, int *lp)
19055 {
19056     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
19057
19058     cseq_len = strlen(cseq);
19059     old_line = line = *lp;
19060     ansi = len = clen = 0;
19061
19062     for (i=0; i < count; i++)
19063     {
19064         if (src[i] == '\033')
19065             ansi = 1;
19066
19067         // if we hit the width, back up
19068         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
19069         {
19070             // store i & len in case the word is too long
19071             old_i = i, old_len = len;
19072
19073             // find the end of the last word
19074             while (i && src[i] != ' ' && src[i] != '\n')
19075             {
19076                 i--;
19077                 len--;
19078             }
19079
19080             // word too long?  restore i & len before splitting it
19081             if ((old_i-i+clen) >= width)
19082             {
19083                 i = old_i;
19084                 len = old_len;
19085             }
19086
19087             // extra space?
19088             if (i && src[i-1] == ' ')
19089                 len--;
19090
19091             if (src[i] != ' ' && src[i] != '\n')
19092             {
19093                 i--;
19094                 if (len)
19095                     len--;
19096             }
19097
19098             // now append the newline and continuation sequence
19099             if (dest)
19100                 dest[len] = '\n';
19101             len++;
19102             if (dest)
19103                 strncpy(dest+len, cseq, cseq_len);
19104             len += cseq_len;
19105             line = cseq_len;
19106             clen = cseq_len;
19107             continue;
19108         }
19109
19110         if (dest)
19111             dest[len] = src[i];
19112         len++;
19113         if (!ansi)
19114             line++;
19115         if (src[i] == '\n')
19116             line = 0;
19117         if (src[i] == 'm')
19118             ansi = 0;
19119     }
19120     if (dest && appData.debugMode)
19121     {
19122         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
19123             count, width, line, len, *lp);
19124         show_bytes(debugFP, src, count);
19125         fprintf(debugFP, "\ndest: ");
19126         show_bytes(debugFP, dest, len);
19127         fprintf(debugFP, "\n");
19128     }
19129     *lp = dest ? line : old_line;
19130
19131     return len;
19132 }
19133
19134 // [HGM] vari: routines for shelving variations
19135 Boolean modeRestore = FALSE;
19136
19137 void
19138 PushInner (int firstMove, int lastMove)
19139 {
19140         int i, j, nrMoves = lastMove - firstMove;
19141
19142         // push current tail of game on stack
19143         savedResult[storedGames] = gameInfo.result;
19144         savedDetails[storedGames] = gameInfo.resultDetails;
19145         gameInfo.resultDetails = NULL;
19146         savedFirst[storedGames] = firstMove;
19147         savedLast [storedGames] = lastMove;
19148         savedFramePtr[storedGames] = framePtr;
19149         framePtr -= nrMoves; // reserve space for the boards
19150         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19151             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19152             for(j=0; j<MOVE_LEN; j++)
19153                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19154             for(j=0; j<2*MOVE_LEN; j++)
19155                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19156             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19157             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19158             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19159             pvInfoList[firstMove+i-1].depth = 0;
19160             commentList[framePtr+i] = commentList[firstMove+i];
19161             commentList[firstMove+i] = NULL;
19162         }
19163
19164         storedGames++;
19165         forwardMostMove = firstMove; // truncate game so we can start variation
19166 }
19167
19168 void
19169 PushTail (int firstMove, int lastMove)
19170 {
19171         if(appData.icsActive) { // only in local mode
19172                 forwardMostMove = currentMove; // mimic old ICS behavior
19173                 return;
19174         }
19175         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19176
19177         PushInner(firstMove, lastMove);
19178         if(storedGames == 1) GreyRevert(FALSE);
19179         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19180 }
19181
19182 void
19183 PopInner (Boolean annotate)
19184 {
19185         int i, j, nrMoves;
19186         char buf[8000], moveBuf[20];
19187
19188         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19189         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19190         nrMoves = savedLast[storedGames] - currentMove;
19191         if(annotate) {
19192                 int cnt = 10;
19193                 if(!WhiteOnMove(currentMove))
19194                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19195                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19196                 for(i=currentMove; i<forwardMostMove; i++) {
19197                         if(WhiteOnMove(i))
19198                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19199                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19200                         strcat(buf, moveBuf);
19201                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19202                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19203                 }
19204                 strcat(buf, ")");
19205         }
19206         for(i=1; i<=nrMoves; i++) { // copy last variation back
19207             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19208             for(j=0; j<MOVE_LEN; j++)
19209                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19210             for(j=0; j<2*MOVE_LEN; j++)
19211                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19212             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19213             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19214             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19215             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19216             commentList[currentMove+i] = commentList[framePtr+i];
19217             commentList[framePtr+i] = NULL;
19218         }
19219         if(annotate) AppendComment(currentMove+1, buf, FALSE);
19220         framePtr = savedFramePtr[storedGames];
19221         gameInfo.result = savedResult[storedGames];
19222         if(gameInfo.resultDetails != NULL) {
19223             free(gameInfo.resultDetails);
19224       }
19225         gameInfo.resultDetails = savedDetails[storedGames];
19226         forwardMostMove = currentMove + nrMoves;
19227 }
19228
19229 Boolean
19230 PopTail (Boolean annotate)
19231 {
19232         if(appData.icsActive) return FALSE; // only in local mode
19233         if(!storedGames) return FALSE; // sanity
19234         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19235
19236         PopInner(annotate);
19237         if(currentMove < forwardMostMove) ForwardEvent(); else
19238         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19239
19240         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19241         return TRUE;
19242 }
19243
19244 void
19245 CleanupTail ()
19246 {       // remove all shelved variations
19247         int i;
19248         for(i=0; i<storedGames; i++) {
19249             if(savedDetails[i])
19250                 free(savedDetails[i]);
19251             savedDetails[i] = NULL;
19252         }
19253         for(i=framePtr; i<MAX_MOVES; i++) {
19254                 if(commentList[i]) free(commentList[i]);
19255                 commentList[i] = NULL;
19256         }
19257         framePtr = MAX_MOVES-1;
19258         storedGames = 0;
19259 }
19260
19261 void
19262 LoadVariation (int index, char *text)
19263 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19264         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19265         int level = 0, move;
19266
19267         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19268         // first find outermost bracketing variation
19269         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19270             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19271                 if(*p == '{') wait = '}'; else
19272                 if(*p == '[') wait = ']'; else
19273                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19274                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19275             }
19276             if(*p == wait) wait = NULLCHAR; // closing ]} found
19277             p++;
19278         }
19279         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19280         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19281         end[1] = NULLCHAR; // clip off comment beyond variation
19282         ToNrEvent(currentMove-1);
19283         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19284         // kludge: use ParsePV() to append variation to game
19285         move = currentMove;
19286         ParsePV(start, TRUE, TRUE);
19287         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19288         ClearPremoveHighlights();
19289         CommentPopDown();
19290         ToNrEvent(currentMove+1);
19291 }
19292
19293 int transparency[2];
19294
19295 void
19296 LoadTheme ()
19297 {
19298 #define BUF_SIZ (2*MSG_SIZ)
19299     char *p, *q, buf[BUF_SIZ];
19300     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19301         snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19302         ParseArgsFromString(buf);
19303         ActivateTheme(TRUE); // also redo colors
19304         return;
19305     }
19306     p = nickName;
19307     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19308     {
19309         int len;
19310         q = appData.themeNames;
19311         snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19312       if(appData.useBitmaps) {
19313         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19314                 Shorten(appData.liteBackTextureFile));
19315         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19316                 Shorten(appData.darkBackTextureFile),
19317                 appData.liteBackTextureMode,
19318                 appData.darkBackTextureMode );
19319       } else {
19320         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19321       }
19322       if(!appData.useBitmaps || transparency[0]) {
19323         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19324       }
19325       if(!appData.useBitmaps || transparency[1]) {
19326         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19327       }
19328       if(appData.useBorder) {
19329         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19330                 appData.border);
19331       } else {
19332         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19333       }
19334       if(appData.useFont) {
19335         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19336                 appData.renderPiecesWithFont,
19337                 appData.fontToPieceTable,
19338                 Col2Text(9),    // appData.fontBackColorWhite
19339                 Col2Text(10) ); // appData.fontForeColorBlack
19340       } else {
19341         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19342         if(appData.pieceDirectory[0]) {
19343           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19344           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19345             snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19346         }
19347         if(!appData.pieceDirectory[0] || !appData.trueColors)
19348           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19349                 Col2Text(0),   // whitePieceColor
19350                 Col2Text(1) ); // blackPieceColor
19351       }
19352       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19353                 Col2Text(4),   // highlightSquareColor
19354                 Col2Text(5) ); // premoveHighlightColor
19355         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19356         if(insert != q) insert[-1] = NULLCHAR;
19357         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19358         if(q)   free(q);
19359     }
19360     ActivateTheme(FALSE);
19361 }