Fix bug #43792 (no highlights after rejection of premove)
[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 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void SendToProgram P((char *message, ChessProgramState *cps));
196 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
197 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
198                            char *buf, int count, int error));
199 void SendTimeControl P((ChessProgramState *cps,
200                         int mps, long tc, int inc, int sd, int st));
201 char *TimeControlTagValue P((void));
202 void Attention P((ChessProgramState *cps));
203 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
204 int ResurrectChessProgram P((void));
205 void DisplayComment P((int moveNumber, char *text));
206 void DisplayMove P((int moveNumber));
207
208 void ParseGameHistory P((char *game));
209 void ParseBoard12 P((char *string));
210 void KeepAlive P((void));
211 void StartClocks P((void));
212 void SwitchClocks P((int nr));
213 void StopClocks P((void));
214 void ResetClocks P((void));
215 char *PGNDate P((void));
216 void SetGameInfo P((void));
217 int RegisterMove P((void));
218 void MakeRegisteredMove P((void));
219 void TruncateGame P((void));
220 int looking_at P((char *, int *, char *));
221 void CopyPlayerNameIntoFileName P((char **, char *));
222 char *SavePart P((char *));
223 int SaveGameOldStyle P((FILE *));
224 int SaveGamePGN P((FILE *));
225 int CheckFlags P((void));
226 long NextTickLength P((long));
227 void CheckTimeControl P((void));
228 void show_bytes P((FILE *, char *, int));
229 int string_to_rating P((char *str));
230 void ParseFeatures P((char* args, ChessProgramState *cps));
231 void InitBackEnd3 P((void));
232 void FeatureDone P((ChessProgramState* cps, int val));
233 void InitChessProgram P((ChessProgramState *cps, int setup));
234 void OutputKibitz(int window, char *text);
235 int PerpetualChase(int first, int last);
236 int EngineOutputIsUp();
237 void InitDrawingSizes(int x, int y);
238 void NextMatchGame P((void));
239 int NextTourneyGame P((int nr, int *swap));
240 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
241 FILE *WriteTourneyFile P((char *results, FILE *f));
242 void DisplayTwoMachinesTitle P(());
243 static void ExcludeClick P((int index));
244 void ToggleSecond P((void));
245 void PauseEngine P((ChessProgramState *cps));
246 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
247
248 #ifdef WIN32
249        extern void ConsoleCreate();
250 #endif
251
252 ChessProgramState *WhitePlayer();
253 int VerifyDisplayMode P(());
254
255 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
256 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
257 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
258 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
259 void ics_update_width P((int new_width));
260 extern char installDir[MSG_SIZ];
261 VariantClass startVariant; /* [HGM] nicks: initial variant */
262 Boolean abortMatch;
263
264 extern int tinyLayout, smallLayout;
265 ChessProgramStats programStats;
266 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
267 int endPV = -1;
268 static int exiting = 0; /* [HGM] moved to top */
269 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
270 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
271 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
272 int partnerHighlight[2];
273 Boolean partnerBoardValid = 0;
274 char partnerStatus[MSG_SIZ];
275 Boolean partnerUp;
276 Boolean originalFlip;
277 Boolean twoBoards = 0;
278 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
279 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
280 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
281 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
282 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
283 int opponentKibitzes;
284 int lastSavedGame; /* [HGM] save: ID of game */
285 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
286 extern int chatCount;
287 int chattingPartner;
288 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
289 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
290 char lastMsg[MSG_SIZ];
291 char lastTalker[MSG_SIZ];
292 ChessSquare pieceSweep = EmptySquare;
293 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
294 int promoDefaultAltered;
295 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
296 static int initPing = -1;
297 int border;       /* [HGM] width of board rim, needed to size seek graph  */
298 char bestMove[MSG_SIZ];
299 int solvingTime, totalTime;
300
301 /* States for ics_getting_history */
302 #define H_FALSE 0
303 #define H_REQUESTED 1
304 #define H_GOT_REQ_HEADER 2
305 #define H_GOT_UNREQ_HEADER 3
306 #define H_GETTING_MOVES 4
307 #define H_GOT_UNWANTED_HEADER 5
308
309 /* whosays values for GameEnds */
310 #define GE_ICS 0
311 #define GE_ENGINE 1
312 #define GE_PLAYER 2
313 #define GE_FILE 3
314 #define GE_XBOARD 4
315 #define GE_ENGINE1 5
316 #define GE_ENGINE2 6
317
318 /* Maximum number of games in a cmail message */
319 #define CMAIL_MAX_GAMES 20
320
321 /* Different types of move when calling RegisterMove */
322 #define CMAIL_MOVE   0
323 #define CMAIL_RESIGN 1
324 #define CMAIL_DRAW   2
325 #define CMAIL_ACCEPT 3
326
327 /* Different types of result to remember for each game */
328 #define CMAIL_NOT_RESULT 0
329 #define CMAIL_OLD_RESULT 1
330 #define CMAIL_NEW_RESULT 2
331
332 /* Telnet protocol constants */
333 #define TN_WILL 0373
334 #define TN_WONT 0374
335 #define TN_DO   0375
336 #define TN_DONT 0376
337 #define TN_IAC  0377
338 #define TN_ECHO 0001
339 #define TN_SGA  0003
340 #define TN_PORT 23
341
342 char*
343 safeStrCpy (char *dst, const char *src, size_t count)
344 { // [HGM] made safe
345   int i;
346   assert( dst != NULL );
347   assert( src != NULL );
348   assert( count > 0 );
349
350   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
351   if(  i == count && dst[count-1] != NULLCHAR)
352     {
353       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
354       if(appData.debugMode)
355         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
356     }
357
358   return dst;
359 }
360
361 /* Some compiler can't cast u64 to double
362  * This function do the job for us:
363
364  * We use the highest bit for cast, this only
365  * works if the highest bit is not
366  * in use (This should not happen)
367  *
368  * We used this for all compiler
369  */
370 double
371 u64ToDouble (u64 value)
372 {
373   double r;
374   u64 tmp = value & u64Const(0x7fffffffffffffff);
375   r = (double)(s64)tmp;
376   if (value & u64Const(0x8000000000000000))
377        r +=  9.2233720368547758080e18; /* 2^63 */
378  return r;
379 }
380
381 /* Fake up flags for now, as we aren't keeping track of castling
382    availability yet. [HGM] Change of logic: the flag now only
383    indicates the type of castlings allowed by the rule of the game.
384    The actual rights themselves are maintained in the array
385    castlingRights, as part of the game history, and are not probed
386    by this function.
387  */
388 int
389 PosFlags (index)
390 {
391   int flags = F_ALL_CASTLE_OK;
392   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
393   switch (gameInfo.variant) {
394   case VariantSuicide:
395     flags &= ~F_ALL_CASTLE_OK;
396   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
397     flags |= F_IGNORE_CHECK;
398   case VariantLosers:
399     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
400     break;
401   case VariantAtomic:
402     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
403     break;
404   case VariantKriegspiel:
405     flags |= F_KRIEGSPIEL_CAPTURE;
406     break;
407   case VariantCapaRandom:
408   case VariantFischeRandom:
409     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
410   case VariantNoCastle:
411   case VariantShatranj:
412   case VariantCourier:
413   case VariantMakruk:
414   case VariantASEAN:
415   case VariantGrand:
416     flags &= ~F_ALL_CASTLE_OK;
417     break;
418   case VariantChu:
419   case VariantChuChess:
420   case VariantLion:
421     flags |= F_NULL_MOVE;
422     break;
423   default:
424     break;
425   }
426   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
427   return flags;
428 }
429
430 FILE *gameFileFP, *debugFP, *serverFP;
431 char *currentDebugFile; // [HGM] debug split: to remember name
432
433 /*
434     [AS] Note: sometimes, the sscanf() function is used to parse the input
435     into a fixed-size buffer. Because of this, we must be prepared to
436     receive strings as long as the size of the input buffer, which is currently
437     set to 4K for Windows and 8K for the rest.
438     So, we must either allocate sufficiently large buffers here, or
439     reduce the size of the input buffer in the input reading part.
440 */
441
442 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
443 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
444 char thinkOutput1[MSG_SIZ*10];
445 char promoRestrict[MSG_SIZ];
446
447 ChessProgramState first, second, pairing;
448
449 /* premove variables */
450 int premoveToX = 0;
451 int premoveToY = 0;
452 int premoveFromX = 0;
453 int premoveFromY = 0;
454 int premovePromoChar = 0;
455 int gotPremove = 0;
456 Boolean alarmSounded;
457 /* end premove variables */
458
459 char *ics_prefix = "$";
460 enum ICS_TYPE ics_type = ICS_GENERIC;
461
462 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
463 int pauseExamForwardMostMove = 0;
464 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
465 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
466 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
467 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
468 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
469 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
470 int whiteFlag = FALSE, blackFlag = FALSE;
471 int userOfferedDraw = FALSE;
472 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
473 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
474 int cmailMoveType[CMAIL_MAX_GAMES];
475 long ics_clock_paused = 0;
476 ProcRef icsPR = NoProc, cmailPR = NoProc;
477 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
478 GameMode gameMode = BeginningOfGame;
479 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
480 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
481 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
482 int hiddenThinkOutputState = 0; /* [AS] */
483 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
484 int adjudicateLossPlies = 6;
485 char white_holding[64], black_holding[64];
486 TimeMark lastNodeCountTime;
487 long lastNodeCount=0;
488 int shiftKey, controlKey; // [HGM] set by mouse handler
489
490 int have_sent_ICS_logon = 0;
491 int movesPerSession;
492 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
493 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
494 Boolean adjustedClock;
495 long timeControl_2; /* [AS] Allow separate time controls */
496 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
497 long timeRemaining[2][MAX_MOVES];
498 int matchGame = 0, nextGame = 0, roundNr = 0;
499 Boolean waitingForGame = FALSE, startingEngine = FALSE;
500 TimeMark programStartTime, pauseStart;
501 char ics_handle[MSG_SIZ];
502 int have_set_title = 0;
503
504 /* animateTraining preserves the state of appData.animate
505  * when Training mode is activated. This allows the
506  * response to be animated when appData.animate == TRUE and
507  * appData.animateDragging == TRUE.
508  */
509 Boolean animateTraining;
510
511 GameInfo gameInfo;
512
513 AppData appData;
514
515 Board boards[MAX_MOVES];
516 /* [HGM] Following 7 needed for accurate legality tests: */
517 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
518 unsigned char initialRights[BOARD_FILES];
519 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
520 int   initialRulePlies, FENrulePlies;
521 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
522 int loadFlag = 0;
523 Boolean shuffleOpenings;
524 int mute; // mute all sounds
525
526 // [HGM] vari: next 12 to save and restore variations
527 #define MAX_VARIATIONS 10
528 int framePtr = MAX_MOVES-1; // points to free stack entry
529 int storedGames = 0;
530 int savedFirst[MAX_VARIATIONS];
531 int savedLast[MAX_VARIATIONS];
532 int savedFramePtr[MAX_VARIATIONS];
533 char *savedDetails[MAX_VARIATIONS];
534 ChessMove savedResult[MAX_VARIATIONS];
535
536 void PushTail P((int firstMove, int lastMove));
537 Boolean PopTail P((Boolean annotate));
538 void PushInner P((int firstMove, int lastMove));
539 void PopInner P((Boolean annotate));
540 void CleanupTail P((void));
541
542 ChessSquare  FIDEArray[2][BOARD_FILES] = {
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
546         BlackKing, BlackBishop, BlackKnight, BlackRook }
547 };
548
549 ChessSquare twoKingsArray[2][BOARD_FILES] = {
550     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
551         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
553         BlackKing, BlackKing, BlackKnight, BlackRook }
554 };
555
556 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
558         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
559     { BlackRook, BlackMan, BlackBishop, BlackQueen,
560         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
561 };
562
563 ChessSquare SpartanArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
566     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
567         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
568 };
569
570 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
571     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
574         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
575 };
576
577 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
578     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
579         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
581         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
582 };
583
584 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
585     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
586         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackMan, BlackFerz,
588         BlackKing, BlackMan, BlackKnight, BlackRook }
589 };
590
591 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
592     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
593         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
594     { BlackRook, BlackKnight, BlackMan, BlackFerz,
595         BlackKing, BlackMan, BlackKnight, BlackRook }
596 };
597
598 ChessSquare  lionArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
600         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
601     { BlackRook, BlackLion, BlackBishop, BlackQueen,
602         BlackKing, BlackBishop, BlackKnight, BlackRook }
603 };
604
605
606 #if (BOARD_FILES>=10)
607 ChessSquare ShogiArray[2][BOARD_FILES] = {
608     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
609         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
610     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
611         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
612 };
613
614 ChessSquare XiangqiArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
616         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
618         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
619 };
620
621 ChessSquare CapablancaArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
623         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
625         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
626 };
627
628 ChessSquare GreatArray[2][BOARD_FILES] = {
629     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
630         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
631     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
632         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
633 };
634
635 ChessSquare JanusArray[2][BOARD_FILES] = {
636     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
637         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
638     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
639         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
640 };
641
642 ChessSquare GrandArray[2][BOARD_FILES] = {
643     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
644         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
645     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
646         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
647 };
648
649 ChessSquare ChuChessArray[2][BOARD_FILES] = {
650     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
651         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
652     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
653         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
654 };
655
656 #ifdef GOTHIC
657 ChessSquare GothicArray[2][BOARD_FILES] = {
658     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
659         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
660     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
661         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
662 };
663 #else // !GOTHIC
664 #define GothicArray CapablancaArray
665 #endif // !GOTHIC
666
667 #ifdef FALCON
668 ChessSquare FalconArray[2][BOARD_FILES] = {
669     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
670         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
671     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
672         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
673 };
674 #else // !FALCON
675 #define FalconArray CapablancaArray
676 #endif // !FALCON
677
678 #else // !(BOARD_FILES>=10)
679 #define XiangqiPosition FIDEArray
680 #define CapablancaArray FIDEArray
681 #define GothicArray FIDEArray
682 #define GreatArray FIDEArray
683 #endif // !(BOARD_FILES>=10)
684
685 #if (BOARD_FILES>=12)
686 ChessSquare CourierArray[2][BOARD_FILES] = {
687     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
688         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
689     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
690         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
691 };
692 ChessSquare ChuArray[6][BOARD_FILES] = {
693     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
694       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
695     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
696       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
697     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
698       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
699     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
700       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
701     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
702       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
703     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
704       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
705 };
706 #else // !(BOARD_FILES>=12)
707 #define CourierArray CapablancaArray
708 #define ChuArray CapablancaArray
709 #endif // !(BOARD_FILES>=12)
710
711
712 Board initialPosition;
713
714
715 /* Convert str to a rating. Checks for special cases of "----",
716
717    "++++", etc. Also strips ()'s */
718 int
719 string_to_rating (char *str)
720 {
721   while(*str && !isdigit(*str)) ++str;
722   if (!*str)
723     return 0;   /* One of the special "no rating" cases */
724   else
725     return atoi(str);
726 }
727
728 void
729 ClearProgramStats ()
730 {
731     /* Init programStats */
732     programStats.movelist[0] = 0;
733     programStats.depth = 0;
734     programStats.nr_moves = 0;
735     programStats.moves_left = 0;
736     programStats.nodes = 0;
737     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
738     programStats.score = 0;
739     programStats.got_only_move = 0;
740     programStats.got_fail = 0;
741     programStats.line_is_book = 0;
742 }
743
744 void
745 CommonEngineInit ()
746 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754
755     first.other = &second;
756     second.other = &first;
757
758     { float norm = 1;
759         if(appData.timeOddsMode) {
760             norm = appData.timeOdds[0];
761             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
762         }
763         first.timeOdds  = appData.timeOdds[0]/norm;
764         second.timeOdds = appData.timeOdds[1]/norm;
765     }
766
767     if(programVersion) free(programVersion);
768     if (appData.noChessProgram) {
769         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
770         sprintf(programVersion, "%s", PACKAGE_STRING);
771     } else {
772       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
773       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
774       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
775     }
776 }
777
778 void
779 UnloadEngine (ChessProgramState *cps)
780 {
781         /* Kill off first chess program */
782         if (cps->isr != NULL)
783           RemoveInputSource(cps->isr);
784         cps->isr = NULL;
785
786         if (cps->pr != NoProc) {
787             ExitAnalyzeMode();
788             DoSleep( appData.delayBeforeQuit );
789             SendToProgram("quit\n", cps);
790             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
791         }
792         cps->pr = NoProc;
793         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
794 }
795
796 void
797 ClearOptions (ChessProgramState *cps)
798 {
799     int i;
800     cps->nrOptions = cps->comboCnt = 0;
801     for(i=0; i<MAX_OPTIONS; i++) {
802         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
803         cps->option[i].textValue = 0;
804     }
805 }
806
807 char *engineNames[] = {
808   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
809      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
810 N_("first"),
811   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
812      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
813 N_("second")
814 };
815
816 void
817 InitEngine (ChessProgramState *cps, int n)
818 {   // [HGM] all engine initialiation put in a function that does one engine
819
820     ClearOptions(cps);
821
822     cps->which = engineNames[n];
823     cps->maybeThinking = FALSE;
824     cps->pr = NoProc;
825     cps->isr = NULL;
826     cps->sendTime = 2;
827     cps->sendDrawOffers = 1;
828
829     cps->program = appData.chessProgram[n];
830     cps->host = appData.host[n];
831     cps->dir = appData.directory[n];
832     cps->initString = appData.engInitString[n];
833     cps->computerString = appData.computerString[n];
834     cps->useSigint  = TRUE;
835     cps->useSigterm = TRUE;
836     cps->reuse = appData.reuse[n];
837     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
838     cps->useSetboard = FALSE;
839     cps->useSAN = FALSE;
840     cps->usePing = FALSE;
841     cps->lastPing = 0;
842     cps->lastPong = 0;
843     cps->usePlayother = FALSE;
844     cps->useColors = TRUE;
845     cps->useUsermove = FALSE;
846     cps->sendICS = FALSE;
847     cps->sendName = appData.icsActive;
848     cps->sdKludge = FALSE;
849     cps->stKludge = FALSE;
850     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
851     TidyProgramName(cps->program, cps->host, cps->tidy);
852     cps->matchWins = 0;
853     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
854     cps->analysisSupport = 2; /* detect */
855     cps->analyzing = FALSE;
856     cps->initDone = FALSE;
857     cps->reload = FALSE;
858     cps->pseudo = appData.pseudo[n];
859
860     /* New features added by Tord: */
861     cps->useFEN960 = FALSE;
862     cps->useOOCastle = TRUE;
863     /* End of new features added by Tord. */
864     cps->fenOverride  = appData.fenOverride[n];
865
866     /* [HGM] time odds: set factor for each machine */
867     cps->timeOdds  = appData.timeOdds[n];
868
869     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
870     cps->accumulateTC = appData.accumulateTC[n];
871     cps->maxNrOfSessions = 1;
872
873     /* [HGM] debug */
874     cps->debug = FALSE;
875
876     cps->drawDepth = appData.drawDepth[n];
877     cps->supportsNPS = UNKNOWN;
878     cps->memSize = FALSE;
879     cps->maxCores = FALSE;
880     ASSIGN(cps->egtFormats, "");
881
882     /* [HGM] options */
883     cps->optionSettings  = appData.engOptions[n];
884
885     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
886     cps->isUCI = appData.isUCI[n]; /* [AS] */
887     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
888     cps->highlight = 0;
889
890     if (appData.protocolVersion[n] > PROTOVER
891         || appData.protocolVersion[n] < 1)
892       {
893         char buf[MSG_SIZ];
894         int len;
895
896         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
897                        appData.protocolVersion[n]);
898         if( (len >= MSG_SIZ) && appData.debugMode )
899           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
900
901         DisplayFatalError(buf, 0, 2);
902       }
903     else
904       {
905         cps->protocolVersion = appData.protocolVersion[n];
906       }
907
908     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
909     ParseFeatures(appData.featureDefaults, cps);
910 }
911
912 ChessProgramState *savCps;
913
914 GameMode oldMode;
915
916 void
917 LoadEngine ()
918 {
919     int i;
920     if(WaitForEngine(savCps, LoadEngine)) return;
921     CommonEngineInit(); // recalculate time odds
922     if(gameInfo.variant != StringToVariant(appData.variant)) {
923         // we changed variant when loading the engine; this forces us to reset
924         Reset(TRUE, savCps != &first);
925         oldMode = BeginningOfGame; // to prevent restoring old mode
926     }
927     InitChessProgram(savCps, FALSE);
928     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
929     DisplayMessage("", "");
930     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
931     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
932     ThawUI();
933     SetGNUMode();
934     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
935 }
936
937 void
938 ReplaceEngine (ChessProgramState *cps, int n)
939 {
940     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
941     keepInfo = 1;
942     if(oldMode != BeginningOfGame) EditGameEvent();
943     keepInfo = 0;
944     UnloadEngine(cps);
945     appData.noChessProgram = FALSE;
946     appData.clockMode = TRUE;
947     InitEngine(cps, n);
948     UpdateLogos(TRUE);
949     if(n) return; // only startup first engine immediately; second can wait
950     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
951     LoadEngine();
952 }
953
954 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
955 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
956
957 static char resetOptions[] =
958         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
959         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
960         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
961         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
962
963 void
964 FloatToFront(char **list, char *engineLine)
965 {
966     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
967     int i=0;
968     if(appData.recentEngines <= 0) return;
969     TidyProgramName(engineLine, "localhost", tidy+1);
970     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
971     strncpy(buf+1, *list, MSG_SIZ-50);
972     if(p = strstr(buf, tidy)) { // tidy name appears in list
973         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
974         while(*p++ = *++q); // squeeze out
975     }
976     strcat(tidy, buf+1); // put list behind tidy name
977     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
978     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
979     ASSIGN(*list, tidy+1);
980 }
981
982 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
983
984 void
985 Load (ChessProgramState *cps, int i)
986 {
987     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
988     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
989         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
990         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
991         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
992         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
993         appData.firstProtocolVersion = PROTOVER;
994         ParseArgsFromString(buf);
995         SwapEngines(i);
996         ReplaceEngine(cps, i);
997         FloatToFront(&appData.recentEngineList, engineLine);
998         return;
999     }
1000     p = engineName;
1001     while(q = strchr(p, SLASH)) p = q+1;
1002     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1003     if(engineDir[0] != NULLCHAR) {
1004         ASSIGN(appData.directory[i], engineDir); p = engineName;
1005     } else if(p != engineName) { // derive directory from engine path, when not given
1006         p[-1] = 0;
1007         ASSIGN(appData.directory[i], engineName);
1008         p[-1] = SLASH;
1009         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1010     } else { ASSIGN(appData.directory[i], "."); }
1011     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1012     if(params[0]) {
1013         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1014         snprintf(command, MSG_SIZ, "%s %s", p, params);
1015         p = command;
1016     }
1017     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1018     ASSIGN(appData.chessProgram[i], p);
1019     appData.isUCI[i] = isUCI;
1020     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1021     appData.hasOwnBookUCI[i] = hasBook;
1022     if(!nickName[0]) useNick = FALSE;
1023     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1024     if(addToList) {
1025         int len;
1026         char quote;
1027         q = firstChessProgramNames;
1028         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1029         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1030         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1031                         quote, p, quote, appData.directory[i],
1032                         useNick ? " -fn \"" : "",
1033                         useNick ? nickName : "",
1034                         useNick ? "\"" : "",
1035                         v1 ? " -firstProtocolVersion 1" : "",
1036                         hasBook ? "" : " -fNoOwnBookUCI",
1037                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1038                         storeVariant ? " -variant " : "",
1039                         storeVariant ? VariantName(gameInfo.variant) : "");
1040         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1041         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1042         if(insert != q) insert[-1] = NULLCHAR;
1043         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1044         if(q)   free(q);
1045         FloatToFront(&appData.recentEngineList, buf);
1046     }
1047     ReplaceEngine(cps, i);
1048 }
1049
1050 void
1051 InitTimeControls ()
1052 {
1053     int matched, min, sec;
1054     /*
1055      * Parse timeControl resource
1056      */
1057     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1058                           appData.movesPerSession)) {
1059         char buf[MSG_SIZ];
1060         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1061         DisplayFatalError(buf, 0, 2);
1062     }
1063
1064     /*
1065      * Parse searchTime resource
1066      */
1067     if (*appData.searchTime != NULLCHAR) {
1068         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1069         if (matched == 1) {
1070             searchTime = min * 60;
1071         } else if (matched == 2) {
1072             searchTime = min * 60 + sec;
1073         } else {
1074             char buf[MSG_SIZ];
1075             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1076             DisplayFatalError(buf, 0, 2);
1077         }
1078     }
1079 }
1080
1081 void
1082 InitBackEnd1 ()
1083 {
1084
1085     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1086     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1087
1088     GetTimeMark(&programStartTime);
1089     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1090     appData.seedBase = random() + (random()<<15);
1091     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1092
1093     ClearProgramStats();
1094     programStats.ok_to_send = 1;
1095     programStats.seen_stat = 0;
1096
1097     /*
1098      * Initialize game list
1099      */
1100     ListNew(&gameList);
1101
1102
1103     /*
1104      * Internet chess server status
1105      */
1106     if (appData.icsActive) {
1107         appData.matchMode = FALSE;
1108         appData.matchGames = 0;
1109 #if ZIPPY
1110         appData.noChessProgram = !appData.zippyPlay;
1111 #else
1112         appData.zippyPlay = FALSE;
1113         appData.zippyTalk = FALSE;
1114         appData.noChessProgram = TRUE;
1115 #endif
1116         if (*appData.icsHelper != NULLCHAR) {
1117             appData.useTelnet = TRUE;
1118             appData.telnetProgram = appData.icsHelper;
1119         }
1120     } else {
1121         appData.zippyTalk = appData.zippyPlay = FALSE;
1122     }
1123
1124     /* [AS] Initialize pv info list [HGM] and game state */
1125     {
1126         int i, j;
1127
1128         for( i=0; i<=framePtr; i++ ) {
1129             pvInfoList[i].depth = -1;
1130             boards[i][EP_STATUS] = EP_NONE;
1131             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1132         }
1133     }
1134
1135     InitTimeControls();
1136
1137     /* [AS] Adjudication threshold */
1138     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1139
1140     InitEngine(&first, 0);
1141     InitEngine(&second, 1);
1142     CommonEngineInit();
1143
1144     pairing.which = "pairing"; // pairing engine
1145     pairing.pr = NoProc;
1146     pairing.isr = NULL;
1147     pairing.program = appData.pairingEngine;
1148     pairing.host = "localhost";
1149     pairing.dir = ".";
1150
1151     if (appData.icsActive) {
1152         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1153     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1154         appData.clockMode = FALSE;
1155         first.sendTime = second.sendTime = 0;
1156     }
1157
1158 #if ZIPPY
1159     /* Override some settings from environment variables, for backward
1160        compatibility.  Unfortunately it's not feasible to have the env
1161        vars just set defaults, at least in xboard.  Ugh.
1162     */
1163     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1164       ZippyInit();
1165     }
1166 #endif
1167
1168     if (!appData.icsActive) {
1169       char buf[MSG_SIZ];
1170       int len;
1171
1172       /* Check for variants that are supported only in ICS mode,
1173          or not at all.  Some that are accepted here nevertheless
1174          have bugs; see comments below.
1175       */
1176       VariantClass variant = StringToVariant(appData.variant);
1177       switch (variant) {
1178       case VariantBughouse:     /* need four players and two boards */
1179       case VariantKriegspiel:   /* need to hide pieces and move details */
1180         /* case VariantFischeRandom: (Fabien: moved below) */
1181         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1182         if( (len >= MSG_SIZ) && appData.debugMode )
1183           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1184
1185         DisplayFatalError(buf, 0, 2);
1186         return;
1187
1188       case VariantUnknown:
1189       case VariantLoadable:
1190       case Variant29:
1191       case Variant30:
1192       case Variant31:
1193       case Variant32:
1194       case Variant33:
1195       case Variant34:
1196       case Variant35:
1197       case Variant36:
1198       default:
1199         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1200         if( (len >= MSG_SIZ) && appData.debugMode )
1201           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1202
1203         DisplayFatalError(buf, 0, 2);
1204         return;
1205
1206       case VariantNormal:     /* definitely works! */
1207         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1208           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1209           return;
1210         }
1211       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1212       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1213       case VariantGothic:     /* [HGM] should work */
1214       case VariantCapablanca: /* [HGM] should work */
1215       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1216       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1217       case VariantChu:        /* [HGM] experimental */
1218       case VariantKnightmate: /* [HGM] should work */
1219       case VariantCylinder:   /* [HGM] untested */
1220       case VariantFalcon:     /* [HGM] untested */
1221       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1222                                  offboard interposition not understood */
1223       case VariantWildCastle: /* pieces not automatically shuffled */
1224       case VariantNoCastle:   /* pieces not automatically shuffled */
1225       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1226       case VariantLosers:     /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantSuicide:    /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantGiveaway:   /* should work except for win condition,
1231                                  and doesn't know captures are mandatory */
1232       case VariantTwoKings:   /* should work */
1233       case VariantAtomic:     /* should work except for win condition */
1234       case Variant3Check:     /* should work except for win condition */
1235       case VariantShatranj:   /* should work except for all win conditions */
1236       case VariantMakruk:     /* should work except for draw countdown */
1237       case VariantASEAN :     /* should work except for draw countdown */
1238       case VariantBerolina:   /* might work if TestLegality is off */
1239       case VariantCapaRandom: /* should work */
1240       case VariantJanus:      /* should work */
1241       case VariantSuper:      /* experimental */
1242       case VariantGreat:      /* experimental, requires legality testing to be off */
1243       case VariantSChess:     /* S-Chess, should work */
1244       case VariantGrand:      /* should work */
1245       case VariantSpartan:    /* should work */
1246       case VariantLion:       /* should work */
1247       case VariantChuChess:   /* should work */
1248         break;
1249       }
1250     }
1251
1252 }
1253
1254 int
1255 NextIntegerFromString (char ** str, long * value)
1256 {
1257     int result = -1;
1258     char * s = *str;
1259
1260     while( *s == ' ' || *s == '\t' ) {
1261         s++;
1262     }
1263
1264     *value = 0;
1265
1266     if( *s >= '0' && *s <= '9' ) {
1267         while( *s >= '0' && *s <= '9' ) {
1268             *value = *value * 10 + (*s - '0');
1269             s++;
1270         }
1271
1272         result = 0;
1273     }
1274
1275     *str = s;
1276
1277     return result;
1278 }
1279
1280 int
1281 NextTimeControlFromString (char ** str, long * value)
1282 {
1283     long temp;
1284     int result = NextIntegerFromString( str, &temp );
1285
1286     if( result == 0 ) {
1287         *value = temp * 60; /* Minutes */
1288         if( **str == ':' ) {
1289             (*str)++;
1290             result = NextIntegerFromString( str, &temp );
1291             *value += temp; /* Seconds */
1292         }
1293     }
1294
1295     return result;
1296 }
1297
1298 int
1299 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1300 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1301     int result = -1, type = 0; long temp, temp2;
1302
1303     if(**str != ':') return -1; // old params remain in force!
1304     (*str)++;
1305     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1306     if( NextIntegerFromString( str, &temp ) ) return -1;
1307     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1308
1309     if(**str != '/') {
1310         /* time only: incremental or sudden-death time control */
1311         if(**str == '+') { /* increment follows; read it */
1312             (*str)++;
1313             if(**str == '!') type = *(*str)++; // Bronstein TC
1314             if(result = NextIntegerFromString( str, &temp2)) return -1;
1315             *inc = temp2 * 1000;
1316             if(**str == '.') { // read fraction of increment
1317                 char *start = ++(*str);
1318                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1319                 temp2 *= 1000;
1320                 while(start++ < *str) temp2 /= 10;
1321                 *inc += temp2;
1322             }
1323         } else *inc = 0;
1324         *moves = 0; *tc = temp * 1000; *incType = type;
1325         return 0;
1326     }
1327
1328     (*str)++; /* classical time control */
1329     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1330
1331     if(result == 0) {
1332         *moves = temp;
1333         *tc    = temp2 * 1000;
1334         *inc   = 0;
1335         *incType = type;
1336     }
1337     return result;
1338 }
1339
1340 int
1341 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1342 {   /* [HGM] get time to add from the multi-session time-control string */
1343     int incType, moves=1; /* kludge to force reading of first session */
1344     long time, increment;
1345     char *s = tcString;
1346
1347     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1348     do {
1349         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1350         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1351         if(movenr == -1) return time;    /* last move before new session     */
1352         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1353         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1354         if(!moves) return increment;     /* current session is incremental   */
1355         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1356     } while(movenr >= -1);               /* try again for next session       */
1357
1358     return 0; // no new time quota on this move
1359 }
1360
1361 int
1362 ParseTimeControl (char *tc, float ti, int mps)
1363 {
1364   long tc1;
1365   long tc2;
1366   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1367   int min, sec=0;
1368
1369   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1370   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1371       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1372   if(ti > 0) {
1373
1374     if(mps)
1375       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1376     else
1377       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1378   } else {
1379     if(mps)
1380       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1381     else
1382       snprintf(buf, MSG_SIZ, ":%s", mytc);
1383   }
1384   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1385
1386   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1387     return FALSE;
1388   }
1389
1390   if( *tc == '/' ) {
1391     /* Parse second time control */
1392     tc++;
1393
1394     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1395       return FALSE;
1396     }
1397
1398     if( tc2 == 0 ) {
1399       return FALSE;
1400     }
1401
1402     timeControl_2 = tc2 * 1000;
1403   }
1404   else {
1405     timeControl_2 = 0;
1406   }
1407
1408   if( tc1 == 0 ) {
1409     return FALSE;
1410   }
1411
1412   timeControl = tc1 * 1000;
1413
1414   if (ti >= 0) {
1415     timeIncrement = ti * 1000;  /* convert to ms */
1416     movesPerSession = 0;
1417   } else {
1418     timeIncrement = 0;
1419     movesPerSession = mps;
1420   }
1421   return TRUE;
1422 }
1423
1424 void
1425 InitBackEnd2 ()
1426 {
1427     if (appData.debugMode) {
1428 #    ifdef __GIT_VERSION
1429       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1430 #    else
1431       fprintf(debugFP, "Version: %s\n", programVersion);
1432 #    endif
1433     }
1434     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1435
1436     set_cont_sequence(appData.wrapContSeq);
1437     if (appData.matchGames > 0) {
1438         appData.matchMode = TRUE;
1439     } else if (appData.matchMode) {
1440         appData.matchGames = 1;
1441     }
1442     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1443         appData.matchGames = appData.sameColorGames;
1444     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1445         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1446         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1447     }
1448     Reset(TRUE, FALSE);
1449     if (appData.noChessProgram || first.protocolVersion == 1) {
1450       InitBackEnd3();
1451     } else {
1452       /* kludge: allow timeout for initial "feature" commands */
1453       FreezeUI();
1454       DisplayMessage("", _("Starting chess program"));
1455       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1456     }
1457 }
1458
1459 int
1460 CalculateIndex (int index, int gameNr)
1461 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1462     int res;
1463     if(index > 0) return index; // fixed nmber
1464     if(index == 0) return 1;
1465     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1466     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1467     return res;
1468 }
1469
1470 int
1471 LoadGameOrPosition (int gameNr)
1472 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1473     if (*appData.loadGameFile != NULLCHAR) {
1474         if (!LoadGameFromFile(appData.loadGameFile,
1475                 CalculateIndex(appData.loadGameIndex, gameNr),
1476                               appData.loadGameFile, FALSE)) {
1477             DisplayFatalError(_("Bad game file"), 0, 1);
1478             return 0;
1479         }
1480     } else if (*appData.loadPositionFile != NULLCHAR) {
1481         if (!LoadPositionFromFile(appData.loadPositionFile,
1482                 CalculateIndex(appData.loadPositionIndex, gameNr),
1483                                   appData.loadPositionFile)) {
1484             DisplayFatalError(_("Bad position file"), 0, 1);
1485             return 0;
1486         }
1487     }
1488     return 1;
1489 }
1490
1491 void
1492 ReserveGame (int gameNr, char resChar)
1493 {
1494     FILE *tf = fopen(appData.tourneyFile, "r+");
1495     char *p, *q, c, buf[MSG_SIZ];
1496     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1497     safeStrCpy(buf, lastMsg, MSG_SIZ);
1498     DisplayMessage(_("Pick new game"), "");
1499     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1500     ParseArgsFromFile(tf);
1501     p = q = appData.results;
1502     if(appData.debugMode) {
1503       char *r = appData.participants;
1504       fprintf(debugFP, "results = '%s'\n", p);
1505       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1506       fprintf(debugFP, "\n");
1507     }
1508     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1509     nextGame = q - p;
1510     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1511     safeStrCpy(q, p, strlen(p) + 2);
1512     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1513     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1514     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1515         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1516         q[nextGame] = '*';
1517     }
1518     fseek(tf, -(strlen(p)+4), SEEK_END);
1519     c = fgetc(tf);
1520     if(c != '"') // depending on DOS or Unix line endings we can be one off
1521          fseek(tf, -(strlen(p)+2), SEEK_END);
1522     else fseek(tf, -(strlen(p)+3), SEEK_END);
1523     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1524     DisplayMessage(buf, "");
1525     free(p); appData.results = q;
1526     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1527        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1528       int round = appData.defaultMatchGames * appData.tourneyType;
1529       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1530          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1531         UnloadEngine(&first);  // next game belongs to other pairing;
1532         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1533     }
1534     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1535 }
1536
1537 void
1538 MatchEvent (int mode)
1539 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1540         int dummy;
1541         if(matchMode) { // already in match mode: switch it off
1542             abortMatch = TRUE;
1543             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1544             return;
1545         }
1546 //      if(gameMode != BeginningOfGame) {
1547 //          DisplayError(_("You can only start a match from the initial position."), 0);
1548 //          return;
1549 //      }
1550         abortMatch = FALSE;
1551         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1552         /* Set up machine vs. machine match */
1553         nextGame = 0;
1554         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1555         if(appData.tourneyFile[0]) {
1556             ReserveGame(-1, 0);
1557             if(nextGame > appData.matchGames) {
1558                 char buf[MSG_SIZ];
1559                 if(strchr(appData.results, '*') == NULL) {
1560                     FILE *f;
1561                     appData.tourneyCycles++;
1562                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1563                         fclose(f);
1564                         NextTourneyGame(-1, &dummy);
1565                         ReserveGame(-1, 0);
1566                         if(nextGame <= appData.matchGames) {
1567                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1568                             matchMode = mode;
1569                             ScheduleDelayedEvent(NextMatchGame, 10000);
1570                             return;
1571                         }
1572                     }
1573                 }
1574                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1575                 DisplayError(buf, 0);
1576                 appData.tourneyFile[0] = 0;
1577                 return;
1578             }
1579         } else
1580         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1581             DisplayFatalError(_("Can't have a match with no chess programs"),
1582                               0, 2);
1583             return;
1584         }
1585         matchMode = mode;
1586         matchGame = roundNr = 1;
1587         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1588         NextMatchGame();
1589 }
1590
1591 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1592
1593 void
1594 InitBackEnd3 P((void))
1595 {
1596     GameMode initialMode;
1597     char buf[MSG_SIZ];
1598     int err, len;
1599
1600     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1601        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1602         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1603        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1604        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1605         char c, *q = first.variants, *p = strchr(q, ',');
1606         if(p) *p = NULLCHAR;
1607         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1608             int w, h, s;
1609             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1610                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1611             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1612             Reset(TRUE, FALSE);         // and re-initialize
1613         }
1614         if(p) *p = ',';
1615     }
1616
1617     InitChessProgram(&first, startedFromSetupPosition);
1618
1619     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1620         free(programVersion);
1621         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1622         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1623         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1624     }
1625
1626     if (appData.icsActive) {
1627 #ifdef WIN32
1628         /* [DM] Make a console window if needed [HGM] merged ifs */
1629         ConsoleCreate();
1630 #endif
1631         err = establish();
1632         if (err != 0)
1633           {
1634             if (*appData.icsCommPort != NULLCHAR)
1635               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1636                              appData.icsCommPort);
1637             else
1638               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1639                         appData.icsHost, appData.icsPort);
1640
1641             if( (len >= MSG_SIZ) && appData.debugMode )
1642               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1643
1644             DisplayFatalError(buf, err, 1);
1645             return;
1646         }
1647         SetICSMode();
1648         telnetISR =
1649           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1650         fromUserISR =
1651           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1652         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1653             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1654     } else if (appData.noChessProgram) {
1655         SetNCPMode();
1656     } else {
1657         SetGNUMode();
1658     }
1659
1660     if (*appData.cmailGameName != NULLCHAR) {
1661         SetCmailMode();
1662         OpenLoopback(&cmailPR);
1663         cmailISR =
1664           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1665     }
1666
1667     ThawUI();
1668     DisplayMessage("", "");
1669     if (StrCaseCmp(appData.initialMode, "") == 0) {
1670       initialMode = BeginningOfGame;
1671       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1672         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1673         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1674         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1675         ModeHighlight();
1676       }
1677     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1678       initialMode = TwoMachinesPlay;
1679     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1680       initialMode = AnalyzeFile;
1681     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1682       initialMode = AnalyzeMode;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1684       initialMode = MachinePlaysWhite;
1685     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1686       initialMode = MachinePlaysBlack;
1687     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1688       initialMode = EditGame;
1689     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1690       initialMode = EditPosition;
1691     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1692       initialMode = Training;
1693     } else {
1694       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1695       if( (len >= MSG_SIZ) && appData.debugMode )
1696         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1697
1698       DisplayFatalError(buf, 0, 2);
1699       return;
1700     }
1701
1702     if (appData.matchMode) {
1703         if(appData.tourneyFile[0]) { // start tourney from command line
1704             FILE *f;
1705             if(f = fopen(appData.tourneyFile, "r")) {
1706                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1707                 fclose(f);
1708                 appData.clockMode = TRUE;
1709                 SetGNUMode();
1710             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1711         }
1712         MatchEvent(TRUE);
1713     } else if (*appData.cmailGameName != NULLCHAR) {
1714         /* Set up cmail mode */
1715         ReloadCmailMsgEvent(TRUE);
1716     } else {
1717         /* Set up other modes */
1718         if (initialMode == AnalyzeFile) {
1719           if (*appData.loadGameFile == NULLCHAR) {
1720             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1721             return;
1722           }
1723         }
1724         if (*appData.loadGameFile != NULLCHAR) {
1725             (void) LoadGameFromFile(appData.loadGameFile,
1726                                     appData.loadGameIndex,
1727                                     appData.loadGameFile, TRUE);
1728         } else if (*appData.loadPositionFile != NULLCHAR) {
1729             (void) LoadPositionFromFile(appData.loadPositionFile,
1730                                         appData.loadPositionIndex,
1731                                         appData.loadPositionFile);
1732             /* [HGM] try to make self-starting even after FEN load */
1733             /* to allow automatic setup of fairy variants with wtm */
1734             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1735                 gameMode = BeginningOfGame;
1736                 setboardSpoiledMachineBlack = 1;
1737             }
1738             /* [HGM] loadPos: make that every new game uses the setup */
1739             /* from file as long as we do not switch variant          */
1740             if(!blackPlaysFirst) {
1741                 startedFromPositionFile = TRUE;
1742                 CopyBoard(filePosition, boards[0]);
1743                 CopyBoard(initialPosition, boards[0]);
1744             }
1745         }
1746         if (initialMode == AnalyzeMode) {
1747           if (appData.noChessProgram) {
1748             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1749             return;
1750           }
1751           if (appData.icsActive) {
1752             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1753             return;
1754           }
1755           AnalyzeModeEvent();
1756         } else if (initialMode == AnalyzeFile) {
1757           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1758           ShowThinkingEvent();
1759           AnalyzeFileEvent();
1760           AnalysisPeriodicEvent(1);
1761         } else if (initialMode == MachinePlaysWhite) {
1762           if (appData.noChessProgram) {
1763             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1764                               0, 2);
1765             return;
1766           }
1767           if (appData.icsActive) {
1768             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1769                               0, 2);
1770             return;
1771           }
1772           MachineWhiteEvent();
1773         } else if (initialMode == MachinePlaysBlack) {
1774           if (appData.noChessProgram) {
1775             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1776                               0, 2);
1777             return;
1778           }
1779           if (appData.icsActive) {
1780             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1781                               0, 2);
1782             return;
1783           }
1784           MachineBlackEvent();
1785         } else if (initialMode == TwoMachinesPlay) {
1786           if (appData.noChessProgram) {
1787             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1788                               0, 2);
1789             return;
1790           }
1791           if (appData.icsActive) {
1792             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1793                               0, 2);
1794             return;
1795           }
1796           TwoMachinesEvent();
1797         } else if (initialMode == EditGame) {
1798           EditGameEvent();
1799         } else if (initialMode == EditPosition) {
1800           EditPositionEvent();
1801         } else if (initialMode == Training) {
1802           if (*appData.loadGameFile == NULLCHAR) {
1803             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1804             return;
1805           }
1806           TrainingEvent();
1807         }
1808     }
1809 }
1810
1811 void
1812 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1813 {
1814     DisplayBook(current+1);
1815
1816     MoveHistorySet( movelist, first, last, current, pvInfoList );
1817
1818     EvalGraphSet( first, last, current, pvInfoList );
1819
1820     MakeEngineOutputTitle();
1821 }
1822
1823 /*
1824  * Establish will establish a contact to a remote host.port.
1825  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1826  *  used to talk to the host.
1827  * Returns 0 if okay, error code if not.
1828  */
1829 int
1830 establish ()
1831 {
1832     char buf[MSG_SIZ];
1833
1834     if (*appData.icsCommPort != NULLCHAR) {
1835         /* Talk to the host through a serial comm port */
1836         return OpenCommPort(appData.icsCommPort, &icsPR);
1837
1838     } else if (*appData.gateway != NULLCHAR) {
1839         if (*appData.remoteShell == NULLCHAR) {
1840             /* Use the rcmd protocol to run telnet program on a gateway host */
1841             snprintf(buf, sizeof(buf), "%s %s %s",
1842                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1843             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1844
1845         } else {
1846             /* Use the rsh program to run telnet program on a gateway host */
1847             if (*appData.remoteUser == NULLCHAR) {
1848                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1849                         appData.gateway, appData.telnetProgram,
1850                         appData.icsHost, appData.icsPort);
1851             } else {
1852                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1853                         appData.remoteShell, appData.gateway,
1854                         appData.remoteUser, appData.telnetProgram,
1855                         appData.icsHost, appData.icsPort);
1856             }
1857             return StartChildProcess(buf, "", &icsPR);
1858
1859         }
1860     } else if (appData.useTelnet) {
1861         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1862
1863     } else {
1864         /* TCP socket interface differs somewhat between
1865            Unix and NT; handle details in the front end.
1866            */
1867         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1868     }
1869 }
1870
1871 void
1872 EscapeExpand (char *p, char *q)
1873 {       // [HGM] initstring: routine to shape up string arguments
1874         while(*p++ = *q++) if(p[-1] == '\\')
1875             switch(*q++) {
1876                 case 'n': p[-1] = '\n'; break;
1877                 case 'r': p[-1] = '\r'; break;
1878                 case 't': p[-1] = '\t'; break;
1879                 case '\\': p[-1] = '\\'; break;
1880                 case 0: *p = 0; return;
1881                 default: p[-1] = q[-1]; break;
1882             }
1883 }
1884
1885 void
1886 show_bytes (FILE *fp, char *buf, int count)
1887 {
1888     while (count--) {
1889         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1890             fprintf(fp, "\\%03o", *buf & 0xff);
1891         } else {
1892             putc(*buf, fp);
1893         }
1894         buf++;
1895     }
1896     fflush(fp);
1897 }
1898
1899 /* Returns an errno value */
1900 int
1901 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1902 {
1903     char buf[8192], *p, *q, *buflim;
1904     int left, newcount, outcount;
1905
1906     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1907         *appData.gateway != NULLCHAR) {
1908         if (appData.debugMode) {
1909             fprintf(debugFP, ">ICS: ");
1910             show_bytes(debugFP, message, count);
1911             fprintf(debugFP, "\n");
1912         }
1913         return OutputToProcess(pr, message, count, outError);
1914     }
1915
1916     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1917     p = message;
1918     q = buf;
1919     left = count;
1920     newcount = 0;
1921     while (left) {
1922         if (q >= buflim) {
1923             if (appData.debugMode) {
1924                 fprintf(debugFP, ">ICS: ");
1925                 show_bytes(debugFP, buf, newcount);
1926                 fprintf(debugFP, "\n");
1927             }
1928             outcount = OutputToProcess(pr, buf, newcount, outError);
1929             if (outcount < newcount) return -1; /* to be sure */
1930             q = buf;
1931             newcount = 0;
1932         }
1933         if (*p == '\n') {
1934             *q++ = '\r';
1935             newcount++;
1936         } else if (((unsigned char) *p) == TN_IAC) {
1937             *q++ = (char) TN_IAC;
1938             newcount ++;
1939         }
1940         *q++ = *p++;
1941         newcount++;
1942         left--;
1943     }
1944     if (appData.debugMode) {
1945         fprintf(debugFP, ">ICS: ");
1946         show_bytes(debugFP, buf, newcount);
1947         fprintf(debugFP, "\n");
1948     }
1949     outcount = OutputToProcess(pr, buf, newcount, outError);
1950     if (outcount < newcount) return -1; /* to be sure */
1951     return count;
1952 }
1953
1954 void
1955 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1956 {
1957     int outError, outCount;
1958     static int gotEof = 0;
1959     static FILE *ini;
1960
1961     /* Pass data read from player on to ICS */
1962     if (count > 0) {
1963         gotEof = 0;
1964         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1965         if (outCount < count) {
1966             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1967         }
1968         if(have_sent_ICS_logon == 2) {
1969           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1970             fprintf(ini, "%s", message);
1971             have_sent_ICS_logon = 3;
1972           } else
1973             have_sent_ICS_logon = 1;
1974         } else if(have_sent_ICS_logon == 3) {
1975             fprintf(ini, "%s", message);
1976             fclose(ini);
1977           have_sent_ICS_logon = 1;
1978         }
1979     } else if (count < 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1982     } else if (gotEof++ > 0) {
1983         RemoveInputSource(isr);
1984         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1985     }
1986 }
1987
1988 void
1989 KeepAlive ()
1990 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1991     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1992     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1993     SendToICS("date\n");
1994     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1995 }
1996
1997 /* added routine for printf style output to ics */
1998 void
1999 ics_printf (char *format, ...)
2000 {
2001     char buffer[MSG_SIZ];
2002     va_list args;
2003
2004     va_start(args, format);
2005     vsnprintf(buffer, sizeof(buffer), format, args);
2006     buffer[sizeof(buffer)-1] = '\0';
2007     SendToICS(buffer);
2008     va_end(args);
2009 }
2010
2011 void
2012 SendToICS (char *s)
2013 {
2014     int count, outCount, outError;
2015
2016     if (icsPR == NoProc) return;
2017
2018     count = strlen(s);
2019     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2020     if (outCount < count) {
2021         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2022     }
2023 }
2024
2025 /* This is used for sending logon scripts to the ICS. Sending
2026    without a delay causes problems when using timestamp on ICC
2027    (at least on my machine). */
2028 void
2029 SendToICSDelayed (char *s, long msdelay)
2030 {
2031     int count, outCount, outError;
2032
2033     if (icsPR == NoProc) return;
2034
2035     count = strlen(s);
2036     if (appData.debugMode) {
2037         fprintf(debugFP, ">ICS: ");
2038         show_bytes(debugFP, s, count);
2039         fprintf(debugFP, "\n");
2040     }
2041     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2042                                       msdelay);
2043     if (outCount < count) {
2044         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2045     }
2046 }
2047
2048
2049 /* Remove all highlighting escape sequences in s
2050    Also deletes any suffix starting with '('
2051    */
2052 char *
2053 StripHighlightAndTitle (char *s)
2054 {
2055     static char retbuf[MSG_SIZ];
2056     char *p = retbuf;
2057
2058     while (*s != NULLCHAR) {
2059         while (*s == '\033') {
2060             while (*s != NULLCHAR && !isalpha(*s)) s++;
2061             if (*s != NULLCHAR) s++;
2062         }
2063         while (*s != NULLCHAR && *s != '\033') {
2064             if (*s == '(' || *s == '[') {
2065                 *p = NULLCHAR;
2066                 return retbuf;
2067             }
2068             *p++ = *s++;
2069         }
2070     }
2071     *p = NULLCHAR;
2072     return retbuf;
2073 }
2074
2075 /* Remove all highlighting escape sequences in s */
2076 char *
2077 StripHighlight (char *s)
2078 {
2079     static char retbuf[MSG_SIZ];
2080     char *p = retbuf;
2081
2082     while (*s != NULLCHAR) {
2083         while (*s == '\033') {
2084             while (*s != NULLCHAR && !isalpha(*s)) s++;
2085             if (*s != NULLCHAR) s++;
2086         }
2087         while (*s != NULLCHAR && *s != '\033') {
2088             *p++ = *s++;
2089         }
2090     }
2091     *p = NULLCHAR;
2092     return retbuf;
2093 }
2094
2095 char engineVariant[MSG_SIZ];
2096 char *variantNames[] = VARIANT_NAMES;
2097 char *
2098 VariantName (VariantClass v)
2099 {
2100     if(v == VariantUnknown || *engineVariant) return engineVariant;
2101     return variantNames[v];
2102 }
2103
2104
2105 /* Identify a variant from the strings the chess servers use or the
2106    PGN Variant tag names we use. */
2107 VariantClass
2108 StringToVariant (char *e)
2109 {
2110     char *p;
2111     int wnum = -1;
2112     VariantClass v = VariantNormal;
2113     int i, found = FALSE;
2114     char buf[MSG_SIZ], c;
2115     int len;
2116
2117     if (!e) return v;
2118
2119     /* [HGM] skip over optional board-size prefixes */
2120     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2121         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2122         while( *e++ != '_');
2123     }
2124
2125     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2126         v = VariantNormal;
2127         found = TRUE;
2128     } else
2129     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2130       if (p = StrCaseStr(e, variantNames[i])) {
2131         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2132         v = (VariantClass) i;
2133         found = TRUE;
2134         break;
2135       }
2136     }
2137
2138     if (!found) {
2139       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2140           || StrCaseStr(e, "wild/fr")
2141           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2142         v = VariantFischeRandom;
2143       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2144                  (i = 1, p = StrCaseStr(e, "w"))) {
2145         p += i;
2146         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2147         if (isdigit(*p)) {
2148           wnum = atoi(p);
2149         } else {
2150           wnum = -1;
2151         }
2152         switch (wnum) {
2153         case 0: /* FICS only, actually */
2154         case 1:
2155           /* Castling legal even if K starts on d-file */
2156           v = VariantWildCastle;
2157           break;
2158         case 2:
2159         case 3:
2160         case 4:
2161           /* Castling illegal even if K & R happen to start in
2162              normal positions. */
2163           v = VariantNoCastle;
2164           break;
2165         case 5:
2166         case 7:
2167         case 8:
2168         case 10:
2169         case 11:
2170         case 12:
2171         case 13:
2172         case 14:
2173         case 15:
2174         case 18:
2175         case 19:
2176           /* Castling legal iff K & R start in normal positions */
2177           v = VariantNormal;
2178           break;
2179         case 6:
2180         case 20:
2181         case 21:
2182           /* Special wilds for position setup; unclear what to do here */
2183           v = VariantLoadable;
2184           break;
2185         case 9:
2186           /* Bizarre ICC game */
2187           v = VariantTwoKings;
2188           break;
2189         case 16:
2190           v = VariantKriegspiel;
2191           break;
2192         case 17:
2193           v = VariantLosers;
2194           break;
2195         case 22:
2196           v = VariantFischeRandom;
2197           break;
2198         case 23:
2199           v = VariantCrazyhouse;
2200           break;
2201         case 24:
2202           v = VariantBughouse;
2203           break;
2204         case 25:
2205           v = Variant3Check;
2206           break;
2207         case 26:
2208           /* Not quite the same as FICS suicide! */
2209           v = VariantGiveaway;
2210           break;
2211         case 27:
2212           v = VariantAtomic;
2213           break;
2214         case 28:
2215           v = VariantShatranj;
2216           break;
2217
2218         /* Temporary names for future ICC types.  The name *will* change in
2219            the next xboard/WinBoard release after ICC defines it. */
2220         case 29:
2221           v = Variant29;
2222           break;
2223         case 30:
2224           v = Variant30;
2225           break;
2226         case 31:
2227           v = Variant31;
2228           break;
2229         case 32:
2230           v = Variant32;
2231           break;
2232         case 33:
2233           v = Variant33;
2234           break;
2235         case 34:
2236           v = Variant34;
2237           break;
2238         case 35:
2239           v = Variant35;
2240           break;
2241         case 36:
2242           v = Variant36;
2243           break;
2244         case 37:
2245           v = VariantShogi;
2246           break;
2247         case 38:
2248           v = VariantXiangqi;
2249           break;
2250         case 39:
2251           v = VariantCourier;
2252           break;
2253         case 40:
2254           v = VariantGothic;
2255           break;
2256         case 41:
2257           v = VariantCapablanca;
2258           break;
2259         case 42:
2260           v = VariantKnightmate;
2261           break;
2262         case 43:
2263           v = VariantFairy;
2264           break;
2265         case 44:
2266           v = VariantCylinder;
2267           break;
2268         case 45:
2269           v = VariantFalcon;
2270           break;
2271         case 46:
2272           v = VariantCapaRandom;
2273           break;
2274         case 47:
2275           v = VariantBerolina;
2276           break;
2277         case 48:
2278           v = VariantJanus;
2279           break;
2280         case 49:
2281           v = VariantSuper;
2282           break;
2283         case 50:
2284           v = VariantGreat;
2285           break;
2286         case -1:
2287           /* Found "wild" or "w" in the string but no number;
2288              must assume it's normal chess. */
2289           v = VariantNormal;
2290           break;
2291         default:
2292           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2293           if( (len >= MSG_SIZ) && appData.debugMode )
2294             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2295
2296           DisplayError(buf, 0);
2297           v = VariantUnknown;
2298           break;
2299         }
2300       }
2301     }
2302     if (appData.debugMode) {
2303       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2304               e, wnum, VariantName(v));
2305     }
2306     return v;
2307 }
2308
2309 static int leftover_start = 0, leftover_len = 0;
2310 char star_match[STAR_MATCH_N][MSG_SIZ];
2311
2312 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2313    advance *index beyond it, and set leftover_start to the new value of
2314    *index; else return FALSE.  If pattern contains the character '*', it
2315    matches any sequence of characters not containing '\r', '\n', or the
2316    character following the '*' (if any), and the matched sequence(s) are
2317    copied into star_match.
2318    */
2319 int
2320 looking_at ( char *buf, int *index, char *pattern)
2321 {
2322     char *bufp = &buf[*index], *patternp = pattern;
2323     int star_count = 0;
2324     char *matchp = star_match[0];
2325
2326     for (;;) {
2327         if (*patternp == NULLCHAR) {
2328             *index = leftover_start = bufp - buf;
2329             *matchp = NULLCHAR;
2330             return TRUE;
2331         }
2332         if (*bufp == NULLCHAR) return FALSE;
2333         if (*patternp == '*') {
2334             if (*bufp == *(patternp + 1)) {
2335                 *matchp = NULLCHAR;
2336                 matchp = star_match[++star_count];
2337                 patternp += 2;
2338                 bufp++;
2339                 continue;
2340             } else if (*bufp == '\n' || *bufp == '\r') {
2341                 patternp++;
2342                 if (*patternp == NULLCHAR)
2343                   continue;
2344                 else
2345                   return FALSE;
2346             } else {
2347                 *matchp++ = *bufp++;
2348                 continue;
2349             }
2350         }
2351         if (*patternp != *bufp) return FALSE;
2352         patternp++;
2353         bufp++;
2354     }
2355 }
2356
2357 void
2358 SendToPlayer (char *data, int length)
2359 {
2360     int error, outCount;
2361     outCount = OutputToProcess(NoProc, data, length, &error);
2362     if (outCount < length) {
2363         DisplayFatalError(_("Error writing to display"), error, 1);
2364     }
2365 }
2366
2367 void
2368 PackHolding (char packed[], char *holding)
2369 {
2370     char *p = holding;
2371     char *q = packed;
2372     int runlength = 0;
2373     int curr = 9999;
2374     do {
2375         if (*p == curr) {
2376             runlength++;
2377         } else {
2378             switch (runlength) {
2379               case 0:
2380                 break;
2381               case 1:
2382                 *q++ = curr;
2383                 break;
2384               case 2:
2385                 *q++ = curr;
2386                 *q++ = curr;
2387                 break;
2388               default:
2389                 sprintf(q, "%d", runlength);
2390                 while (*q) q++;
2391                 *q++ = curr;
2392                 break;
2393             }
2394             runlength = 1;
2395             curr = *p;
2396         }
2397     } while (*p++);
2398     *q = NULLCHAR;
2399 }
2400
2401 /* Telnet protocol requests from the front end */
2402 void
2403 TelnetRequest (unsigned char ddww, unsigned char option)
2404 {
2405     unsigned char msg[3];
2406     int outCount, outError;
2407
2408     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2409
2410     if (appData.debugMode) {
2411         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2412         switch (ddww) {
2413           case TN_DO:
2414             ddwwStr = "DO";
2415             break;
2416           case TN_DONT:
2417             ddwwStr = "DONT";
2418             break;
2419           case TN_WILL:
2420             ddwwStr = "WILL";
2421             break;
2422           case TN_WONT:
2423             ddwwStr = "WONT";
2424             break;
2425           default:
2426             ddwwStr = buf1;
2427             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2428             break;
2429         }
2430         switch (option) {
2431           case TN_ECHO:
2432             optionStr = "ECHO";
2433             break;
2434           default:
2435             optionStr = buf2;
2436             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2437             break;
2438         }
2439         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2440     }
2441     msg[0] = TN_IAC;
2442     msg[1] = ddww;
2443     msg[2] = option;
2444     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2445     if (outCount < 3) {
2446         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2447     }
2448 }
2449
2450 void
2451 DoEcho ()
2452 {
2453     if (!appData.icsActive) return;
2454     TelnetRequest(TN_DO, TN_ECHO);
2455 }
2456
2457 void
2458 DontEcho ()
2459 {
2460     if (!appData.icsActive) return;
2461     TelnetRequest(TN_DONT, TN_ECHO);
2462 }
2463
2464 void
2465 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2466 {
2467     /* put the holdings sent to us by the server on the board holdings area */
2468     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2469     char p;
2470     ChessSquare piece;
2471
2472     if(gameInfo.holdingsWidth < 2)  return;
2473     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2474         return; // prevent overwriting by pre-board holdings
2475
2476     if( (int)lowestPiece >= BlackPawn ) {
2477         holdingsColumn = 0;
2478         countsColumn = 1;
2479         holdingsStartRow = BOARD_HEIGHT-1;
2480         direction = -1;
2481     } else {
2482         holdingsColumn = BOARD_WIDTH-1;
2483         countsColumn = BOARD_WIDTH-2;
2484         holdingsStartRow = 0;
2485         direction = 1;
2486     }
2487
2488     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2489         board[i][holdingsColumn] = EmptySquare;
2490         board[i][countsColumn]   = (ChessSquare) 0;
2491     }
2492     while( (p=*holdings++) != NULLCHAR ) {
2493         piece = CharToPiece( ToUpper(p) );
2494         if(piece == EmptySquare) continue;
2495         /*j = (int) piece - (int) WhitePawn;*/
2496         j = PieceToNumber(piece);
2497         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2498         if(j < 0) continue;               /* should not happen */
2499         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2500         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2501         board[holdingsStartRow+j*direction][countsColumn]++;
2502     }
2503 }
2504
2505
2506 void
2507 VariantSwitch (Board board, VariantClass newVariant)
2508 {
2509    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2510    static Board oldBoard;
2511
2512    startedFromPositionFile = FALSE;
2513    if(gameInfo.variant == newVariant) return;
2514
2515    /* [HGM] This routine is called each time an assignment is made to
2516     * gameInfo.variant during a game, to make sure the board sizes
2517     * are set to match the new variant. If that means adding or deleting
2518     * holdings, we shift the playing board accordingly
2519     * This kludge is needed because in ICS observe mode, we get boards
2520     * of an ongoing game without knowing the variant, and learn about the
2521     * latter only later. This can be because of the move list we requested,
2522     * in which case the game history is refilled from the beginning anyway,
2523     * but also when receiving holdings of a crazyhouse game. In the latter
2524     * case we want to add those holdings to the already received position.
2525     */
2526
2527
2528    if (appData.debugMode) {
2529      fprintf(debugFP, "Switch board from %s to %s\n",
2530              VariantName(gameInfo.variant), VariantName(newVariant));
2531      setbuf(debugFP, NULL);
2532    }
2533    shuffleOpenings = 0;       /* [HGM] shuffle */
2534    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2535    switch(newVariant)
2536      {
2537      case VariantShogi:
2538        newWidth = 9;  newHeight = 9;
2539        gameInfo.holdingsSize = 7;
2540      case VariantBughouse:
2541      case VariantCrazyhouse:
2542        newHoldingsWidth = 2; break;
2543      case VariantGreat:
2544        newWidth = 10;
2545      case VariantSuper:
2546        newHoldingsWidth = 2;
2547        gameInfo.holdingsSize = 8;
2548        break;
2549      case VariantGothic:
2550      case VariantCapablanca:
2551      case VariantCapaRandom:
2552        newWidth = 10;
2553      default:
2554        newHoldingsWidth = gameInfo.holdingsSize = 0;
2555      };
2556
2557    if(newWidth  != gameInfo.boardWidth  ||
2558       newHeight != gameInfo.boardHeight ||
2559       newHoldingsWidth != gameInfo.holdingsWidth ) {
2560
2561      /* shift position to new playing area, if needed */
2562      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2563        for(i=0; i<BOARD_HEIGHT; i++)
2564          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2565            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2566              board[i][j];
2567        for(i=0; i<newHeight; i++) {
2568          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2569          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2570        }
2571      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2572        for(i=0; i<BOARD_HEIGHT; i++)
2573          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2574            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2575              board[i][j];
2576      }
2577      board[HOLDINGS_SET] = 0;
2578      gameInfo.boardWidth  = newWidth;
2579      gameInfo.boardHeight = newHeight;
2580      gameInfo.holdingsWidth = newHoldingsWidth;
2581      gameInfo.variant = newVariant;
2582      InitDrawingSizes(-2, 0);
2583    } else gameInfo.variant = newVariant;
2584    CopyBoard(oldBoard, board);   // remember correctly formatted board
2585      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2586    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2587 }
2588
2589 static int loggedOn = FALSE;
2590
2591 /*-- Game start info cache: --*/
2592 int gs_gamenum;
2593 char gs_kind[MSG_SIZ];
2594 static char player1Name[128] = "";
2595 static char player2Name[128] = "";
2596 static char cont_seq[] = "\n\\   ";
2597 static int player1Rating = -1;
2598 static int player2Rating = -1;
2599 /*----------------------------*/
2600
2601 ColorClass curColor = ColorNormal;
2602 int suppressKibitz = 0;
2603
2604 // [HGM] seekgraph
2605 Boolean soughtPending = FALSE;
2606 Boolean seekGraphUp;
2607 #define MAX_SEEK_ADS 200
2608 #define SQUARE 0x80
2609 char *seekAdList[MAX_SEEK_ADS];
2610 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2611 float tcList[MAX_SEEK_ADS];
2612 char colorList[MAX_SEEK_ADS];
2613 int nrOfSeekAds = 0;
2614 int minRating = 1010, maxRating = 2800;
2615 int hMargin = 10, vMargin = 20, h, w;
2616 extern int squareSize, lineGap;
2617
2618 void
2619 PlotSeekAd (int i)
2620 {
2621         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2622         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2623         if(r < minRating+100 && r >=0 ) r = minRating+100;
2624         if(r > maxRating) r = maxRating;
2625         if(tc < 1.f) tc = 1.f;
2626         if(tc > 95.f) tc = 95.f;
2627         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2628         y = ((double)r - minRating)/(maxRating - minRating)
2629             * (h-vMargin-squareSize/8-1) + vMargin;
2630         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2631         if(strstr(seekAdList[i], " u ")) color = 1;
2632         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2633            !strstr(seekAdList[i], "bullet") &&
2634            !strstr(seekAdList[i], "blitz") &&
2635            !strstr(seekAdList[i], "standard") ) color = 2;
2636         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2637         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2638 }
2639
2640 void
2641 PlotSingleSeekAd (int i)
2642 {
2643         PlotSeekAd(i);
2644 }
2645
2646 void
2647 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2648 {
2649         char buf[MSG_SIZ], *ext = "";
2650         VariantClass v = StringToVariant(type);
2651         if(strstr(type, "wild")) {
2652             ext = type + 4; // append wild number
2653             if(v == VariantFischeRandom) type = "chess960"; else
2654             if(v == VariantLoadable) type = "setup"; else
2655             type = VariantName(v);
2656         }
2657         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2658         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2659             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2660             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2661             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2662             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2663             seekNrList[nrOfSeekAds] = nr;
2664             zList[nrOfSeekAds] = 0;
2665             seekAdList[nrOfSeekAds++] = StrSave(buf);
2666             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2667         }
2668 }
2669
2670 void
2671 EraseSeekDot (int i)
2672 {
2673     int x = xList[i], y = yList[i], d=squareSize/4, k;
2674     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2675     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2676     // now replot every dot that overlapped
2677     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2678         int xx = xList[k], yy = yList[k];
2679         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2680             DrawSeekDot(xx, yy, colorList[k]);
2681     }
2682 }
2683
2684 void
2685 RemoveSeekAd (int nr)
2686 {
2687         int i;
2688         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2689             EraseSeekDot(i);
2690             if(seekAdList[i]) free(seekAdList[i]);
2691             seekAdList[i] = seekAdList[--nrOfSeekAds];
2692             seekNrList[i] = seekNrList[nrOfSeekAds];
2693             ratingList[i] = ratingList[nrOfSeekAds];
2694             colorList[i]  = colorList[nrOfSeekAds];
2695             tcList[i] = tcList[nrOfSeekAds];
2696             xList[i]  = xList[nrOfSeekAds];
2697             yList[i]  = yList[nrOfSeekAds];
2698             zList[i]  = zList[nrOfSeekAds];
2699             seekAdList[nrOfSeekAds] = NULL;
2700             break;
2701         }
2702 }
2703
2704 Boolean
2705 MatchSoughtLine (char *line)
2706 {
2707     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2708     int nr, base, inc, u=0; char dummy;
2709
2710     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2712        (u=1) &&
2713        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2714         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2715         // match: compact and save the line
2716         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2717         return TRUE;
2718     }
2719     return FALSE;
2720 }
2721
2722 int
2723 DrawSeekGraph ()
2724 {
2725     int i;
2726     if(!seekGraphUp) return FALSE;
2727     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2728     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2729
2730     DrawSeekBackground(0, 0, w, h);
2731     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2732     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2733     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2734         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2735         yy = h-1-yy;
2736         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2737         if(i%500 == 0) {
2738             char buf[MSG_SIZ];
2739             snprintf(buf, MSG_SIZ, "%d", i);
2740             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2741         }
2742     }
2743     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2744     for(i=1; i<100; i+=(i<10?1:5)) {
2745         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2746         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2747         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2748             char buf[MSG_SIZ];
2749             snprintf(buf, MSG_SIZ, "%d", i);
2750             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2751         }
2752     }
2753     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2754     return TRUE;
2755 }
2756
2757 int
2758 SeekGraphClick (ClickType click, int x, int y, int moving)
2759 {
2760     static int lastDown = 0, displayed = 0, lastSecond;
2761     if(y < 0) return FALSE;
2762     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2763         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2764         if(!seekGraphUp) return FALSE;
2765         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2766         DrawPosition(TRUE, NULL);
2767         return TRUE;
2768     }
2769     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2770         if(click == Release || moving) return FALSE;
2771         nrOfSeekAds = 0;
2772         soughtPending = TRUE;
2773         SendToICS(ics_prefix);
2774         SendToICS("sought\n"); // should this be "sought all"?
2775     } else { // issue challenge based on clicked ad
2776         int dist = 10000; int i, closest = 0, second = 0;
2777         for(i=0; i<nrOfSeekAds; i++) {
2778             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2779             if(d < dist) { dist = d; closest = i; }
2780             second += (d - zList[i] < 120); // count in-range ads
2781             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2782         }
2783         if(dist < 120) {
2784             char buf[MSG_SIZ];
2785             second = (second > 1);
2786             if(displayed != closest || second != lastSecond) {
2787                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2788                 lastSecond = second; displayed = closest;
2789             }
2790             if(click == Press) {
2791                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2792                 lastDown = closest;
2793                 return TRUE;
2794             } // on press 'hit', only show info
2795             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2796             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2797             SendToICS(ics_prefix);
2798             SendToICS(buf);
2799             return TRUE; // let incoming board of started game pop down the graph
2800         } else if(click == Release) { // release 'miss' is ignored
2801             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2802             if(moving == 2) { // right up-click
2803                 nrOfSeekAds = 0; // refresh graph
2804                 soughtPending = TRUE;
2805                 SendToICS(ics_prefix);
2806                 SendToICS("sought\n"); // should this be "sought all"?
2807             }
2808             return TRUE;
2809         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2810         // press miss or release hit 'pop down' seek graph
2811         seekGraphUp = FALSE;
2812         DrawPosition(TRUE, NULL);
2813     }
2814     return TRUE;
2815 }
2816
2817 void
2818 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2819 {
2820 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2821 #define STARTED_NONE 0
2822 #define STARTED_MOVES 1
2823 #define STARTED_BOARD 2
2824 #define STARTED_OBSERVE 3
2825 #define STARTED_HOLDINGS 4
2826 #define STARTED_CHATTER 5
2827 #define STARTED_COMMENT 6
2828 #define STARTED_MOVES_NOHIDE 7
2829
2830     static int started = STARTED_NONE;
2831     static char parse[20000];
2832     static int parse_pos = 0;
2833     static char buf[BUF_SIZE + 1];
2834     static int firstTime = TRUE, intfSet = FALSE;
2835     static ColorClass prevColor = ColorNormal;
2836     static int savingComment = FALSE;
2837     static int cmatch = 0; // continuation sequence match
2838     char *bp;
2839     char str[MSG_SIZ];
2840     int i, oldi;
2841     int buf_len;
2842     int next_out;
2843     int tkind;
2844     int backup;    /* [DM] For zippy color lines */
2845     char *p;
2846     char talker[MSG_SIZ]; // [HGM] chat
2847     int channel, collective=0;
2848
2849     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2850
2851     if (appData.debugMode) {
2852       if (!error) {
2853         fprintf(debugFP, "<ICS: ");
2854         show_bytes(debugFP, data, count);
2855         fprintf(debugFP, "\n");
2856       }
2857     }
2858
2859     if (appData.debugMode) { int f = forwardMostMove;
2860         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2861                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2862                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2863     }
2864     if (count > 0) {
2865         /* If last read ended with a partial line that we couldn't parse,
2866            prepend it to the new read and try again. */
2867         if (leftover_len > 0) {
2868             for (i=0; i<leftover_len; i++)
2869               buf[i] = buf[leftover_start + i];
2870         }
2871
2872     /* copy new characters into the buffer */
2873     bp = buf + leftover_len;
2874     buf_len=leftover_len;
2875     for (i=0; i<count; i++)
2876     {
2877         // ignore these
2878         if (data[i] == '\r')
2879             continue;
2880
2881         // join lines split by ICS?
2882         if (!appData.noJoin)
2883         {
2884             /*
2885                 Joining just consists of finding matches against the
2886                 continuation sequence, and discarding that sequence
2887                 if found instead of copying it.  So, until a match
2888                 fails, there's nothing to do since it might be the
2889                 complete sequence, and thus, something we don't want
2890                 copied.
2891             */
2892             if (data[i] == cont_seq[cmatch])
2893             {
2894                 cmatch++;
2895                 if (cmatch == strlen(cont_seq))
2896                 {
2897                     cmatch = 0; // complete match.  just reset the counter
2898
2899                     /*
2900                         it's possible for the ICS to not include the space
2901                         at the end of the last word, making our [correct]
2902                         join operation fuse two separate words.  the server
2903                         does this when the space occurs at the width setting.
2904                     */
2905                     if (!buf_len || buf[buf_len-1] != ' ')
2906                     {
2907                         *bp++ = ' ';
2908                         buf_len++;
2909                     }
2910                 }
2911                 continue;
2912             }
2913             else if (cmatch)
2914             {
2915                 /*
2916                     match failed, so we have to copy what matched before
2917                     falling through and copying this character.  In reality,
2918                     this will only ever be just the newline character, but
2919                     it doesn't hurt to be precise.
2920                 */
2921                 strncpy(bp, cont_seq, cmatch);
2922                 bp += cmatch;
2923                 buf_len += cmatch;
2924                 cmatch = 0;
2925             }
2926         }
2927
2928         // copy this char
2929         *bp++ = data[i];
2930         buf_len++;
2931     }
2932
2933         buf[buf_len] = NULLCHAR;
2934 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2935         next_out = 0;
2936         leftover_start = 0;
2937
2938         i = 0;
2939         while (i < buf_len) {
2940             /* Deal with part of the TELNET option negotiation
2941                protocol.  We refuse to do anything beyond the
2942                defaults, except that we allow the WILL ECHO option,
2943                which ICS uses to turn off password echoing when we are
2944                directly connected to it.  We reject this option
2945                if localLineEditing mode is on (always on in xboard)
2946                and we are talking to port 23, which might be a real
2947                telnet server that will try to keep WILL ECHO on permanently.
2948              */
2949             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2950                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2951                 unsigned char option;
2952                 oldi = i;
2953                 switch ((unsigned char) buf[++i]) {
2954                   case TN_WILL:
2955                     if (appData.debugMode)
2956                       fprintf(debugFP, "\n<WILL ");
2957                     switch (option = (unsigned char) buf[++i]) {
2958                       case TN_ECHO:
2959                         if (appData.debugMode)
2960                           fprintf(debugFP, "ECHO ");
2961                         /* Reply only if this is a change, according
2962                            to the protocol rules. */
2963                         if (remoteEchoOption) break;
2964                         if (appData.localLineEditing &&
2965                             atoi(appData.icsPort) == TN_PORT) {
2966                             TelnetRequest(TN_DONT, TN_ECHO);
2967                         } else {
2968                             EchoOff();
2969                             TelnetRequest(TN_DO, TN_ECHO);
2970                             remoteEchoOption = TRUE;
2971                         }
2972                         break;
2973                       default:
2974                         if (appData.debugMode)
2975                           fprintf(debugFP, "%d ", option);
2976                         /* Whatever this is, we don't want it. */
2977                         TelnetRequest(TN_DONT, option);
2978                         break;
2979                     }
2980                     break;
2981                   case TN_WONT:
2982                     if (appData.debugMode)
2983                       fprintf(debugFP, "\n<WONT ");
2984                     switch (option = (unsigned char) buf[++i]) {
2985                       case TN_ECHO:
2986                         if (appData.debugMode)
2987                           fprintf(debugFP, "ECHO ");
2988                         /* Reply only if this is a change, according
2989                            to the protocol rules. */
2990                         if (!remoteEchoOption) break;
2991                         EchoOn();
2992                         TelnetRequest(TN_DONT, TN_ECHO);
2993                         remoteEchoOption = FALSE;
2994                         break;
2995                       default:
2996                         if (appData.debugMode)
2997                           fprintf(debugFP, "%d ", (unsigned char) option);
2998                         /* Whatever this is, it must already be turned
2999                            off, because we never agree to turn on
3000                            anything non-default, so according to the
3001                            protocol rules, we don't reply. */
3002                         break;
3003                     }
3004                     break;
3005                   case TN_DO:
3006                     if (appData.debugMode)
3007                       fprintf(debugFP, "\n<DO ");
3008                     switch (option = (unsigned char) buf[++i]) {
3009                       default:
3010                         /* Whatever this is, we refuse to do it. */
3011                         if (appData.debugMode)
3012                           fprintf(debugFP, "%d ", option);
3013                         TelnetRequest(TN_WONT, option);
3014                         break;
3015                     }
3016                     break;
3017                   case TN_DONT:
3018                     if (appData.debugMode)
3019                       fprintf(debugFP, "\n<DONT ");
3020                     switch (option = (unsigned char) buf[++i]) {
3021                       default:
3022                         if (appData.debugMode)
3023                           fprintf(debugFP, "%d ", option);
3024                         /* Whatever this is, we are already not doing
3025                            it, because we never agree to do anything
3026                            non-default, so according to the protocol
3027                            rules, we don't reply. */
3028                         break;
3029                     }
3030                     break;
3031                   case TN_IAC:
3032                     if (appData.debugMode)
3033                       fprintf(debugFP, "\n<IAC ");
3034                     /* Doubled IAC; pass it through */
3035                     i--;
3036                     break;
3037                   default:
3038                     if (appData.debugMode)
3039                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3040                     /* Drop all other telnet commands on the floor */
3041                     break;
3042                 }
3043                 if (oldi > next_out)
3044                   SendToPlayer(&buf[next_out], oldi - next_out);
3045                 if (++i > next_out)
3046                   next_out = i;
3047                 continue;
3048             }
3049
3050             /* OK, this at least will *usually* work */
3051             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3052                 loggedOn = TRUE;
3053             }
3054
3055             if (loggedOn && !intfSet) {
3056                 if (ics_type == ICS_ICC) {
3057                   snprintf(str, MSG_SIZ,
3058                           "/set-quietly interface %s\n/set-quietly style 12\n",
3059                           programVersion);
3060                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3061                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3062                 } else if (ics_type == ICS_CHESSNET) {
3063                   snprintf(str, MSG_SIZ, "/style 12\n");
3064                 } else {
3065                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3066                   strcat(str, programVersion);
3067                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3068                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3069                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3070 #ifdef WIN32
3071                   strcat(str, "$iset nohighlight 1\n");
3072 #endif
3073                   strcat(str, "$iset lock 1\n$style 12\n");
3074                 }
3075                 SendToICS(str);
3076                 NotifyFrontendLogin();
3077                 intfSet = TRUE;
3078             }
3079
3080             if (started == STARTED_COMMENT) {
3081                 /* Accumulate characters in comment */
3082                 parse[parse_pos++] = buf[i];
3083                 if (buf[i] == '\n') {
3084                     parse[parse_pos] = NULLCHAR;
3085                     if(chattingPartner>=0) {
3086                         char mess[MSG_SIZ];
3087                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3088                         OutputChatMessage(chattingPartner, mess);
3089                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3090                             int p;
3091                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3092                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3093                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3094                                 OutputChatMessage(p, mess);
3095                                 break;
3096                             }
3097                         }
3098                         chattingPartner = -1;
3099                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3100                         collective = 0;
3101                     } else
3102                     if(!suppressKibitz) // [HGM] kibitz
3103                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3104                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3105                         int nrDigit = 0, nrAlph = 0, j;
3106                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3107                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3108                         parse[parse_pos] = NULLCHAR;
3109                         // try to be smart: if it does not look like search info, it should go to
3110                         // ICS interaction window after all, not to engine-output window.
3111                         for(j=0; j<parse_pos; j++) { // count letters and digits
3112                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3113                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3114                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3115                         }
3116                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3117                             int depth=0; float score;
3118                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3119                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3120                                 pvInfoList[forwardMostMove-1].depth = depth;
3121                                 pvInfoList[forwardMostMove-1].score = 100*score;
3122                             }
3123                             OutputKibitz(suppressKibitz, parse);
3124                         } else {
3125                             char tmp[MSG_SIZ];
3126                             if(gameMode == IcsObserving) // restore original ICS messages
3127                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3129                             else
3130                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3131                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3132                             SendToPlayer(tmp, strlen(tmp));
3133                         }
3134                         next_out = i+1; // [HGM] suppress printing in ICS window
3135                     }
3136                     started = STARTED_NONE;
3137                 } else {
3138                     /* Don't match patterns against characters in comment */
3139                     i++;
3140                     continue;
3141                 }
3142             }
3143             if (started == STARTED_CHATTER) {
3144                 if (buf[i] != '\n') {
3145                     /* Don't match patterns against characters in chatter */
3146                     i++;
3147                     continue;
3148                 }
3149                 started = STARTED_NONE;
3150                 if(suppressKibitz) next_out = i+1;
3151             }
3152
3153             /* Kludge to deal with rcmd protocol */
3154             if (firstTime && looking_at(buf, &i, "\001*")) {
3155                 DisplayFatalError(&buf[1], 0, 1);
3156                 continue;
3157             } else {
3158                 firstTime = FALSE;
3159             }
3160
3161             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3162                 ics_type = ICS_ICC;
3163                 ics_prefix = "/";
3164                 if (appData.debugMode)
3165                   fprintf(debugFP, "ics_type %d\n", ics_type);
3166                 continue;
3167             }
3168             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3169                 ics_type = ICS_FICS;
3170                 ics_prefix = "$";
3171                 if (appData.debugMode)
3172                   fprintf(debugFP, "ics_type %d\n", ics_type);
3173                 continue;
3174             }
3175             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3176                 ics_type = ICS_CHESSNET;
3177                 ics_prefix = "/";
3178                 if (appData.debugMode)
3179                   fprintf(debugFP, "ics_type %d\n", ics_type);
3180                 continue;
3181             }
3182
3183             if (!loggedOn &&
3184                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3185                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3186                  looking_at(buf, &i, "will be \"*\""))) {
3187               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3188               continue;
3189             }
3190
3191             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3192               char buf[MSG_SIZ];
3193               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3194               DisplayIcsInteractionTitle(buf);
3195               have_set_title = TRUE;
3196             }
3197
3198             /* skip finger notes */
3199             if (started == STARTED_NONE &&
3200                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3201                  (buf[i] == '1' && buf[i+1] == '0')) &&
3202                 buf[i+2] == ':' && buf[i+3] == ' ') {
3203               started = STARTED_CHATTER;
3204               i += 3;
3205               continue;
3206             }
3207
3208             oldi = i;
3209             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3210             if(appData.seekGraph) {
3211                 if(soughtPending && MatchSoughtLine(buf+i)) {
3212                     i = strstr(buf+i, "rated") - buf;
3213                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3214                     next_out = leftover_start = i;
3215                     started = STARTED_CHATTER;
3216                     suppressKibitz = TRUE;
3217                     continue;
3218                 }
3219                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3220                         && looking_at(buf, &i, "* ads displayed")) {
3221                     soughtPending = FALSE;
3222                     seekGraphUp = TRUE;
3223                     DrawSeekGraph();
3224                     continue;
3225                 }
3226                 if(appData.autoRefresh) {
3227                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3228                         int s = (ics_type == ICS_ICC); // ICC format differs
3229                         if(seekGraphUp)
3230                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3231                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3232                         looking_at(buf, &i, "*% "); // eat prompt
3233                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3234                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3235                         next_out = i; // suppress
3236                         continue;
3237                     }
3238                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3239                         char *p = star_match[0];
3240                         while(*p) {
3241                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3242                             while(*p && *p++ != ' '); // next
3243                         }
3244                         looking_at(buf, &i, "*% "); // eat prompt
3245                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3246                         next_out = i;
3247                         continue;
3248                     }
3249                 }
3250             }
3251
3252             /* skip formula vars */
3253             if (started == STARTED_NONE &&
3254                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3255               started = STARTED_CHATTER;
3256               i += 3;
3257               continue;
3258             }
3259
3260             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3261             if (appData.autoKibitz && started == STARTED_NONE &&
3262                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3263                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3264                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3265                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3266                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3267                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3268                         suppressKibitz = TRUE;
3269                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3270                         next_out = i;
3271                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3272                                 && (gameMode == IcsPlayingWhite)) ||
3273                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3274                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3275                             started = STARTED_CHATTER; // own kibitz we simply discard
3276                         else {
3277                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3278                             parse_pos = 0; parse[0] = NULLCHAR;
3279                             savingComment = TRUE;
3280                             suppressKibitz = gameMode != IcsObserving ? 2 :
3281                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3282                         }
3283                         continue;
3284                 } else
3285                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3286                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3287                          && atoi(star_match[0])) {
3288                     // suppress the acknowledgements of our own autoKibitz
3289                     char *p;
3290                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3291                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3292                     SendToPlayer(star_match[0], strlen(star_match[0]));
3293                     if(looking_at(buf, &i, "*% ")) // eat prompt
3294                         suppressKibitz = FALSE;
3295                     next_out = i;
3296                     continue;
3297                 }
3298             } // [HGM] kibitz: end of patch
3299
3300             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3301
3302             // [HGM] chat: intercept tells by users for which we have an open chat window
3303             channel = -1;
3304             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3305                                            looking_at(buf, &i, "* whispers:") ||
3306                                            looking_at(buf, &i, "* kibitzes:") ||
3307                                            looking_at(buf, &i, "* shouts:") ||
3308                                            looking_at(buf, &i, "* c-shouts:") ||
3309                                            looking_at(buf, &i, "--> * ") ||
3310                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3311                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3314                 int p;
3315                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3316                 chattingPartner = -1; collective = 0;
3317
3318                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3319                 for(p=0; p<MAX_CHAT; p++) {
3320                     collective = 1;
3321                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3322                     talker[0] = '['; strcat(talker, "] ");
3323                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3324                     chattingPartner = p; break;
3325                     }
3326                 } else
3327                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3328                 for(p=0; p<MAX_CHAT; p++) {
3329                     collective = 1;
3330                     if(!strcmp("kibitzes", chatPartner[p])) {
3331                         talker[0] = '['; strcat(talker, "] ");
3332                         chattingPartner = p; break;
3333                     }
3334                 } else
3335                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3336                 for(p=0; p<MAX_CHAT; p++) {
3337                     collective = 1;
3338                     if(!strcmp("whispers", chatPartner[p])) {
3339                         talker[0] = '['; strcat(talker, "] ");
3340                         chattingPartner = p; break;
3341                     }
3342                 } else
3343                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3344                   if(buf[i-8] == '-' && buf[i-3] == 't')
3345                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3346                     collective = 1;
3347                     if(!strcmp("c-shouts", chatPartner[p])) {
3348                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3349                         chattingPartner = p; break;
3350                     }
3351                   }
3352                   if(chattingPartner < 0)
3353                   for(p=0; p<MAX_CHAT; p++) {
3354                     collective = 1;
3355                     if(!strcmp("shouts", chatPartner[p])) {
3356                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3357                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3358                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3359                         chattingPartner = p; break;
3360                     }
3361                   }
3362                 }
3363                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3364                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3365                     talker[0] = 0;
3366                     Colorize(ColorTell, FALSE);
3367                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3368                     collective |= 2;
3369                     chattingPartner = p; break;
3370                 }
3371                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3372                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3373                     started = STARTED_COMMENT;
3374                     parse_pos = 0; parse[0] = NULLCHAR;
3375                     savingComment = 3 + chattingPartner; // counts as TRUE
3376                     if(collective == 3) i = oldi; else {
3377                         suppressKibitz = TRUE;
3378                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3379                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3380                         continue;
3381                     }
3382                 }
3383             } // [HGM] chat: end of patch
3384
3385           backup = i;
3386             if (appData.zippyTalk || appData.zippyPlay) {
3387                 /* [DM] Backup address for color zippy lines */
3388 #if ZIPPY
3389                if (loggedOn == TRUE)
3390                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3391                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3392 #endif
3393             } // [DM] 'else { ' deleted
3394                 if (
3395                     /* Regular tells and says */
3396                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3397                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3398                     looking_at(buf, &i, "* says: ") ||
3399                     /* Don't color "message" or "messages" output */
3400                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3401                     looking_at(buf, &i, "*. * at *:*: ") ||
3402                     looking_at(buf, &i, "--* (*:*): ") ||
3403                     /* Message notifications (same color as tells) */
3404                     looking_at(buf, &i, "* has left a message ") ||
3405                     looking_at(buf, &i, "* just sent you a message:\n") ||
3406                     /* Whispers and kibitzes */
3407                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3408                     looking_at(buf, &i, "* kibitzes: ") ||
3409                     /* Channel tells */
3410                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3411
3412                   if (tkind == 1 && strchr(star_match[0], ':')) {
3413                       /* Avoid "tells you:" spoofs in channels */
3414                      tkind = 3;
3415                   }
3416                   if (star_match[0][0] == NULLCHAR ||
3417                       strchr(star_match[0], ' ') ||
3418                       (tkind == 3 && strchr(star_match[1], ' '))) {
3419                     /* Reject bogus matches */
3420                     i = oldi;
3421                   } else {
3422                     if (appData.colorize) {
3423                       if (oldi > next_out) {
3424                         SendToPlayer(&buf[next_out], oldi - next_out);
3425                         next_out = oldi;
3426                       }
3427                       switch (tkind) {
3428                       case 1:
3429                         Colorize(ColorTell, FALSE);
3430                         curColor = ColorTell;
3431                         break;
3432                       case 2:
3433                         Colorize(ColorKibitz, FALSE);
3434                         curColor = ColorKibitz;
3435                         break;
3436                       case 3:
3437                         p = strrchr(star_match[1], '(');
3438                         if (p == NULL) {
3439                           p = star_match[1];
3440                         } else {
3441                           p++;
3442                         }
3443                         if (atoi(p) == 1) {
3444                           Colorize(ColorChannel1, FALSE);
3445                           curColor = ColorChannel1;
3446                         } else {
3447                           Colorize(ColorChannel, FALSE);
3448                           curColor = ColorChannel;
3449                         }
3450                         break;
3451                       case 5:
3452                         curColor = ColorNormal;
3453                         break;
3454                       }
3455                     }
3456                     if (started == STARTED_NONE && appData.autoComment &&
3457                         (gameMode == IcsObserving ||
3458                          gameMode == IcsPlayingWhite ||
3459                          gameMode == IcsPlayingBlack)) {
3460                       parse_pos = i - oldi;
3461                       memcpy(parse, &buf[oldi], parse_pos);
3462                       parse[parse_pos] = NULLCHAR;
3463                       started = STARTED_COMMENT;
3464                       savingComment = TRUE;
3465                     } else if(collective != 3) {
3466                       started = STARTED_CHATTER;
3467                       savingComment = FALSE;
3468                     }
3469                     loggedOn = TRUE;
3470                     continue;
3471                   }
3472                 }
3473
3474                 if (looking_at(buf, &i, "* s-shouts: ") ||
3475                     looking_at(buf, &i, "* c-shouts: ")) {
3476                     if (appData.colorize) {
3477                         if (oldi > next_out) {
3478                             SendToPlayer(&buf[next_out], oldi - next_out);
3479                             next_out = oldi;
3480                         }
3481                         Colorize(ColorSShout, FALSE);
3482                         curColor = ColorSShout;
3483                     }
3484                     loggedOn = TRUE;
3485                     started = STARTED_CHATTER;
3486                     continue;
3487                 }
3488
3489                 if (looking_at(buf, &i, "--->")) {
3490                     loggedOn = TRUE;
3491                     continue;
3492                 }
3493
3494                 if (looking_at(buf, &i, "* shouts: ") ||
3495                     looking_at(buf, &i, "--> ")) {
3496                     if (appData.colorize) {
3497                         if (oldi > next_out) {
3498                             SendToPlayer(&buf[next_out], oldi - next_out);
3499                             next_out = oldi;
3500                         }
3501                         Colorize(ColorShout, FALSE);
3502                         curColor = ColorShout;
3503                     }
3504                     loggedOn = TRUE;
3505                     started = STARTED_CHATTER;
3506                     continue;
3507                 }
3508
3509                 if (looking_at( buf, &i, "Challenge:")) {
3510                     if (appData.colorize) {
3511                         if (oldi > next_out) {
3512                             SendToPlayer(&buf[next_out], oldi - next_out);
3513                             next_out = oldi;
3514                         }
3515                         Colorize(ColorChallenge, FALSE);
3516                         curColor = ColorChallenge;
3517                     }
3518                     loggedOn = TRUE;
3519                     continue;
3520                 }
3521
3522                 if (looking_at(buf, &i, "* offers you") ||
3523                     looking_at(buf, &i, "* offers to be") ||
3524                     looking_at(buf, &i, "* would like to") ||
3525                     looking_at(buf, &i, "* requests to") ||
3526                     looking_at(buf, &i, "Your opponent offers") ||
3527                     looking_at(buf, &i, "Your opponent requests")) {
3528
3529                     if (appData.colorize) {
3530                         if (oldi > next_out) {
3531                             SendToPlayer(&buf[next_out], oldi - next_out);
3532                             next_out = oldi;
3533                         }
3534                         Colorize(ColorRequest, FALSE);
3535                         curColor = ColorRequest;
3536                     }
3537                     continue;
3538                 }
3539
3540                 if (looking_at(buf, &i, "* (*) seeking")) {
3541                     if (appData.colorize) {
3542                         if (oldi > next_out) {
3543                             SendToPlayer(&buf[next_out], oldi - next_out);
3544                             next_out = oldi;
3545                         }
3546                         Colorize(ColorSeek, FALSE);
3547                         curColor = ColorSeek;
3548                     }
3549                     continue;
3550             }
3551
3552           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3553
3554             if (looking_at(buf, &i, "\\   ")) {
3555                 if (prevColor != ColorNormal) {
3556                     if (oldi > next_out) {
3557                         SendToPlayer(&buf[next_out], oldi - next_out);
3558                         next_out = oldi;
3559                     }
3560                     Colorize(prevColor, TRUE);
3561                     curColor = prevColor;
3562                 }
3563                 if (savingComment) {
3564                     parse_pos = i - oldi;
3565                     memcpy(parse, &buf[oldi], parse_pos);
3566                     parse[parse_pos] = NULLCHAR;
3567                     started = STARTED_COMMENT;
3568                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3569                         chattingPartner = savingComment - 3; // kludge to remember the box
3570                 } else {
3571                     started = STARTED_CHATTER;
3572                 }
3573                 continue;
3574             }
3575
3576             if (looking_at(buf, &i, "Black Strength :") ||
3577                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3578                 looking_at(buf, &i, "<10>") ||
3579                 looking_at(buf, &i, "#@#")) {
3580                 /* Wrong board style */
3581                 loggedOn = TRUE;
3582                 SendToICS(ics_prefix);
3583                 SendToICS("set style 12\n");
3584                 SendToICS(ics_prefix);
3585                 SendToICS("refresh\n");
3586                 continue;
3587             }
3588
3589             if (looking_at(buf, &i, "login:")) {
3590               if (!have_sent_ICS_logon) {
3591                 if(ICSInitScript())
3592                   have_sent_ICS_logon = 1;
3593                 else // no init script was found
3594                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3595               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3596                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3597               }
3598                 continue;
3599             }
3600
3601             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3602                 (looking_at(buf, &i, "\n<12> ") ||
3603                  looking_at(buf, &i, "<12> "))) {
3604                 loggedOn = TRUE;
3605                 if (oldi > next_out) {
3606                     SendToPlayer(&buf[next_out], oldi - next_out);
3607                 }
3608                 next_out = i;
3609                 started = STARTED_BOARD;
3610                 parse_pos = 0;
3611                 continue;
3612             }
3613
3614             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3615                 looking_at(buf, &i, "<b1> ")) {
3616                 if (oldi > next_out) {
3617                     SendToPlayer(&buf[next_out], oldi - next_out);
3618                 }
3619                 next_out = i;
3620                 started = STARTED_HOLDINGS;
3621                 parse_pos = 0;
3622                 continue;
3623             }
3624
3625             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3626                 loggedOn = TRUE;
3627                 /* Header for a move list -- first line */
3628
3629                 switch (ics_getting_history) {
3630                   case H_FALSE:
3631                     switch (gameMode) {
3632                       case IcsIdle:
3633                       case BeginningOfGame:
3634                         /* User typed "moves" or "oldmoves" while we
3635                            were idle.  Pretend we asked for these
3636                            moves and soak them up so user can step
3637                            through them and/or save them.
3638                            */
3639                         Reset(FALSE, TRUE);
3640                         gameMode = IcsObserving;
3641                         ModeHighlight();
3642                         ics_gamenum = -1;
3643                         ics_getting_history = H_GOT_UNREQ_HEADER;
3644                         break;
3645                       case EditGame: /*?*/
3646                       case EditPosition: /*?*/
3647                         /* Should above feature work in these modes too? */
3648                         /* For now it doesn't */
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                       default:
3652                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3653                         break;
3654                     }
3655                     break;
3656                   case H_REQUESTED:
3657                     /* Is this the right one? */
3658                     if (gameInfo.white && gameInfo.black &&
3659                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3660                         strcmp(gameInfo.black, star_match[2]) == 0) {
3661                         /* All is well */
3662                         ics_getting_history = H_GOT_REQ_HEADER;
3663                     }
3664                     break;
3665                   case H_GOT_REQ_HEADER:
3666                   case H_GOT_UNREQ_HEADER:
3667                   case H_GOT_UNWANTED_HEADER:
3668                   case H_GETTING_MOVES:
3669                     /* Should not happen */
3670                     DisplayError(_("Error gathering move list: two headers"), 0);
3671                     ics_getting_history = H_FALSE;
3672                     break;
3673                 }
3674
3675                 /* Save player ratings into gameInfo if needed */
3676                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3677                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3678                     (gameInfo.whiteRating == -1 ||
3679                      gameInfo.blackRating == -1)) {
3680
3681                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3682                     gameInfo.blackRating = string_to_rating(star_match[3]);
3683                     if (appData.debugMode)
3684                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3685                               gameInfo.whiteRating, gameInfo.blackRating);
3686                 }
3687                 continue;
3688             }
3689
3690             if (looking_at(buf, &i,
3691               "* * match, initial time: * minute*, increment: * second")) {
3692                 /* Header for a move list -- second line */
3693                 /* Initial board will follow if this is a wild game */
3694                 if (gameInfo.event != NULL) free(gameInfo.event);
3695                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3696                 gameInfo.event = StrSave(str);
3697                 /* [HGM] we switched variant. Translate boards if needed. */
3698                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3699                 continue;
3700             }
3701
3702             if (looking_at(buf, &i, "Move  ")) {
3703                 /* Beginning of a move list */
3704                 switch (ics_getting_history) {
3705                   case H_FALSE:
3706                     /* Normally should not happen */
3707                     /* Maybe user hit reset while we were parsing */
3708                     break;
3709                   case H_REQUESTED:
3710                     /* Happens if we are ignoring a move list that is not
3711                      * the one we just requested.  Common if the user
3712                      * tries to observe two games without turning off
3713                      * getMoveList */
3714                     break;
3715                   case H_GETTING_MOVES:
3716                     /* Should not happen */
3717                     DisplayError(_("Error gathering move list: nested"), 0);
3718                     ics_getting_history = H_FALSE;
3719                     break;
3720                   case H_GOT_REQ_HEADER:
3721                     ics_getting_history = H_GETTING_MOVES;
3722                     started = STARTED_MOVES;
3723                     parse_pos = 0;
3724                     if (oldi > next_out) {
3725                         SendToPlayer(&buf[next_out], oldi - next_out);
3726                     }
3727                     break;
3728                   case H_GOT_UNREQ_HEADER:
3729                     ics_getting_history = H_GETTING_MOVES;
3730                     started = STARTED_MOVES_NOHIDE;
3731                     parse_pos = 0;
3732                     break;
3733                   case H_GOT_UNWANTED_HEADER:
3734                     ics_getting_history = H_FALSE;
3735                     break;
3736                 }
3737                 continue;
3738             }
3739
3740             if (looking_at(buf, &i, "% ") ||
3741                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3742                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3743                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3744                     soughtPending = FALSE;
3745                     seekGraphUp = TRUE;
3746                     DrawSeekGraph();
3747                 }
3748                 if(suppressKibitz) next_out = i;
3749                 savingComment = FALSE;
3750                 suppressKibitz = 0;
3751                 switch (started) {
3752                   case STARTED_MOVES:
3753                   case STARTED_MOVES_NOHIDE:
3754                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3755                     parse[parse_pos + i - oldi] = NULLCHAR;
3756                     ParseGameHistory(parse);
3757 #if ZIPPY
3758                     if (appData.zippyPlay && first.initDone) {
3759                         FeedMovesToProgram(&first, forwardMostMove);
3760                         if (gameMode == IcsPlayingWhite) {
3761                             if (WhiteOnMove(forwardMostMove)) {
3762                                 if (first.sendTime) {
3763                                   if (first.useColors) {
3764                                     SendToProgram("black\n", &first);
3765                                   }
3766                                   SendTimeRemaining(&first, TRUE);
3767                                 }
3768                                 if (first.useColors) {
3769                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3770                                 }
3771                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3772                                 first.maybeThinking = TRUE;
3773                             } else {
3774                                 if (first.usePlayother) {
3775                                   if (first.sendTime) {
3776                                     SendTimeRemaining(&first, TRUE);
3777                                   }
3778                                   SendToProgram("playother\n", &first);
3779                                   firstMove = FALSE;
3780                                 } else {
3781                                   firstMove = TRUE;
3782                                 }
3783                             }
3784                         } else if (gameMode == IcsPlayingBlack) {
3785                             if (!WhiteOnMove(forwardMostMove)) {
3786                                 if (first.sendTime) {
3787                                   if (first.useColors) {
3788                                     SendToProgram("white\n", &first);
3789                                   }
3790                                   SendTimeRemaining(&first, FALSE);
3791                                 }
3792                                 if (first.useColors) {
3793                                   SendToProgram("black\n", &first);
3794                                 }
3795                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3796                                 first.maybeThinking = TRUE;
3797                             } else {
3798                                 if (first.usePlayother) {
3799                                   if (first.sendTime) {
3800                                     SendTimeRemaining(&first, FALSE);
3801                                   }
3802                                   SendToProgram("playother\n", &first);
3803                                   firstMove = FALSE;
3804                                 } else {
3805                                   firstMove = TRUE;
3806                                 }
3807                             }
3808                         }
3809                     }
3810 #endif
3811                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3812                         /* Moves came from oldmoves or moves command
3813                            while we weren't doing anything else.
3814                            */
3815                         currentMove = forwardMostMove;
3816                         ClearHighlights();/*!!could figure this out*/
3817                         flipView = appData.flipView;
3818                         DrawPosition(TRUE, boards[currentMove]);
3819                         DisplayBothClocks();
3820                         snprintf(str, MSG_SIZ, "%s %s %s",
3821                                 gameInfo.white, _("vs."),  gameInfo.black);
3822                         DisplayTitle(str);
3823                         gameMode = IcsIdle;
3824                     } else {
3825                         /* Moves were history of an active game */
3826                         if (gameInfo.resultDetails != NULL) {
3827                             free(gameInfo.resultDetails);
3828                             gameInfo.resultDetails = NULL;
3829                         }
3830                     }
3831                     HistorySet(parseList, backwardMostMove,
3832                                forwardMostMove, currentMove-1);
3833                     DisplayMove(currentMove - 1);
3834                     if (started == STARTED_MOVES) next_out = i;
3835                     started = STARTED_NONE;
3836                     ics_getting_history = H_FALSE;
3837                     break;
3838
3839                   case STARTED_OBSERVE:
3840                     started = STARTED_NONE;
3841                     SendToICS(ics_prefix);
3842                     SendToICS("refresh\n");
3843                     break;
3844
3845                   default:
3846                     break;
3847                 }
3848                 if(bookHit) { // [HGM] book: simulate book reply
3849                     static char bookMove[MSG_SIZ]; // a bit generous?
3850
3851                     programStats.nodes = programStats.depth = programStats.time =
3852                     programStats.score = programStats.got_only_move = 0;
3853                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3854
3855                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3856                     strcat(bookMove, bookHit);
3857                     HandleMachineMove(bookMove, &first);
3858                 }
3859                 continue;
3860             }
3861
3862             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3863                  started == STARTED_HOLDINGS ||
3864                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3865                 /* Accumulate characters in move list or board */
3866                 parse[parse_pos++] = buf[i];
3867             }
3868
3869             /* Start of game messages.  Mostly we detect start of game
3870                when the first board image arrives.  On some versions
3871                of the ICS, though, we need to do a "refresh" after starting
3872                to observe in order to get the current board right away. */
3873             if (looking_at(buf, &i, "Adding game * to observation list")) {
3874                 started = STARTED_OBSERVE;
3875                 continue;
3876             }
3877
3878             /* Handle auto-observe */
3879             if (appData.autoObserve &&
3880                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3881                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3882                 char *player;
3883                 /* Choose the player that was highlighted, if any. */
3884                 if (star_match[0][0] == '\033' ||
3885                     star_match[1][0] != '\033') {
3886                     player = star_match[0];
3887                 } else {
3888                     player = star_match[2];
3889                 }
3890                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3891                         ics_prefix, StripHighlightAndTitle(player));
3892                 SendToICS(str);
3893
3894                 /* Save ratings from notify string */
3895                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3896                 player1Rating = string_to_rating(star_match[1]);
3897                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3898                 player2Rating = string_to_rating(star_match[3]);
3899
3900                 if (appData.debugMode)
3901                   fprintf(debugFP,
3902                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3903                           player1Name, player1Rating,
3904                           player2Name, player2Rating);
3905
3906                 continue;
3907             }
3908
3909             /* Deal with automatic examine mode after a game,
3910                and with IcsObserving -> IcsExamining transition */
3911             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3912                 looking_at(buf, &i, "has made you an examiner of game *")) {
3913
3914                 int gamenum = atoi(star_match[0]);
3915                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3916                     gamenum == ics_gamenum) {
3917                     /* We were already playing or observing this game;
3918                        no need to refetch history */
3919                     gameMode = IcsExamining;
3920                     if (pausing) {
3921                         pauseExamForwardMostMove = forwardMostMove;
3922                     } else if (currentMove < forwardMostMove) {
3923                         ForwardInner(forwardMostMove);
3924                     }
3925                 } else {
3926                     /* I don't think this case really can happen */
3927                     SendToICS(ics_prefix);
3928                     SendToICS("refresh\n");
3929                 }
3930                 continue;
3931             }
3932
3933             /* Error messages */
3934 //          if (ics_user_moved) {
3935             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3936                 if (looking_at(buf, &i, "Illegal move") ||
3937                     looking_at(buf, &i, "Not a legal move") ||
3938                     looking_at(buf, &i, "Your king is in check") ||
3939                     looking_at(buf, &i, "It isn't your turn") ||
3940                     looking_at(buf, &i, "It is not your move")) {
3941                     /* Illegal move */
3942                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3943                         currentMove = forwardMostMove-1;
3944                         DisplayMove(currentMove - 1); /* before DMError */
3945                         DrawPosition(FALSE, boards[currentMove]);
3946                         SwitchClocks(forwardMostMove-1); // [HGM] race
3947                         DisplayBothClocks();
3948                     }
3949                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3950                     ics_user_moved = 0;
3951                     continue;
3952                 }
3953             }
3954
3955             if (looking_at(buf, &i, "still have time") ||
3956                 looking_at(buf, &i, "not out of time") ||
3957                 looking_at(buf, &i, "either player is out of time") ||
3958                 looking_at(buf, &i, "has timeseal; checking")) {
3959                 /* We must have called his flag a little too soon */
3960                 whiteFlag = blackFlag = FALSE;
3961                 continue;
3962             }
3963
3964             if (looking_at(buf, &i, "added * seconds to") ||
3965                 looking_at(buf, &i, "seconds were added to")) {
3966                 /* Update the clocks */
3967                 SendToICS(ics_prefix);
3968                 SendToICS("refresh\n");
3969                 continue;
3970             }
3971
3972             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3973                 ics_clock_paused = TRUE;
3974                 StopClocks();
3975                 continue;
3976             }
3977
3978             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3979                 ics_clock_paused = FALSE;
3980                 StartClocks();
3981                 continue;
3982             }
3983
3984             /* Grab player ratings from the Creating: message.
3985                Note we have to check for the special case when
3986                the ICS inserts things like [white] or [black]. */
3987             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3988                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3989                 /* star_matches:
3990                    0    player 1 name (not necessarily white)
3991                    1    player 1 rating
3992                    2    empty, white, or black (IGNORED)
3993                    3    player 2 name (not necessarily black)
3994                    4    player 2 rating
3995
3996                    The names/ratings are sorted out when the game
3997                    actually starts (below).
3998                 */
3999                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4000                 player1Rating = string_to_rating(star_match[1]);
4001                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4002                 player2Rating = string_to_rating(star_match[4]);
4003
4004                 if (appData.debugMode)
4005                   fprintf(debugFP,
4006                           "Ratings from 'Creating:' %s %d, %s %d\n",
4007                           player1Name, player1Rating,
4008                           player2Name, player2Rating);
4009
4010                 continue;
4011             }
4012
4013             /* Improved generic start/end-of-game messages */
4014             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4015                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4016                 /* If tkind == 0: */
4017                 /* star_match[0] is the game number */
4018                 /*           [1] is the white player's name */
4019                 /*           [2] is the black player's name */
4020                 /* For end-of-game: */
4021                 /*           [3] is the reason for the game end */
4022                 /*           [4] is a PGN end game-token, preceded by " " */
4023                 /* For start-of-game: */
4024                 /*           [3] begins with "Creating" or "Continuing" */
4025                 /*           [4] is " *" or empty (don't care). */
4026                 int gamenum = atoi(star_match[0]);
4027                 char *whitename, *blackname, *why, *endtoken;
4028                 ChessMove endtype = EndOfFile;
4029
4030                 if (tkind == 0) {
4031                   whitename = star_match[1];
4032                   blackname = star_match[2];
4033                   why = star_match[3];
4034                   endtoken = star_match[4];
4035                 } else {
4036                   whitename = star_match[1];
4037                   blackname = star_match[3];
4038                   why = star_match[5];
4039                   endtoken = star_match[6];
4040                 }
4041
4042                 /* Game start messages */
4043                 if (strncmp(why, "Creating ", 9) == 0 ||
4044                     strncmp(why, "Continuing ", 11) == 0) {
4045                     gs_gamenum = gamenum;
4046                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4047                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4048                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4049 #if ZIPPY
4050                     if (appData.zippyPlay) {
4051                         ZippyGameStart(whitename, blackname);
4052                     }
4053 #endif /*ZIPPY*/
4054                     partnerBoardValid = FALSE; // [HGM] bughouse
4055                     continue;
4056                 }
4057
4058                 /* Game end messages */
4059                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4060                     ics_gamenum != gamenum) {
4061                     continue;
4062                 }
4063                 while (endtoken[0] == ' ') endtoken++;
4064                 switch (endtoken[0]) {
4065                   case '*':
4066                   default:
4067                     endtype = GameUnfinished;
4068                     break;
4069                   case '0':
4070                     endtype = BlackWins;
4071                     break;
4072                   case '1':
4073                     if (endtoken[1] == '/')
4074                       endtype = GameIsDrawn;
4075                     else
4076                       endtype = WhiteWins;
4077                     break;
4078                 }
4079                 GameEnds(endtype, why, GE_ICS);
4080 #if ZIPPY
4081                 if (appData.zippyPlay && first.initDone) {
4082                     ZippyGameEnd(endtype, why);
4083                     if (first.pr == NoProc) {
4084                       /* Start the next process early so that we'll
4085                          be ready for the next challenge */
4086                       StartChessProgram(&first);
4087                     }
4088                     /* Send "new" early, in case this command takes
4089                        a long time to finish, so that we'll be ready
4090                        for the next challenge. */
4091                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4092                     Reset(TRUE, TRUE);
4093                 }
4094 #endif /*ZIPPY*/
4095                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4096                 continue;
4097             }
4098
4099             if (looking_at(buf, &i, "Removing game * from observation") ||
4100                 looking_at(buf, &i, "no longer observing game *") ||
4101                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4102                 if (gameMode == IcsObserving &&
4103                     atoi(star_match[0]) == ics_gamenum)
4104                   {
4105                       /* icsEngineAnalyze */
4106                       if (appData.icsEngineAnalyze) {
4107                             ExitAnalyzeMode();
4108                             ModeHighlight();
4109                       }
4110                       StopClocks();
4111                       gameMode = IcsIdle;
4112                       ics_gamenum = -1;
4113                       ics_user_moved = FALSE;
4114                   }
4115                 continue;
4116             }
4117
4118             if (looking_at(buf, &i, "no longer examining game *")) {
4119                 if (gameMode == IcsExamining &&
4120                     atoi(star_match[0]) == ics_gamenum)
4121                   {
4122                       gameMode = IcsIdle;
4123                       ics_gamenum = -1;
4124                       ics_user_moved = FALSE;
4125                   }
4126                 continue;
4127             }
4128
4129             /* Advance leftover_start past any newlines we find,
4130                so only partial lines can get reparsed */
4131             if (looking_at(buf, &i, "\n")) {
4132                 prevColor = curColor;
4133                 if (curColor != ColorNormal) {
4134                     if (oldi > next_out) {
4135                         SendToPlayer(&buf[next_out], oldi - next_out);
4136                         next_out = oldi;
4137                     }
4138                     Colorize(ColorNormal, FALSE);
4139                     curColor = ColorNormal;
4140                 }
4141                 if (started == STARTED_BOARD) {
4142                     started = STARTED_NONE;
4143                     parse[parse_pos] = NULLCHAR;
4144                     ParseBoard12(parse);
4145                     ics_user_moved = 0;
4146
4147                     /* Send premove here */
4148                     if (appData.premove) {
4149                       char str[MSG_SIZ];
4150                       if (currentMove == 0 &&
4151                           gameMode == IcsPlayingWhite &&
4152                           appData.premoveWhite) {
4153                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4154                         if (appData.debugMode)
4155                           fprintf(debugFP, "Sending premove:\n");
4156                         SendToICS(str);
4157                       } else if (currentMove == 1 &&
4158                                  gameMode == IcsPlayingBlack &&
4159                                  appData.premoveBlack) {
4160                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4161                         if (appData.debugMode)
4162                           fprintf(debugFP, "Sending premove:\n");
4163                         SendToICS(str);
4164                       } else if (gotPremove) {
4165                         int oldFMM = forwardMostMove;
4166                         gotPremove = 0;
4167                         ClearPremoveHighlights();
4168                         if (appData.debugMode)
4169                           fprintf(debugFP, "Sending premove:\n");
4170                           UserMoveEvent(premoveFromX, premoveFromY,
4171                                         premoveToX, premoveToY,
4172                                         premovePromoChar);
4173                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4174                           if(moveList[oldFMM-1][1] != '@')
4175                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4176                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4177                           else // (drop)
4178                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4179                         }
4180                       }
4181                     }
4182
4183                     /* Usually suppress following prompt */
4184                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4185                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4186                         if (looking_at(buf, &i, "*% ")) {
4187                             savingComment = FALSE;
4188                             suppressKibitz = 0;
4189                         }
4190                     }
4191                     next_out = i;
4192                 } else if (started == STARTED_HOLDINGS) {
4193                     int gamenum;
4194                     char new_piece[MSG_SIZ];
4195                     started = STARTED_NONE;
4196                     parse[parse_pos] = NULLCHAR;
4197                     if (appData.debugMode)
4198                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4199                                                         parse, currentMove);
4200                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4201                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4202                         if (gameInfo.variant == VariantNormal) {
4203                           /* [HGM] We seem to switch variant during a game!
4204                            * Presumably no holdings were displayed, so we have
4205                            * to move the position two files to the right to
4206                            * create room for them!
4207                            */
4208                           VariantClass newVariant;
4209                           switch(gameInfo.boardWidth) { // base guess on board width
4210                                 case 9:  newVariant = VariantShogi; break;
4211                                 case 10: newVariant = VariantGreat; break;
4212                                 default: newVariant = VariantCrazyhouse; break;
4213                           }
4214                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4215                           /* Get a move list just to see the header, which
4216                              will tell us whether this is really bug or zh */
4217                           if (ics_getting_history == H_FALSE) {
4218                             ics_getting_history = H_REQUESTED;
4219                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4220                             SendToICS(str);
4221                           }
4222                         }
4223                         new_piece[0] = NULLCHAR;
4224                         sscanf(parse, "game %d white [%s black [%s <- %s",
4225                                &gamenum, white_holding, black_holding,
4226                                new_piece);
4227                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4228                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4229                         /* [HGM] copy holdings to board holdings area */
4230                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4231                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4232                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4233 #if ZIPPY
4234                         if (appData.zippyPlay && first.initDone) {
4235                             ZippyHoldings(white_holding, black_holding,
4236                                           new_piece);
4237                         }
4238 #endif /*ZIPPY*/
4239                         if (tinyLayout || smallLayout) {
4240                             char wh[16], bh[16];
4241                             PackHolding(wh, white_holding);
4242                             PackHolding(bh, black_holding);
4243                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4244                                     gameInfo.white, gameInfo.black);
4245                         } else {
4246                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4247                                     gameInfo.white, white_holding, _("vs."),
4248                                     gameInfo.black, black_holding);
4249                         }
4250                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4251                         DrawPosition(FALSE, boards[currentMove]);
4252                         DisplayTitle(str);
4253                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4254                         sscanf(parse, "game %d white [%s black [%s <- %s",
4255                                &gamenum, white_holding, black_holding,
4256                                new_piece);
4257                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4258                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4259                         /* [HGM] copy holdings to partner-board holdings area */
4260                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4261                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4262                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4263                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4264                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4265                       }
4266                     }
4267                     /* Suppress following prompt */
4268                     if (looking_at(buf, &i, "*% ")) {
4269                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4270                         savingComment = FALSE;
4271                         suppressKibitz = 0;
4272                     }
4273                     next_out = i;
4274                 }
4275                 continue;
4276             }
4277
4278             i++;                /* skip unparsed character and loop back */
4279         }
4280
4281         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4282 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4283 //          SendToPlayer(&buf[next_out], i - next_out);
4284             started != STARTED_HOLDINGS && leftover_start > next_out) {
4285             SendToPlayer(&buf[next_out], leftover_start - next_out);
4286             next_out = i;
4287         }
4288
4289         leftover_len = buf_len - leftover_start;
4290         /* if buffer ends with something we couldn't parse,
4291            reparse it after appending the next read */
4292
4293     } else if (count == 0) {
4294         RemoveInputSource(isr);
4295         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4296     } else {
4297         DisplayFatalError(_("Error reading from ICS"), error, 1);
4298     }
4299 }
4300
4301
4302 /* Board style 12 looks like this:
4303
4304    <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
4305
4306  * The "<12> " is stripped before it gets to this routine.  The two
4307  * trailing 0's (flip state and clock ticking) are later addition, and
4308  * some chess servers may not have them, or may have only the first.
4309  * Additional trailing fields may be added in the future.
4310  */
4311
4312 #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"
4313
4314 #define RELATION_OBSERVING_PLAYED    0
4315 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4316 #define RELATION_PLAYING_MYMOVE      1
4317 #define RELATION_PLAYING_NOTMYMOVE  -1
4318 #define RELATION_EXAMINING           2
4319 #define RELATION_ISOLATED_BOARD     -3
4320 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4321
4322 void
4323 ParseBoard12 (char *string)
4324 {
4325 #if ZIPPY
4326     int i, takeback;
4327     char *bookHit = NULL; // [HGM] book
4328 #endif
4329     GameMode newGameMode;
4330     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4331     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4332     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4333     char to_play, board_chars[200];
4334     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4335     char black[32], white[32];
4336     Board board;
4337     int prevMove = currentMove;
4338     int ticking = 2;
4339     ChessMove moveType;
4340     int fromX, fromY, toX, toY;
4341     char promoChar;
4342     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4343     Boolean weird = FALSE, reqFlag = FALSE;
4344
4345     fromX = fromY = toX = toY = -1;
4346
4347     newGame = FALSE;
4348
4349     if (appData.debugMode)
4350       fprintf(debugFP, "Parsing board: %s\n", string);
4351
4352     move_str[0] = NULLCHAR;
4353     elapsed_time[0] = NULLCHAR;
4354     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4355         int  i = 0, j;
4356         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4357             if(string[i] == ' ') { ranks++; files = 0; }
4358             else files++;
4359             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4360             i++;
4361         }
4362         for(j = 0; j <i; j++) board_chars[j] = string[j];
4363         board_chars[i] = '\0';
4364         string += i + 1;
4365     }
4366     n = sscanf(string, PATTERN, &to_play, &double_push,
4367                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4368                &gamenum, white, black, &relation, &basetime, &increment,
4369                &white_stren, &black_stren, &white_time, &black_time,
4370                &moveNum, str, elapsed_time, move_str, &ics_flip,
4371                &ticking);
4372
4373     if (n < 21) {
4374         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4375         DisplayError(str, 0);
4376         return;
4377     }
4378
4379     /* Convert the move number to internal form */
4380     moveNum = (moveNum - 1) * 2;
4381     if (to_play == 'B') moveNum++;
4382     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4383       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4384                         0, 1);
4385       return;
4386     }
4387
4388     switch (relation) {
4389       case RELATION_OBSERVING_PLAYED:
4390       case RELATION_OBSERVING_STATIC:
4391         if (gamenum == -1) {
4392             /* Old ICC buglet */
4393             relation = RELATION_OBSERVING_STATIC;
4394         }
4395         newGameMode = IcsObserving;
4396         break;
4397       case RELATION_PLAYING_MYMOVE:
4398       case RELATION_PLAYING_NOTMYMOVE:
4399         newGameMode =
4400           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4401             IcsPlayingWhite : IcsPlayingBlack;
4402         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4403         break;
4404       case RELATION_EXAMINING:
4405         newGameMode = IcsExamining;
4406         break;
4407       case RELATION_ISOLATED_BOARD:
4408       default:
4409         /* Just display this board.  If user was doing something else,
4410            we will forget about it until the next board comes. */
4411         newGameMode = IcsIdle;
4412         break;
4413       case RELATION_STARTING_POSITION:
4414         newGameMode = gameMode;
4415         break;
4416     }
4417
4418     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4419         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4420          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4421       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4422       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4423       static int lastBgGame = -1;
4424       char *toSqr;
4425       for (k = 0; k < ranks; k++) {
4426         for (j = 0; j < files; j++)
4427           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4428         if(gameInfo.holdingsWidth > 1) {
4429              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4430              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4431         }
4432       }
4433       CopyBoard(partnerBoard, board);
4434       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4435         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4436         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4437       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4438       if(toSqr = strchr(str, '-')) {
4439         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4440         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4441       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4442       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4443       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4444       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4445       if(twoBoards) {
4446           DisplayWhiteClock(white_time*fac, to_play == 'W');
4447           DisplayBlackClock(black_time*fac, to_play != 'W');
4448           activePartner = to_play;
4449           if(gamenum != lastBgGame) {
4450               char buf[MSG_SIZ];
4451               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4452               DisplayTitle(buf);
4453           }
4454           lastBgGame = gamenum;
4455           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4456                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4457       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4458                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4459       if(!twoBoards) DisplayMessage(partnerStatus, "");
4460         partnerBoardValid = TRUE;
4461       return;
4462     }
4463
4464     if(appData.dualBoard && appData.bgObserve) {
4465         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4466             SendToICS(ics_prefix), SendToICS("pobserve\n");
4467         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4468             char buf[MSG_SIZ];
4469             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4470             SendToICS(buf);
4471         }
4472     }
4473
4474     /* Modify behavior for initial board display on move listing
4475        of wild games.
4476        */
4477     switch (ics_getting_history) {
4478       case H_FALSE:
4479       case H_REQUESTED:
4480         break;
4481       case H_GOT_REQ_HEADER:
4482       case H_GOT_UNREQ_HEADER:
4483         /* This is the initial position of the current game */
4484         gamenum = ics_gamenum;
4485         moveNum = 0;            /* old ICS bug workaround */
4486         if (to_play == 'B') {
4487           startedFromSetupPosition = TRUE;
4488           blackPlaysFirst = TRUE;
4489           moveNum = 1;
4490           if (forwardMostMove == 0) forwardMostMove = 1;
4491           if (backwardMostMove == 0) backwardMostMove = 1;
4492           if (currentMove == 0) currentMove = 1;
4493         }
4494         newGameMode = gameMode;
4495         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4496         break;
4497       case H_GOT_UNWANTED_HEADER:
4498         /* This is an initial board that we don't want */
4499         return;
4500       case H_GETTING_MOVES:
4501         /* Should not happen */
4502         DisplayError(_("Error gathering move list: extra board"), 0);
4503         ics_getting_history = H_FALSE;
4504         return;
4505     }
4506
4507    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4508                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4509                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4510      /* [HGM] We seem to have switched variant unexpectedly
4511       * Try to guess new variant from board size
4512       */
4513           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4514           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4515           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4516           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4517           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4518           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4519           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4520           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4521           /* Get a move list just to see the header, which
4522              will tell us whether this is really bug or zh */
4523           if (ics_getting_history == H_FALSE) {
4524             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4525             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4526             SendToICS(str);
4527           }
4528     }
4529
4530     /* Take action if this is the first board of a new game, or of a
4531        different game than is currently being displayed.  */
4532     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4533         relation == RELATION_ISOLATED_BOARD) {
4534
4535         /* Forget the old game and get the history (if any) of the new one */
4536         if (gameMode != BeginningOfGame) {
4537           Reset(TRUE, TRUE);
4538         }
4539         newGame = TRUE;
4540         if (appData.autoRaiseBoard) BoardToTop();
4541         prevMove = -3;
4542         if (gamenum == -1) {
4543             newGameMode = IcsIdle;
4544         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4545                    appData.getMoveList && !reqFlag) {
4546             /* Need to get game history */
4547             ics_getting_history = H_REQUESTED;
4548             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4549             SendToICS(str);
4550         }
4551
4552         /* Initially flip the board to have black on the bottom if playing
4553            black or if the ICS flip flag is set, but let the user change
4554            it with the Flip View button. */
4555         flipView = appData.autoFlipView ?
4556           (newGameMode == IcsPlayingBlack) || ics_flip :
4557           appData.flipView;
4558
4559         /* Done with values from previous mode; copy in new ones */
4560         gameMode = newGameMode;
4561         ModeHighlight();
4562         ics_gamenum = gamenum;
4563         if (gamenum == gs_gamenum) {
4564             int klen = strlen(gs_kind);
4565             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4566             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4567             gameInfo.event = StrSave(str);
4568         } else {
4569             gameInfo.event = StrSave("ICS game");
4570         }
4571         gameInfo.site = StrSave(appData.icsHost);
4572         gameInfo.date = PGNDate();
4573         gameInfo.round = StrSave("-");
4574         gameInfo.white = StrSave(white);
4575         gameInfo.black = StrSave(black);
4576         timeControl = basetime * 60 * 1000;
4577         timeControl_2 = 0;
4578         timeIncrement = increment * 1000;
4579         movesPerSession = 0;
4580         gameInfo.timeControl = TimeControlTagValue();
4581         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4582   if (appData.debugMode) {
4583     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4584     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4585     setbuf(debugFP, NULL);
4586   }
4587
4588         gameInfo.outOfBook = NULL;
4589
4590         /* Do we have the ratings? */
4591         if (strcmp(player1Name, white) == 0 &&
4592             strcmp(player2Name, black) == 0) {
4593             if (appData.debugMode)
4594               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4595                       player1Rating, player2Rating);
4596             gameInfo.whiteRating = player1Rating;
4597             gameInfo.blackRating = player2Rating;
4598         } else if (strcmp(player2Name, white) == 0 &&
4599                    strcmp(player1Name, black) == 0) {
4600             if (appData.debugMode)
4601               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4602                       player2Rating, player1Rating);
4603             gameInfo.whiteRating = player2Rating;
4604             gameInfo.blackRating = player1Rating;
4605         }
4606         player1Name[0] = player2Name[0] = NULLCHAR;
4607
4608         /* Silence shouts if requested */
4609         if (appData.quietPlay &&
4610             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4611             SendToICS(ics_prefix);
4612             SendToICS("set shout 0\n");
4613         }
4614     }
4615
4616     /* Deal with midgame name changes */
4617     if (!newGame) {
4618         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4619             if (gameInfo.white) free(gameInfo.white);
4620             gameInfo.white = StrSave(white);
4621         }
4622         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4623             if (gameInfo.black) free(gameInfo.black);
4624             gameInfo.black = StrSave(black);
4625         }
4626     }
4627
4628     /* Throw away game result if anything actually changes in examine mode */
4629     if (gameMode == IcsExamining && !newGame) {
4630         gameInfo.result = GameUnfinished;
4631         if (gameInfo.resultDetails != NULL) {
4632             free(gameInfo.resultDetails);
4633             gameInfo.resultDetails = NULL;
4634         }
4635     }
4636
4637     /* In pausing && IcsExamining mode, we ignore boards coming
4638        in if they are in a different variation than we are. */
4639     if (pauseExamInvalid) return;
4640     if (pausing && gameMode == IcsExamining) {
4641         if (moveNum <= pauseExamForwardMostMove) {
4642             pauseExamInvalid = TRUE;
4643             forwardMostMove = pauseExamForwardMostMove;
4644             return;
4645         }
4646     }
4647
4648   if (appData.debugMode) {
4649     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4650   }
4651     /* Parse the board */
4652     for (k = 0; k < ranks; k++) {
4653       for (j = 0; j < files; j++)
4654         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4655       if(gameInfo.holdingsWidth > 1) {
4656            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4657            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4658       }
4659     }
4660     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4661       board[5][BOARD_RGHT+1] = WhiteAngel;
4662       board[6][BOARD_RGHT+1] = WhiteMarshall;
4663       board[1][0] = BlackMarshall;
4664       board[2][0] = BlackAngel;
4665       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4666     }
4667     CopyBoard(boards[moveNum], board);
4668     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4669     if (moveNum == 0) {
4670         startedFromSetupPosition =
4671           !CompareBoards(board, initialPosition);
4672         if(startedFromSetupPosition)
4673             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4674     }
4675
4676     /* [HGM] Set castling rights. Take the outermost Rooks,
4677        to make it also work for FRC opening positions. Note that board12
4678        is really defective for later FRC positions, as it has no way to
4679        indicate which Rook can castle if they are on the same side of King.
4680        For the initial position we grant rights to the outermost Rooks,
4681        and remember thos rights, and we then copy them on positions
4682        later in an FRC game. This means WB might not recognize castlings with
4683        Rooks that have moved back to their original position as illegal,
4684        but in ICS mode that is not its job anyway.
4685     */
4686     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4687     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4688
4689         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4690             if(board[0][i] == WhiteRook) j = i;
4691         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4692         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4693             if(board[0][i] == WhiteRook) j = i;
4694         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4695         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4696             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4697         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4698         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4699             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4700         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4701
4702         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4703         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4704         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4705             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4706         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4707             if(board[BOARD_HEIGHT-1][k] == bKing)
4708                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4709         if(gameInfo.variant == VariantTwoKings) {
4710             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4711             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4712             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4713         }
4714     } else { int r;
4715         r = boards[moveNum][CASTLING][0] = initialRights[0];
4716         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4717         r = boards[moveNum][CASTLING][1] = initialRights[1];
4718         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4719         r = boards[moveNum][CASTLING][3] = initialRights[3];
4720         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4721         r = boards[moveNum][CASTLING][4] = initialRights[4];
4722         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4723         /* wildcastle kludge: always assume King has rights */
4724         r = boards[moveNum][CASTLING][2] = initialRights[2];
4725         r = boards[moveNum][CASTLING][5] = initialRights[5];
4726     }
4727     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4728     boards[moveNum][EP_STATUS] = EP_NONE;
4729     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4730     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4731     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4732
4733
4734     if (ics_getting_history == H_GOT_REQ_HEADER ||
4735         ics_getting_history == H_GOT_UNREQ_HEADER) {
4736         /* This was an initial position from a move list, not
4737            the current position */
4738         return;
4739     }
4740
4741     /* Update currentMove and known move number limits */
4742     newMove = newGame || moveNum > forwardMostMove;
4743
4744     if (newGame) {
4745         forwardMostMove = backwardMostMove = currentMove = moveNum;
4746         if (gameMode == IcsExamining && moveNum == 0) {
4747           /* Workaround for ICS limitation: we are not told the wild
4748              type when starting to examine a game.  But if we ask for
4749              the move list, the move list header will tell us */
4750             ics_getting_history = H_REQUESTED;
4751             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4752             SendToICS(str);
4753         }
4754     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4755                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4756 #if ZIPPY
4757         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4758         /* [HGM] applied this also to an engine that is silently watching        */
4759         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4760             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4761             gameInfo.variant == currentlyInitializedVariant) {
4762           takeback = forwardMostMove - moveNum;
4763           for (i = 0; i < takeback; i++) {
4764             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4765             SendToProgram("undo\n", &first);
4766           }
4767         }
4768 #endif
4769
4770         forwardMostMove = moveNum;
4771         if (!pausing || currentMove > forwardMostMove)
4772           currentMove = forwardMostMove;
4773     } else {
4774         /* New part of history that is not contiguous with old part */
4775         if (pausing && gameMode == IcsExamining) {
4776             pauseExamInvalid = TRUE;
4777             forwardMostMove = pauseExamForwardMostMove;
4778             return;
4779         }
4780         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4781 #if ZIPPY
4782             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4783                 // [HGM] when we will receive the move list we now request, it will be
4784                 // fed to the engine from the first move on. So if the engine is not
4785                 // in the initial position now, bring it there.
4786                 InitChessProgram(&first, 0);
4787             }
4788 #endif
4789             ics_getting_history = H_REQUESTED;
4790             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4791             SendToICS(str);
4792         }
4793         forwardMostMove = backwardMostMove = currentMove = moveNum;
4794     }
4795
4796     /* Update the clocks */
4797     if (strchr(elapsed_time, '.')) {
4798       /* Time is in ms */
4799       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4800       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4801     } else {
4802       /* Time is in seconds */
4803       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4804       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4805     }
4806
4807
4808 #if ZIPPY
4809     if (appData.zippyPlay && newGame &&
4810         gameMode != IcsObserving && gameMode != IcsIdle &&
4811         gameMode != IcsExamining)
4812       ZippyFirstBoard(moveNum, basetime, increment);
4813 #endif
4814
4815     /* Put the move on the move list, first converting
4816        to canonical algebraic form. */
4817     if (moveNum > 0) {
4818   if (appData.debugMode) {
4819     int f = forwardMostMove;
4820     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4821             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4822             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4823     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4824     fprintf(debugFP, "moveNum = %d\n", moveNum);
4825     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4826     setbuf(debugFP, NULL);
4827   }
4828         if (moveNum <= backwardMostMove) {
4829             /* We don't know what the board looked like before
4830                this move.  Punt. */
4831           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4832             strcat(parseList[moveNum - 1], " ");
4833             strcat(parseList[moveNum - 1], elapsed_time);
4834             moveList[moveNum - 1][0] = NULLCHAR;
4835         } else if (strcmp(move_str, "none") == 0) {
4836             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4837             /* Again, we don't know what the board looked like;
4838                this is really the start of the game. */
4839             parseList[moveNum - 1][0] = NULLCHAR;
4840             moveList[moveNum - 1][0] = NULLCHAR;
4841             backwardMostMove = moveNum;
4842             startedFromSetupPosition = TRUE;
4843             fromX = fromY = toX = toY = -1;
4844         } else {
4845           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4846           //                 So we parse the long-algebraic move string in stead of the SAN move
4847           int valid; char buf[MSG_SIZ], *prom;
4848
4849           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4850                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4851           // str looks something like "Q/a1-a2"; kill the slash
4852           if(str[1] == '/')
4853             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4854           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4855           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4856                 strcat(buf, prom); // long move lacks promo specification!
4857           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4858                 if(appData.debugMode)
4859                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4860                 safeStrCpy(move_str, buf, MSG_SIZ);
4861           }
4862           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4863                                 &fromX, &fromY, &toX, &toY, &promoChar)
4864                || ParseOneMove(buf, moveNum - 1, &moveType,
4865                                 &fromX, &fromY, &toX, &toY, &promoChar);
4866           // end of long SAN patch
4867           if (valid) {
4868             (void) CoordsToAlgebraic(boards[moveNum - 1],
4869                                      PosFlags(moveNum - 1),
4870                                      fromY, fromX, toY, toX, promoChar,
4871                                      parseList[moveNum-1]);
4872             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4873               case MT_NONE:
4874               case MT_STALEMATE:
4875               default:
4876                 break;
4877               case MT_CHECK:
4878                 if(!IS_SHOGI(gameInfo.variant))
4879                     strcat(parseList[moveNum - 1], "+");
4880                 break;
4881               case MT_CHECKMATE:
4882               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4883                 strcat(parseList[moveNum - 1], "#");
4884                 break;
4885             }
4886             strcat(parseList[moveNum - 1], " ");
4887             strcat(parseList[moveNum - 1], elapsed_time);
4888             /* currentMoveString is set as a side-effect of ParseOneMove */
4889             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4890             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4891             strcat(moveList[moveNum - 1], "\n");
4892
4893             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4894                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4895               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4896                 ChessSquare old, new = boards[moveNum][k][j];
4897                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4898                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4899                   if(old == new) continue;
4900                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4901                   else if(new == WhiteWazir || new == BlackWazir) {
4902                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4903                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4904                       else boards[moveNum][k][j] = old; // preserve type of Gold
4905                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4906                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4907               }
4908           } else {
4909             /* Move from ICS was illegal!?  Punt. */
4910             if (appData.debugMode) {
4911               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4912               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4913             }
4914             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4915             strcat(parseList[moveNum - 1], " ");
4916             strcat(parseList[moveNum - 1], elapsed_time);
4917             moveList[moveNum - 1][0] = NULLCHAR;
4918             fromX = fromY = toX = toY = -1;
4919           }
4920         }
4921   if (appData.debugMode) {
4922     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4923     setbuf(debugFP, NULL);
4924   }
4925
4926 #if ZIPPY
4927         /* Send move to chess program (BEFORE animating it). */
4928         if (appData.zippyPlay && !newGame && newMove &&
4929            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4930
4931             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4932                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4933                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4934                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4935                             move_str);
4936                     DisplayError(str, 0);
4937                 } else {
4938                     if (first.sendTime) {
4939                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4940                     }
4941                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4942                     if (firstMove && !bookHit) {
4943                         firstMove = FALSE;
4944                         if (first.useColors) {
4945                           SendToProgram(gameMode == IcsPlayingWhite ?
4946                                         "white\ngo\n" :
4947                                         "black\ngo\n", &first);
4948                         } else {
4949                           SendToProgram("go\n", &first);
4950                         }
4951                         first.maybeThinking = TRUE;
4952                     }
4953                 }
4954             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4955               if (moveList[moveNum - 1][0] == NULLCHAR) {
4956                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4957                 DisplayError(str, 0);
4958               } else {
4959                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4960                 SendMoveToProgram(moveNum - 1, &first);
4961               }
4962             }
4963         }
4964 #endif
4965     }
4966
4967     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4968         /* If move comes from a remote source, animate it.  If it
4969            isn't remote, it will have already been animated. */
4970         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4971             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4972         }
4973         if (!pausing && appData.highlightLastMove) {
4974             SetHighlights(fromX, fromY, toX, toY);
4975         }
4976     }
4977
4978     /* Start the clocks */
4979     whiteFlag = blackFlag = FALSE;
4980     appData.clockMode = !(basetime == 0 && increment == 0);
4981     if (ticking == 0) {
4982       ics_clock_paused = TRUE;
4983       StopClocks();
4984     } else if (ticking == 1) {
4985       ics_clock_paused = FALSE;
4986     }
4987     if (gameMode == IcsIdle ||
4988         relation == RELATION_OBSERVING_STATIC ||
4989         relation == RELATION_EXAMINING ||
4990         ics_clock_paused)
4991       DisplayBothClocks();
4992     else
4993       StartClocks();
4994
4995     /* Display opponents and material strengths */
4996     if (gameInfo.variant != VariantBughouse &&
4997         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4998         if (tinyLayout || smallLayout) {
4999             if(gameInfo.variant == VariantNormal)
5000               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5001                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5002                     basetime, increment);
5003             else
5004               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5005                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5006                     basetime, increment, (int) gameInfo.variant);
5007         } else {
5008             if(gameInfo.variant == VariantNormal)
5009               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5010                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5011                     basetime, increment);
5012             else
5013               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5014                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5015                     basetime, increment, VariantName(gameInfo.variant));
5016         }
5017         DisplayTitle(str);
5018   if (appData.debugMode) {
5019     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5020   }
5021     }
5022
5023
5024     /* Display the board */
5025     if (!pausing && !appData.noGUI) {
5026
5027       if (appData.premove)
5028           if (!gotPremove ||
5029              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5030              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5031               ClearPremoveHighlights();
5032
5033       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5034         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5035       DrawPosition(j, boards[currentMove]);
5036
5037       DisplayMove(moveNum - 1);
5038       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5039             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5040               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5041         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5042       }
5043     }
5044
5045     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5046 #if ZIPPY
5047     if(bookHit) { // [HGM] book: simulate book reply
5048         static char bookMove[MSG_SIZ]; // a bit generous?
5049
5050         programStats.nodes = programStats.depth = programStats.time =
5051         programStats.score = programStats.got_only_move = 0;
5052         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5053
5054         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5055         strcat(bookMove, bookHit);
5056         HandleMachineMove(bookMove, &first);
5057     }
5058 #endif
5059 }
5060
5061 void
5062 GetMoveListEvent ()
5063 {
5064     char buf[MSG_SIZ];
5065     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5066         ics_getting_history = H_REQUESTED;
5067         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5068         SendToICS(buf);
5069     }
5070 }
5071
5072 void
5073 SendToBoth (char *msg)
5074 {   // to make it easy to keep two engines in step in dual analysis
5075     SendToProgram(msg, &first);
5076     if(second.analyzing) SendToProgram(msg, &second);
5077 }
5078
5079 void
5080 AnalysisPeriodicEvent (int force)
5081 {
5082     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5083          && !force) || !appData.periodicUpdates)
5084       return;
5085
5086     /* Send . command to Crafty to collect stats */
5087     SendToBoth(".\n");
5088
5089     /* Don't send another until we get a response (this makes
5090        us stop sending to old Crafty's which don't understand
5091        the "." command (sending illegal cmds resets node count & time,
5092        which looks bad)) */
5093     programStats.ok_to_send = 0;
5094 }
5095
5096 void
5097 ics_update_width (int new_width)
5098 {
5099         ics_printf("set width %d\n", new_width);
5100 }
5101
5102 void
5103 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5104 {
5105     char buf[MSG_SIZ];
5106
5107     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5108         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5109             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5110             SendToProgram(buf, cps);
5111             return;
5112         }
5113         // null move in variant where engine does not understand it (for analysis purposes)
5114         SendBoard(cps, moveNum + 1); // send position after move in stead.
5115         return;
5116     }
5117     if (cps->useUsermove) {
5118       SendToProgram("usermove ", cps);
5119     }
5120     if (cps->useSAN) {
5121       char *space;
5122       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5123         int len = space - parseList[moveNum];
5124         memcpy(buf, parseList[moveNum], len);
5125         buf[len++] = '\n';
5126         buf[len] = NULLCHAR;
5127       } else {
5128         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5129       }
5130       SendToProgram(buf, cps);
5131     } else {
5132       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5133         AlphaRank(moveList[moveNum], 4);
5134         SendToProgram(moveList[moveNum], cps);
5135         AlphaRank(moveList[moveNum], 4); // and back
5136       } else
5137       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5138        * the engine. It would be nice to have a better way to identify castle
5139        * moves here. */
5140       if(appData.fischerCastling && cps->useOOCastle) {
5141         int fromX = moveList[moveNum][0] - AAA;
5142         int fromY = moveList[moveNum][1] - ONE;
5143         int toX = moveList[moveNum][2] - AAA;
5144         int toY = moveList[moveNum][3] - ONE;
5145         if((boards[moveNum][fromY][fromX] == WhiteKing
5146             && boards[moveNum][toY][toX] == WhiteRook)
5147            || (boards[moveNum][fromY][fromX] == BlackKing
5148                && boards[moveNum][toY][toX] == BlackRook)) {
5149           if(toX > fromX) SendToProgram("O-O\n", cps);
5150           else SendToProgram("O-O-O\n", cps);
5151         }
5152         else SendToProgram(moveList[moveNum], cps);
5153       } else
5154       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5155         char *m = moveList[moveNum];
5156         static char c[2];
5157         *c = m[7]; // promoChar
5158         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
5159           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5160                                                m[2], m[3] - '0',
5161                                                m[5], m[6] - '0',
5162                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5163         else
5164           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5165                                                m[5], m[6] - '0',
5166                                                m[5], m[6] - '0',
5167                                                m[2], m[3] - '0', c);
5168           SendToProgram(buf, cps);
5169       } else
5170       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5171         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5172           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5173           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5174                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5175         } else
5176           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5177                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5178         SendToProgram(buf, cps);
5179       }
5180       else SendToProgram(moveList[moveNum], cps);
5181       /* End of additions by Tord */
5182     }
5183
5184     /* [HGM] setting up the opening has brought engine in force mode! */
5185     /*       Send 'go' if we are in a mode where machine should play. */
5186     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5187         (gameMode == TwoMachinesPlay   ||
5188 #if ZIPPY
5189          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5190 #endif
5191          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5192         SendToProgram("go\n", cps);
5193   if (appData.debugMode) {
5194     fprintf(debugFP, "(extra)\n");
5195   }
5196     }
5197     setboardSpoiledMachineBlack = 0;
5198 }
5199
5200 void
5201 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5202 {
5203     char user_move[MSG_SIZ];
5204     char suffix[4];
5205
5206     if(gameInfo.variant == VariantSChess && promoChar) {
5207         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5208         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5209     } else suffix[0] = NULLCHAR;
5210
5211     switch (moveType) {
5212       default:
5213         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5214                 (int)moveType, fromX, fromY, toX, toY);
5215         DisplayError(user_move + strlen("say "), 0);
5216         break;
5217       case WhiteKingSideCastle:
5218       case BlackKingSideCastle:
5219       case WhiteQueenSideCastleWild:
5220       case BlackQueenSideCastleWild:
5221       /* PUSH Fabien */
5222       case WhiteHSideCastleFR:
5223       case BlackHSideCastleFR:
5224       /* POP Fabien */
5225         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5226         break;
5227       case WhiteQueenSideCastle:
5228       case BlackQueenSideCastle:
5229       case WhiteKingSideCastleWild:
5230       case BlackKingSideCastleWild:
5231       /* PUSH Fabien */
5232       case WhiteASideCastleFR:
5233       case BlackASideCastleFR:
5234       /* POP Fabien */
5235         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5236         break;
5237       case WhiteNonPromotion:
5238       case BlackNonPromotion:
5239         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5240         break;
5241       case WhitePromotion:
5242       case BlackPromotion:
5243         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5244            gameInfo.variant == VariantMakruk)
5245           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5246                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5247                 PieceToChar(WhiteFerz));
5248         else if(gameInfo.variant == VariantGreat)
5249           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5250                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5251                 PieceToChar(WhiteMan));
5252         else
5253           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5254                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5255                 promoChar);
5256         break;
5257       case WhiteDrop:
5258       case BlackDrop:
5259       drop:
5260         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5261                  ToUpper(PieceToChar((ChessSquare) fromX)),
5262                  AAA + toX, ONE + toY);
5263         break;
5264       case IllegalMove:  /* could be a variant we don't quite understand */
5265         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5266       case NormalMove:
5267       case WhiteCapturesEnPassant:
5268       case BlackCapturesEnPassant:
5269         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5270                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5271         break;
5272     }
5273     SendToICS(user_move);
5274     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5275         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5276 }
5277
5278 void
5279 UploadGameEvent ()
5280 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5281     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5282     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5283     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5284       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5285       return;
5286     }
5287     if(gameMode != IcsExamining) { // is this ever not the case?
5288         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5289
5290         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5291           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5292         } else { // on FICS we must first go to general examine mode
5293           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5294         }
5295         if(gameInfo.variant != VariantNormal) {
5296             // try figure out wild number, as xboard names are not always valid on ICS
5297             for(i=1; i<=36; i++) {
5298               snprintf(buf, MSG_SIZ, "wild/%d", i);
5299                 if(StringToVariant(buf) == gameInfo.variant) break;
5300             }
5301             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5302             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5303             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5304         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5305         SendToICS(ics_prefix);
5306         SendToICS(buf);
5307         if(startedFromSetupPosition || backwardMostMove != 0) {
5308           fen = PositionToFEN(backwardMostMove, NULL, 1);
5309           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5310             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5311             SendToICS(buf);
5312           } else { // FICS: everything has to set by separate bsetup commands
5313             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5314             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5315             SendToICS(buf);
5316             if(!WhiteOnMove(backwardMostMove)) {
5317                 SendToICS("bsetup tomove black\n");
5318             }
5319             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5320             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5321             SendToICS(buf);
5322             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5323             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5324             SendToICS(buf);
5325             i = boards[backwardMostMove][EP_STATUS];
5326             if(i >= 0) { // set e.p.
5327               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5328                 SendToICS(buf);
5329             }
5330             bsetup++;
5331           }
5332         }
5333       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5334             SendToICS("bsetup done\n"); // switch to normal examining.
5335     }
5336     for(i = backwardMostMove; i<last; i++) {
5337         char buf[20];
5338         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5339         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5340             int len = strlen(moveList[i]);
5341             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5342             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5343         }
5344         SendToICS(buf);
5345     }
5346     SendToICS(ics_prefix);
5347     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5348 }
5349
5350 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5351 int legNr = 1;
5352
5353 void
5354 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5355 {
5356     if (rf == DROP_RANK) {
5357       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5358       sprintf(move, "%c@%c%c\n",
5359                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5360     } else {
5361         if (promoChar == 'x' || promoChar == NULLCHAR) {
5362           sprintf(move, "%c%c%c%c\n",
5363                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5364           if(killX >= 0 && killY >= 0) {
5365             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5366             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5367           }
5368         } else {
5369             sprintf(move, "%c%c%c%c%c\n",
5370                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5371           if(killX >= 0 && killY >= 0) {
5372             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5373             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5374           }
5375         }
5376     }
5377 }
5378
5379 void
5380 ProcessICSInitScript (FILE *f)
5381 {
5382     char buf[MSG_SIZ];
5383
5384     while (fgets(buf, MSG_SIZ, f)) {
5385         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5386     }
5387
5388     fclose(f);
5389 }
5390
5391
5392 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5393 int dragging;
5394 static ClickType lastClickType;
5395
5396 int
5397 PieceInString (char *s, ChessSquare piece)
5398 {
5399   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5400   while((p = strchr(s, ID))) {
5401     if(!suffix || p[1] == suffix) return TRUE;
5402     s = p;
5403   }
5404   return FALSE;
5405 }
5406
5407 int
5408 Partner (ChessSquare *p)
5409 { // change piece into promotion partner if one shogi-promotes to the other
5410   ChessSquare partner = promoPartner[*p];
5411   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5412   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5413   *p = partner;
5414   return 1;
5415 }
5416
5417 void
5418 Sweep (int step)
5419 {
5420     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5421     static int toggleFlag;
5422     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5423     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5424     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5425     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5426     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5427     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5428     do {
5429         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5430         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5431         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5432         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5433         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5434         if(!step) step = -1;
5435     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5436             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5437             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5438             promoSweep == pawn ||
5439             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5440             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5441     if(toX >= 0) {
5442         int victim = boards[currentMove][toY][toX];
5443         boards[currentMove][toY][toX] = promoSweep;
5444         DrawPosition(FALSE, boards[currentMove]);
5445         boards[currentMove][toY][toX] = victim;
5446     } else
5447     ChangeDragPiece(promoSweep);
5448 }
5449
5450 int
5451 PromoScroll (int x, int y)
5452 {
5453   int step = 0;
5454
5455   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5456   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5457   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5458   if(!step) return FALSE;
5459   lastX = x; lastY = y;
5460   if((promoSweep < BlackPawn) == flipView) step = -step;
5461   if(step > 0) selectFlag = 1;
5462   if(!selectFlag) Sweep(step);
5463   return FALSE;
5464 }
5465
5466 void
5467 NextPiece (int step)
5468 {
5469     ChessSquare piece = boards[currentMove][toY][toX];
5470     do {
5471         pieceSweep -= step;
5472         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5473         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5474         if(!step) step = -1;
5475     } while(PieceToChar(pieceSweep) == '.');
5476     boards[currentMove][toY][toX] = pieceSweep;
5477     DrawPosition(FALSE, boards[currentMove]);
5478     boards[currentMove][toY][toX] = piece;
5479 }
5480 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5481 void
5482 AlphaRank (char *move, int n)
5483 {
5484 //    char *p = move, c; int x, y;
5485
5486     if (appData.debugMode) {
5487         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5488     }
5489
5490     if(move[1]=='*' &&
5491        move[2]>='0' && move[2]<='9' &&
5492        move[3]>='a' && move[3]<='x'    ) {
5493         move[1] = '@';
5494         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5495         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5496     } else
5497     if(move[0]>='0' && move[0]<='9' &&
5498        move[1]>='a' && move[1]<='x' &&
5499        move[2]>='0' && move[2]<='9' &&
5500        move[3]>='a' && move[3]<='x'    ) {
5501         /* input move, Shogi -> normal */
5502         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5503         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5504         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5505         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5506     } else
5507     if(move[1]=='@' &&
5508        move[3]>='0' && move[3]<='9' &&
5509        move[2]>='a' && move[2]<='x'    ) {
5510         move[1] = '*';
5511         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5512         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5513     } else
5514     if(
5515        move[0]>='a' && move[0]<='x' &&
5516        move[3]>='0' && move[3]<='9' &&
5517        move[2]>='a' && move[2]<='x'    ) {
5518          /* output move, normal -> Shogi */
5519         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5520         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5521         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5522         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5523         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5524     }
5525     if (appData.debugMode) {
5526         fprintf(debugFP, "   out = '%s'\n", move);
5527     }
5528 }
5529
5530 char yy_textstr[8000];
5531
5532 /* Parser for moves from gnuchess, ICS, or user typein box */
5533 Boolean
5534 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5535 {
5536     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5537
5538     switch (*moveType) {
5539       case WhitePromotion:
5540       case BlackPromotion:
5541       case WhiteNonPromotion:
5542       case BlackNonPromotion:
5543       case NormalMove:
5544       case FirstLeg:
5545       case WhiteCapturesEnPassant:
5546       case BlackCapturesEnPassant:
5547       case WhiteKingSideCastle:
5548       case WhiteQueenSideCastle:
5549       case BlackKingSideCastle:
5550       case BlackQueenSideCastle:
5551       case WhiteKingSideCastleWild:
5552       case WhiteQueenSideCastleWild:
5553       case BlackKingSideCastleWild:
5554       case BlackQueenSideCastleWild:
5555       /* Code added by Tord: */
5556       case WhiteHSideCastleFR:
5557       case WhiteASideCastleFR:
5558       case BlackHSideCastleFR:
5559       case BlackASideCastleFR:
5560       /* End of code added by Tord */
5561       case IllegalMove:         /* bug or odd chess variant */
5562         if(currentMoveString[1] == '@') { // illegal drop
5563           *fromX = WhiteOnMove(moveNum) ?
5564             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5565             (int) CharToPiece(ToLower(currentMoveString[0]));
5566           goto drop;
5567         }
5568         *fromX = currentMoveString[0] - AAA;
5569         *fromY = currentMoveString[1] - ONE;
5570         *toX = currentMoveString[2] - AAA;
5571         *toY = currentMoveString[3] - ONE;
5572         *promoChar = currentMoveString[4];
5573         if(*promoChar == ';') *promoChar = currentMoveString[7];
5574         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5575             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5576     if (appData.debugMode) {
5577         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5578     }
5579             *fromX = *fromY = *toX = *toY = 0;
5580             return FALSE;
5581         }
5582         if (appData.testLegality) {
5583           return (*moveType != IllegalMove);
5584         } else {
5585           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5586                          // [HGM] lion: if this is a double move we are less critical
5587                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5588         }
5589
5590       case WhiteDrop:
5591       case BlackDrop:
5592         *fromX = *moveType == WhiteDrop ?
5593           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5594           (int) CharToPiece(ToLower(currentMoveString[0]));
5595       drop:
5596         *fromY = DROP_RANK;
5597         *toX = currentMoveString[2] - AAA;
5598         *toY = currentMoveString[3] - ONE;
5599         *promoChar = NULLCHAR;
5600         return TRUE;
5601
5602       case AmbiguousMove:
5603       case ImpossibleMove:
5604       case EndOfFile:
5605       case ElapsedTime:
5606       case Comment:
5607       case PGNTag:
5608       case NAG:
5609       case WhiteWins:
5610       case BlackWins:
5611       case GameIsDrawn:
5612       default:
5613     if (appData.debugMode) {
5614         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5615     }
5616         /* bug? */
5617         *fromX = *fromY = *toX = *toY = 0;
5618         *promoChar = NULLCHAR;
5619         return FALSE;
5620     }
5621 }
5622
5623 Boolean pushed = FALSE;
5624 char *lastParseAttempt;
5625
5626 void
5627 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5628 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5629   int fromX, fromY, toX, toY; char promoChar;
5630   ChessMove moveType;
5631   Boolean valid;
5632   int nr = 0;
5633
5634   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5635   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5636     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5637     pushed = TRUE;
5638   }
5639   endPV = forwardMostMove;
5640   do {
5641     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5642     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5643     lastParseAttempt = pv;
5644     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5645     if(!valid && nr == 0 &&
5646        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5647         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5648         // Hande case where played move is different from leading PV move
5649         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5650         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5651         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5652         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5653           endPV += 2; // if position different, keep this
5654           moveList[endPV-1][0] = fromX + AAA;
5655           moveList[endPV-1][1] = fromY + ONE;
5656           moveList[endPV-1][2] = toX + AAA;
5657           moveList[endPV-1][3] = toY + ONE;
5658           parseList[endPV-1][0] = NULLCHAR;
5659           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5660         }
5661       }
5662     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5663     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5664     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5665     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5666         valid++; // allow comments in PV
5667         continue;
5668     }
5669     nr++;
5670     if(endPV+1 > framePtr) break; // no space, truncate
5671     if(!valid) break;
5672     endPV++;
5673     CopyBoard(boards[endPV], boards[endPV-1]);
5674     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5675     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5676     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5677     CoordsToAlgebraic(boards[endPV - 1],
5678                              PosFlags(endPV - 1),
5679                              fromY, fromX, toY, toX, promoChar,
5680                              parseList[endPV - 1]);
5681   } while(valid);
5682   if(atEnd == 2) return; // used hidden, for PV conversion
5683   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5684   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5685   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5686                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5687   DrawPosition(TRUE, boards[currentMove]);
5688 }
5689
5690 int
5691 MultiPV (ChessProgramState *cps, int kind)
5692 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5693         int i;
5694         for(i=0; i<cps->nrOptions; i++) {
5695             char *s = cps->option[i].name;
5696             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5697             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5698                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5699         }
5700         return -1;
5701 }
5702
5703 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5704 static int multi, pv_margin;
5705 static ChessProgramState *activeCps;
5706
5707 Boolean
5708 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5709 {
5710         int startPV, lineStart, origIndex = index;
5711         char *p, buf2[MSG_SIZ];
5712         ChessProgramState *cps = (pane ? &second : &first);
5713
5714         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5715         lastX = x; lastY = y;
5716         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5717         lineStart = startPV = index;
5718         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5719         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5720         index = startPV;
5721         do{ while(buf[index] && buf[index] != '\n') index++;
5722         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5723         buf[index] = 0;
5724         if(lineStart == 0 && gameMode == AnalyzeMode) {
5725             int n = 0;
5726             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5727             if(n == 0) { // click not on "fewer" or "more"
5728                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5729                     pv_margin = cps->option[multi].value;
5730                     activeCps = cps; // non-null signals margin adjustment
5731                 }
5732             } else if((multi = MultiPV(cps, 1)) >= 0) {
5733                 n += cps->option[multi].value; if(n < 1) n = 1;
5734                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5735                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5736                 cps->option[multi].value = n;
5737                 *start = *end = 0;
5738                 return FALSE;
5739             }
5740         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5741                 ExcludeClick(origIndex - lineStart);
5742                 return FALSE;
5743         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5744                 Collapse(origIndex - lineStart);
5745                 return FALSE;
5746         }
5747         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5748         *start = startPV; *end = index-1;
5749         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5750         return TRUE;
5751 }
5752
5753 char *
5754 PvToSAN (char *pv)
5755 {
5756         static char buf[10*MSG_SIZ];
5757         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5758         *buf = NULLCHAR;
5759         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5760         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5761         for(i = forwardMostMove; i<endPV; i++){
5762             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5763             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5764             k += strlen(buf+k);
5765         }
5766         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5767         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5768         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5769         endPV = savedEnd;
5770         return buf;
5771 }
5772
5773 Boolean
5774 LoadPV (int x, int y)
5775 { // called on right mouse click to load PV
5776   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5777   lastX = x; lastY = y;
5778   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5779   extendGame = FALSE;
5780   return TRUE;
5781 }
5782
5783 void
5784 UnLoadPV ()
5785 {
5786   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5787   if(activeCps) {
5788     if(pv_margin != activeCps->option[multi].value) {
5789       char buf[MSG_SIZ];
5790       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5791       SendToProgram(buf, activeCps);
5792       activeCps->option[multi].value = pv_margin;
5793     }
5794     activeCps = NULL;
5795     return;
5796   }
5797   if(endPV < 0) return;
5798   if(appData.autoCopyPV) CopyFENToClipboard();
5799   endPV = -1;
5800   if(extendGame && currentMove > forwardMostMove) {
5801         Boolean saveAnimate = appData.animate;
5802         if(pushed) {
5803             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5804                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5805             } else storedGames--; // abandon shelved tail of original game
5806         }
5807         pushed = FALSE;
5808         forwardMostMove = currentMove;
5809         currentMove = oldFMM;
5810         appData.animate = FALSE;
5811         ToNrEvent(forwardMostMove);
5812         appData.animate = saveAnimate;
5813   }
5814   currentMove = forwardMostMove;
5815   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5816   ClearPremoveHighlights();
5817   DrawPosition(TRUE, boards[currentMove]);
5818 }
5819
5820 void
5821 MovePV (int x, int y, int h)
5822 { // step through PV based on mouse coordinates (called on mouse move)
5823   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5824
5825   if(activeCps) { // adjusting engine's multi-pv margin
5826     if(x > lastX) pv_margin++; else
5827     if(x < lastX) pv_margin -= (pv_margin > 0);
5828     if(x != lastX) {
5829       char buf[MSG_SIZ];
5830       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5831       DisplayMessage(buf, "");
5832     }
5833     lastX = x;
5834     return;
5835   }
5836   // we must somehow check if right button is still down (might be released off board!)
5837   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5838   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5839   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5840   if(!step) return;
5841   lastX = x; lastY = y;
5842
5843   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5844   if(endPV < 0) return;
5845   if(y < margin) step = 1; else
5846   if(y > h - margin) step = -1;
5847   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5848   currentMove += step;
5849   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5850   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5851                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5852   DrawPosition(FALSE, boards[currentMove]);
5853 }
5854
5855
5856 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5857 // All positions will have equal probability, but the current method will not provide a unique
5858 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5859 #define DARK 1
5860 #define LITE 2
5861 #define ANY 3
5862
5863 int squaresLeft[4];
5864 int piecesLeft[(int)BlackPawn];
5865 int seed, nrOfShuffles;
5866
5867 void
5868 GetPositionNumber ()
5869 {       // sets global variable seed
5870         int i;
5871
5872         seed = appData.defaultFrcPosition;
5873         if(seed < 0) { // randomize based on time for negative FRC position numbers
5874                 for(i=0; i<50; i++) seed += random();
5875                 seed = random() ^ random() >> 8 ^ random() << 8;
5876                 if(seed<0) seed = -seed;
5877         }
5878 }
5879
5880 int
5881 put (Board board, int pieceType, int rank, int n, int shade)
5882 // put the piece on the (n-1)-th empty squares of the given shade
5883 {
5884         int i;
5885
5886         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5887                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5888                         board[rank][i] = (ChessSquare) pieceType;
5889                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5890                         squaresLeft[ANY]--;
5891                         piecesLeft[pieceType]--;
5892                         return i;
5893                 }
5894         }
5895         return -1;
5896 }
5897
5898
5899 void
5900 AddOnePiece (Board board, int pieceType, int rank, int shade)
5901 // calculate where the next piece goes, (any empty square), and put it there
5902 {
5903         int i;
5904
5905         i = seed % squaresLeft[shade];
5906         nrOfShuffles *= squaresLeft[shade];
5907         seed /= squaresLeft[shade];
5908         put(board, pieceType, rank, i, shade);
5909 }
5910
5911 void
5912 AddTwoPieces (Board board, int pieceType, int rank)
5913 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5914 {
5915         int i, n=squaresLeft[ANY], j=n-1, k;
5916
5917         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5918         i = seed % k;  // pick one
5919         nrOfShuffles *= k;
5920         seed /= k;
5921         while(i >= j) i -= j--;
5922         j = n - 1 - j; i += j;
5923         put(board, pieceType, rank, j, ANY);
5924         put(board, pieceType, rank, i, ANY);
5925 }
5926
5927 void
5928 SetUpShuffle (Board board, int number)
5929 {
5930         int i, p, first=1;
5931
5932         GetPositionNumber(); nrOfShuffles = 1;
5933
5934         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5935         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5936         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5937
5938         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5939
5940         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5941             p = (int) board[0][i];
5942             if(p < (int) BlackPawn) piecesLeft[p] ++;
5943             board[0][i] = EmptySquare;
5944         }
5945
5946         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5947             // shuffles restricted to allow normal castling put KRR first
5948             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5949                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5950             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5951                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5952             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5953                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5954             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5955                 put(board, WhiteRook, 0, 0, ANY);
5956             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5957         }
5958
5959         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5960             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5961             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5962                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5963                 while(piecesLeft[p] >= 2) {
5964                     AddOnePiece(board, p, 0, LITE);
5965                     AddOnePiece(board, p, 0, DARK);
5966                 }
5967                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5968             }
5969
5970         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5971             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5972             // but we leave King and Rooks for last, to possibly obey FRC restriction
5973             if(p == (int)WhiteRook) continue;
5974             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5975             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5976         }
5977
5978         // now everything is placed, except perhaps King (Unicorn) and Rooks
5979
5980         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5981             // Last King gets castling rights
5982             while(piecesLeft[(int)WhiteUnicorn]) {
5983                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5984                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5985             }
5986
5987             while(piecesLeft[(int)WhiteKing]) {
5988                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5989                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5990             }
5991
5992
5993         } else {
5994             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5995             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5996         }
5997
5998         // Only Rooks can be left; simply place them all
5999         while(piecesLeft[(int)WhiteRook]) {
6000                 i = put(board, WhiteRook, 0, 0, ANY);
6001                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6002                         if(first) {
6003                                 first=0;
6004                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6005                         }
6006                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6007                 }
6008         }
6009         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6010             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6011         }
6012
6013         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6014 }
6015
6016 int
6017 ptclen (const char *s, char *escapes)
6018 {
6019     int n = 0;
6020     if(!*escapes) return strlen(s);
6021     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6022     return n;
6023 }
6024
6025 int
6026 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6027 /* [HGM] moved here from winboard.c because of its general usefulness */
6028 /*       Basically a safe strcpy that uses the last character as King */
6029 {
6030     int result = FALSE; int NrPieces;
6031     unsigned char partner[EmptySquare];
6032
6033     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6034                     && NrPieces >= 12 && !(NrPieces&1)) {
6035         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6036
6037         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6038         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6039             char *p, c=0;
6040             if(map[j] == '/') offs = WhitePBishop - i, j++;
6041             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6042             table[i+offs] = map[j++];
6043             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6044             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6045             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6046         }
6047         table[(int) WhiteKing]  = map[j++];
6048         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6049             char *p, c=0;
6050             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6051             i = WHITE_TO_BLACK ii;
6052             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6053             table[i+offs] = map[j++];
6054             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6055             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6056             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6057         }
6058         table[(int) BlackKing]  = map[j++];
6059
6060
6061         if(*escapes) { // set up promotion pairing
6062             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6063             // pieceToChar entirely filled, so we can look up specified partners
6064             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6065                 int c = table[i];
6066                 if(c == '^' || c == '-') { // has specified partner
6067                     int p;
6068                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6069                     if(c == '^') table[i] = '+';
6070                     if(p < EmptySquare) {
6071                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6072                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6073                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6074                     }
6075                 } else if(c == '*') {
6076                     table[i] = partner[i];
6077                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6078                 }
6079             }
6080         }
6081
6082         result = TRUE;
6083     }
6084
6085     return result;
6086 }
6087
6088 int
6089 SetCharTable (unsigned char *table, const char * map)
6090 {
6091     return SetCharTableEsc(table, map, "");
6092 }
6093
6094 void
6095 Prelude (Board board)
6096 {       // [HGM] superchess: random selection of exo-pieces
6097         int i, j, k; ChessSquare p;
6098         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6099
6100         GetPositionNumber(); // use FRC position number
6101
6102         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6103             SetCharTable(pieceToChar, appData.pieceToCharTable);
6104             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6105                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6106         }
6107
6108         j = seed%4;                 seed /= 4;
6109         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6110         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6111         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6112         j = seed%3 + (seed%3 >= j); seed /= 3;
6113         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6114         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6115         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6116         j = seed%3;                 seed /= 3;
6117         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6118         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6119         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6120         j = seed%2 + (seed%2 >= j); seed /= 2;
6121         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6122         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6123         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6124         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6125         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6126         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6127         put(board, exoPieces[0],    0, 0, ANY);
6128         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6129 }
6130
6131 void
6132 InitPosition (int redraw)
6133 {
6134     ChessSquare (* pieces)[BOARD_FILES];
6135     int i, j, pawnRow=1, pieceRows=1, overrule,
6136     oldx = gameInfo.boardWidth,
6137     oldy = gameInfo.boardHeight,
6138     oldh = gameInfo.holdingsWidth;
6139     static int oldv;
6140
6141     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6142
6143     /* [AS] Initialize pv info list [HGM] and game status */
6144     {
6145         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6146             pvInfoList[i].depth = 0;
6147             boards[i][EP_STATUS] = EP_NONE;
6148             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6149         }
6150
6151         initialRulePlies = 0; /* 50-move counter start */
6152
6153         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6154         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6155     }
6156
6157
6158     /* [HGM] logic here is completely changed. In stead of full positions */
6159     /* the initialized data only consist of the two backranks. The switch */
6160     /* selects which one we will use, which is than copied to the Board   */
6161     /* initialPosition, which for the rest is initialized by Pawns and    */
6162     /* empty squares. This initial position is then copied to boards[0],  */
6163     /* possibly after shuffling, so that it remains available.            */
6164
6165     gameInfo.holdingsWidth = 0; /* default board sizes */
6166     gameInfo.boardWidth    = 8;
6167     gameInfo.boardHeight   = 8;
6168     gameInfo.holdingsSize  = 0;
6169     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6170     for(i=0; i<BOARD_FILES-6; i++)
6171       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6172     initialPosition[EP_STATUS] = EP_NONE;
6173     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6174     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6175     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6176          SetCharTable(pieceNickName, appData.pieceNickNames);
6177     else SetCharTable(pieceNickName, "............");
6178     pieces = FIDEArray;
6179
6180     switch (gameInfo.variant) {
6181     case VariantFischeRandom:
6182       shuffleOpenings = TRUE;
6183       appData.fischerCastling = TRUE;
6184     default:
6185       break;
6186     case VariantShatranj:
6187       pieces = ShatranjArray;
6188       nrCastlingRights = 0;
6189       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6190       break;
6191     case VariantMakruk:
6192       pieces = makrukArray;
6193       nrCastlingRights = 0;
6194       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6195       break;
6196     case VariantASEAN:
6197       pieces = aseanArray;
6198       nrCastlingRights = 0;
6199       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6200       break;
6201     case VariantTwoKings:
6202       pieces = twoKingsArray;
6203       break;
6204     case VariantGrand:
6205       pieces = GrandArray;
6206       nrCastlingRights = 0;
6207       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6208       gameInfo.boardWidth = 10;
6209       gameInfo.boardHeight = 10;
6210       gameInfo.holdingsSize = 7;
6211       break;
6212     case VariantCapaRandom:
6213       shuffleOpenings = TRUE;
6214       appData.fischerCastling = TRUE;
6215     case VariantCapablanca:
6216       pieces = CapablancaArray;
6217       gameInfo.boardWidth = 10;
6218       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6219       break;
6220     case VariantGothic:
6221       pieces = GothicArray;
6222       gameInfo.boardWidth = 10;
6223       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6224       break;
6225     case VariantSChess:
6226       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6227       gameInfo.holdingsSize = 7;
6228       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6229       break;
6230     case VariantJanus:
6231       pieces = JanusArray;
6232       gameInfo.boardWidth = 10;
6233       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6234       nrCastlingRights = 6;
6235         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6236         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6237         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6238         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6239         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6240         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6241       break;
6242     case VariantFalcon:
6243       pieces = FalconArray;
6244       gameInfo.boardWidth = 10;
6245       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6246       break;
6247     case VariantXiangqi:
6248       pieces = XiangqiArray;
6249       gameInfo.boardWidth  = 9;
6250       gameInfo.boardHeight = 10;
6251       nrCastlingRights = 0;
6252       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6253       break;
6254     case VariantShogi:
6255       pieces = ShogiArray;
6256       gameInfo.boardWidth  = 9;
6257       gameInfo.boardHeight = 9;
6258       gameInfo.holdingsSize = 7;
6259       nrCastlingRights = 0;
6260       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6261       break;
6262     case VariantChu:
6263       pieces = ChuArray; pieceRows = 3;
6264       gameInfo.boardWidth  = 12;
6265       gameInfo.boardHeight = 12;
6266       nrCastlingRights = 0;
6267       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6268                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6269       break;
6270     case VariantCourier:
6271       pieces = CourierArray;
6272       gameInfo.boardWidth  = 12;
6273       nrCastlingRights = 0;
6274       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6275       break;
6276     case VariantKnightmate:
6277       pieces = KnightmateArray;
6278       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6279       break;
6280     case VariantSpartan:
6281       pieces = SpartanArray;
6282       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6283       break;
6284     case VariantLion:
6285       pieces = lionArray;
6286       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6287       break;
6288     case VariantChuChess:
6289       pieces = ChuChessArray;
6290       gameInfo.boardWidth = 10;
6291       gameInfo.boardHeight = 10;
6292       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6293       break;
6294     case VariantFairy:
6295       pieces = fairyArray;
6296       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6297       break;
6298     case VariantGreat:
6299       pieces = GreatArray;
6300       gameInfo.boardWidth = 10;
6301       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6302       gameInfo.holdingsSize = 8;
6303       break;
6304     case VariantSuper:
6305       pieces = FIDEArray;
6306       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6307       gameInfo.holdingsSize = 8;
6308       startedFromSetupPosition = TRUE;
6309       break;
6310     case VariantCrazyhouse:
6311     case VariantBughouse:
6312       pieces = FIDEArray;
6313       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6314       gameInfo.holdingsSize = 5;
6315       break;
6316     case VariantWildCastle:
6317       pieces = FIDEArray;
6318       /* !!?shuffle with kings guaranteed to be on d or e file */
6319       shuffleOpenings = 1;
6320       break;
6321     case VariantNoCastle:
6322       pieces = FIDEArray;
6323       nrCastlingRights = 0;
6324       /* !!?unconstrained back-rank shuffle */
6325       shuffleOpenings = 1;
6326       break;
6327     }
6328
6329     overrule = 0;
6330     if(appData.NrFiles >= 0) {
6331         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6332         gameInfo.boardWidth = appData.NrFiles;
6333     }
6334     if(appData.NrRanks >= 0) {
6335         gameInfo.boardHeight = appData.NrRanks;
6336     }
6337     if(appData.holdingsSize >= 0) {
6338         i = appData.holdingsSize;
6339         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6340         gameInfo.holdingsSize = i;
6341     }
6342     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6343     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6344         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6345
6346     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6347     if(pawnRow < 1) pawnRow = 1;
6348     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6349        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6350     if(gameInfo.variant == VariantChu) pawnRow = 3;
6351
6352     /* User pieceToChar list overrules defaults */
6353     if(appData.pieceToCharTable != NULL)
6354         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6355
6356     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6357
6358         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6359             s = (ChessSquare) 0; /* account holding counts in guard band */
6360         for( i=0; i<BOARD_HEIGHT; i++ )
6361             initialPosition[i][j] = s;
6362
6363         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6364         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6365         initialPosition[pawnRow][j] = WhitePawn;
6366         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6367         if(gameInfo.variant == VariantXiangqi) {
6368             if(j&1) {
6369                 initialPosition[pawnRow][j] =
6370                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6371                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6372                    initialPosition[2][j] = WhiteCannon;
6373                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6374                 }
6375             }
6376         }
6377         if(gameInfo.variant == VariantChu) {
6378              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6379                initialPosition[pawnRow+1][j] = WhiteCobra,
6380                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6381              for(i=1; i<pieceRows; i++) {
6382                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6383                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6384              }
6385         }
6386         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6387             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6388                initialPosition[0][j] = WhiteRook;
6389                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6390             }
6391         }
6392         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6393     }
6394     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6395     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6396
6397             j=BOARD_LEFT+1;
6398             initialPosition[1][j] = WhiteBishop;
6399             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6400             j=BOARD_RGHT-2;
6401             initialPosition[1][j] = WhiteRook;
6402             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6403     }
6404
6405     if( nrCastlingRights == -1) {
6406         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6407         /*       This sets default castling rights from none to normal corners   */
6408         /* Variants with other castling rights must set them themselves above    */
6409         nrCastlingRights = 6;
6410
6411         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6412         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6413         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6414         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6415         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6416         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6417      }
6418
6419      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6420      if(gameInfo.variant == VariantGreat) { // promotion commoners
6421         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6422         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6423         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6424         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6425      }
6426      if( gameInfo.variant == VariantSChess ) {
6427       initialPosition[1][0] = BlackMarshall;
6428       initialPosition[2][0] = BlackAngel;
6429       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6430       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6431       initialPosition[1][1] = initialPosition[2][1] =
6432       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6433      }
6434   if (appData.debugMode) {
6435     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6436   }
6437     if(shuffleOpenings) {
6438         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6439         startedFromSetupPosition = TRUE;
6440     }
6441     if(startedFromPositionFile) {
6442       /* [HGM] loadPos: use PositionFile for every new game */
6443       CopyBoard(initialPosition, filePosition);
6444       for(i=0; i<nrCastlingRights; i++)
6445           initialRights[i] = filePosition[CASTLING][i];
6446       startedFromSetupPosition = TRUE;
6447     }
6448
6449     CopyBoard(boards[0], initialPosition);
6450
6451     if(oldx != gameInfo.boardWidth ||
6452        oldy != gameInfo.boardHeight ||
6453        oldv != gameInfo.variant ||
6454        oldh != gameInfo.holdingsWidth
6455                                          )
6456             InitDrawingSizes(-2 ,0);
6457
6458     oldv = gameInfo.variant;
6459     if (redraw)
6460       DrawPosition(TRUE, boards[currentMove]);
6461 }
6462
6463 void
6464 SendBoard (ChessProgramState *cps, int moveNum)
6465 {
6466     char message[MSG_SIZ];
6467
6468     if (cps->useSetboard) {
6469       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6470       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6471       SendToProgram(message, cps);
6472       free(fen);
6473
6474     } else {
6475       ChessSquare *bp;
6476       int i, j, left=0, right=BOARD_WIDTH;
6477       /* Kludge to set black to move, avoiding the troublesome and now
6478        * deprecated "black" command.
6479        */
6480       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6481         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6482
6483       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6484
6485       SendToProgram("edit\n", cps);
6486       SendToProgram("#\n", cps);
6487       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6488         bp = &boards[moveNum][i][left];
6489         for (j = left; j < right; j++, bp++) {
6490           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6491           if ((int) *bp < (int) BlackPawn) {
6492             if(j == BOARD_RGHT+1)
6493                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6494             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6495             if(message[0] == '+' || message[0] == '~') {
6496               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6497                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6498                         AAA + j, ONE + i - '0');
6499             }
6500             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6501                 message[1] = BOARD_RGHT   - 1 - j + '1';
6502                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6503             }
6504             SendToProgram(message, cps);
6505           }
6506         }
6507       }
6508
6509       SendToProgram("c\n", cps);
6510       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6511         bp = &boards[moveNum][i][left];
6512         for (j = left; j < right; j++, bp++) {
6513           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6514           if (((int) *bp != (int) EmptySquare)
6515               && ((int) *bp >= (int) BlackPawn)) {
6516             if(j == BOARD_LEFT-2)
6517                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6518             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6519                     AAA + j, ONE + i - '0');
6520             if(message[0] == '+' || message[0] == '~') {
6521               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6522                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6523                         AAA + j, ONE + i - '0');
6524             }
6525             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6526                 message[1] = BOARD_RGHT   - 1 - j + '1';
6527                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6528             }
6529             SendToProgram(message, cps);
6530           }
6531         }
6532       }
6533
6534       SendToProgram(".\n", cps);
6535     }
6536     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6537 }
6538
6539 char exclusionHeader[MSG_SIZ];
6540 int exCnt, excludePtr;
6541 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6542 static Exclusion excluTab[200];
6543 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6544
6545 static void
6546 WriteMap (int s)
6547 {
6548     int j;
6549     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6550     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6551 }
6552
6553 static void
6554 ClearMap ()
6555 {
6556     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6557     excludePtr = 24; exCnt = 0;
6558     WriteMap(0);
6559 }
6560
6561 static void
6562 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6563 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6564     char buf[2*MOVE_LEN], *p;
6565     Exclusion *e = excluTab;
6566     int i;
6567     for(i=0; i<exCnt; i++)
6568         if(e[i].ff == fromX && e[i].fr == fromY &&
6569            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6570     if(i == exCnt) { // was not in exclude list; add it
6571         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6572         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6573             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6574             return; // abort
6575         }
6576         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6577         excludePtr++; e[i].mark = excludePtr++;
6578         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6579         exCnt++;
6580     }
6581     exclusionHeader[e[i].mark] = state;
6582 }
6583
6584 static int
6585 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6586 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6587     char buf[MSG_SIZ];
6588     int j, k;
6589     ChessMove moveType;
6590     if((signed char)promoChar == -1) { // kludge to indicate best move
6591         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6592             return 1; // if unparsable, abort
6593     }
6594     // update exclusion map (resolving toggle by consulting existing state)
6595     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6596     j = k%8; k >>= 3;
6597     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6598     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6599          excludeMap[k] |=   1<<j;
6600     else excludeMap[k] &= ~(1<<j);
6601     // update header
6602     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6603     // inform engine
6604     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6605     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6606     SendToBoth(buf);
6607     return (state == '+');
6608 }
6609
6610 static void
6611 ExcludeClick (int index)
6612 {
6613     int i, j;
6614     Exclusion *e = excluTab;
6615     if(index < 25) { // none, best or tail clicked
6616         if(index < 13) { // none: include all
6617             WriteMap(0); // clear map
6618             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6619             SendToBoth("include all\n"); // and inform engine
6620         } else if(index > 18) { // tail
6621             if(exclusionHeader[19] == '-') { // tail was excluded
6622                 SendToBoth("include all\n");
6623                 WriteMap(0); // clear map completely
6624                 // now re-exclude selected moves
6625                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6626                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6627             } else { // tail was included or in mixed state
6628                 SendToBoth("exclude all\n");
6629                 WriteMap(0xFF); // fill map completely
6630                 // now re-include selected moves
6631                 j = 0; // count them
6632                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6633                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6634                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6635             }
6636         } else { // best
6637             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6638         }
6639     } else {
6640         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6641             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6642             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6643             break;
6644         }
6645     }
6646 }
6647
6648 ChessSquare
6649 DefaultPromoChoice (int white)
6650 {
6651     ChessSquare result;
6652     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6653        gameInfo.variant == VariantMakruk)
6654         result = WhiteFerz; // no choice
6655     else if(gameInfo.variant == VariantASEAN)
6656         result = WhiteRook; // no choice
6657     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6658         result= WhiteKing; // in Suicide Q is the last thing we want
6659     else if(gameInfo.variant == VariantSpartan)
6660         result = white ? WhiteQueen : WhiteAngel;
6661     else result = WhiteQueen;
6662     if(!white) result = WHITE_TO_BLACK result;
6663     return result;
6664 }
6665
6666 static int autoQueen; // [HGM] oneclick
6667
6668 int
6669 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6670 {
6671     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6672     /* [HGM] add Shogi promotions */
6673     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6674     ChessSquare piece, partner;
6675     ChessMove moveType;
6676     Boolean premove;
6677
6678     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6679     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6680
6681     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6682       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6683         return FALSE;
6684
6685     piece = boards[currentMove][fromY][fromX];
6686     if(gameInfo.variant == VariantChu) {
6687         promotionZoneSize = BOARD_HEIGHT/3;
6688         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6689     } else if(gameInfo.variant == VariantShogi) {
6690         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6691         highestPromotingPiece = (int)WhiteAlfil;
6692     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6693         promotionZoneSize = 3;
6694     }
6695
6696     // Treat Lance as Pawn when it is not representing Amazon or Lance
6697     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6698         if(piece == WhiteLance) piece = WhitePawn; else
6699         if(piece == BlackLance) piece = BlackPawn;
6700     }
6701
6702     // next weed out all moves that do not touch the promotion zone at all
6703     if((int)piece >= BlackPawn) {
6704         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6705              return FALSE;
6706         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6707         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6708     } else {
6709         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6710            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6711         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6712              return FALSE;
6713     }
6714
6715     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6716
6717     // weed out mandatory Shogi promotions
6718     if(gameInfo.variant == VariantShogi) {
6719         if(piece >= BlackPawn) {
6720             if(toY == 0 && piece == BlackPawn ||
6721                toY == 0 && piece == BlackQueen ||
6722                toY <= 1 && piece == BlackKnight) {
6723                 *promoChoice = '+';
6724                 return FALSE;
6725             }
6726         } else {
6727             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6728                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6729                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6730                 *promoChoice = '+';
6731                 return FALSE;
6732             }
6733         }
6734     }
6735
6736     // weed out obviously illegal Pawn moves
6737     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6738         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6739         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6740         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6741         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6742         // note we are not allowed to test for valid (non-)capture, due to premove
6743     }
6744
6745     // we either have a choice what to promote to, or (in Shogi) whether to promote
6746     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6747        gameInfo.variant == VariantMakruk) {
6748         ChessSquare p=BlackFerz;  // no choice
6749         while(p < EmptySquare) {  //but make sure we use piece that exists
6750             *promoChoice = PieceToChar(p++);
6751             if(*promoChoice != '.') break;
6752         }
6753         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6754     }
6755     // no sense asking what we must promote to if it is going to explode...
6756     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6757         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6758         return FALSE;
6759     }
6760     // give caller the default choice even if we will not make it
6761     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6762     partner = piece; // pieces can promote if the pieceToCharTable says so
6763     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6764     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6765     if(        sweepSelect && gameInfo.variant != VariantGreat
6766                            && gameInfo.variant != VariantGrand
6767                            && gameInfo.variant != VariantSuper) return FALSE;
6768     if(autoQueen) return FALSE; // predetermined
6769
6770     // suppress promotion popup on illegal moves that are not premoves
6771     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6772               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6773     if(appData.testLegality && !premove) {
6774         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6775                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6776         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6777         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6778             return FALSE;
6779     }
6780
6781     return TRUE;
6782 }
6783
6784 int
6785 InPalace (int row, int column)
6786 {   /* [HGM] for Xiangqi */
6787     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6788          column < (BOARD_WIDTH + 4)/2 &&
6789          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6790     return FALSE;
6791 }
6792
6793 int
6794 PieceForSquare (int x, int y)
6795 {
6796   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6797      return -1;
6798   else
6799      return boards[currentMove][y][x];
6800 }
6801
6802 int
6803 OKToStartUserMove (int x, int y)
6804 {
6805     ChessSquare from_piece;
6806     int white_piece;
6807
6808     if (matchMode) return FALSE;
6809     if (gameMode == EditPosition) return TRUE;
6810
6811     if (x >= 0 && y >= 0)
6812       from_piece = boards[currentMove][y][x];
6813     else
6814       from_piece = EmptySquare;
6815
6816     if (from_piece == EmptySquare) return FALSE;
6817
6818     white_piece = (int)from_piece >= (int)WhitePawn &&
6819       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6820
6821     switch (gameMode) {
6822       case AnalyzeFile:
6823       case TwoMachinesPlay:
6824       case EndOfGame:
6825         return FALSE;
6826
6827       case IcsObserving:
6828       case IcsIdle:
6829         return FALSE;
6830
6831       case MachinePlaysWhite:
6832       case IcsPlayingBlack:
6833         if (appData.zippyPlay) return FALSE;
6834         if (white_piece) {
6835             DisplayMoveError(_("You are playing Black"));
6836             return FALSE;
6837         }
6838         break;
6839
6840       case MachinePlaysBlack:
6841       case IcsPlayingWhite:
6842         if (appData.zippyPlay) return FALSE;
6843         if (!white_piece) {
6844             DisplayMoveError(_("You are playing White"));
6845             return FALSE;
6846         }
6847         break;
6848
6849       case PlayFromGameFile:
6850             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6851       case EditGame:
6852         if (!white_piece && WhiteOnMove(currentMove)) {
6853             DisplayMoveError(_("It is White's turn"));
6854             return FALSE;
6855         }
6856         if (white_piece && !WhiteOnMove(currentMove)) {
6857             DisplayMoveError(_("It is Black's turn"));
6858             return FALSE;
6859         }
6860         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6861             /* Editing correspondence game history */
6862             /* Could disallow this or prompt for confirmation */
6863             cmailOldMove = -1;
6864         }
6865         break;
6866
6867       case BeginningOfGame:
6868         if (appData.icsActive) return FALSE;
6869         if (!appData.noChessProgram) {
6870             if (!white_piece) {
6871                 DisplayMoveError(_("You are playing White"));
6872                 return FALSE;
6873             }
6874         }
6875         break;
6876
6877       case Training:
6878         if (!white_piece && WhiteOnMove(currentMove)) {
6879             DisplayMoveError(_("It is White's turn"));
6880             return FALSE;
6881         }
6882         if (white_piece && !WhiteOnMove(currentMove)) {
6883             DisplayMoveError(_("It is Black's turn"));
6884             return FALSE;
6885         }
6886         break;
6887
6888       default:
6889       case IcsExamining:
6890         break;
6891     }
6892     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6893         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6894         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6895         && gameMode != AnalyzeFile && gameMode != Training) {
6896         DisplayMoveError(_("Displayed position is not current"));
6897         return FALSE;
6898     }
6899     return TRUE;
6900 }
6901
6902 Boolean
6903 OnlyMove (int *x, int *y, Boolean captures)
6904 {
6905     DisambiguateClosure cl;
6906     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6907     switch(gameMode) {
6908       case MachinePlaysBlack:
6909       case IcsPlayingWhite:
6910       case BeginningOfGame:
6911         if(!WhiteOnMove(currentMove)) return FALSE;
6912         break;
6913       case MachinePlaysWhite:
6914       case IcsPlayingBlack:
6915         if(WhiteOnMove(currentMove)) return FALSE;
6916         break;
6917       case EditGame:
6918         break;
6919       default:
6920         return FALSE;
6921     }
6922     cl.pieceIn = EmptySquare;
6923     cl.rfIn = *y;
6924     cl.ffIn = *x;
6925     cl.rtIn = -1;
6926     cl.ftIn = -1;
6927     cl.promoCharIn = NULLCHAR;
6928     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6929     if( cl.kind == NormalMove ||
6930         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6931         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6932         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6933       fromX = cl.ff;
6934       fromY = cl.rf;
6935       *x = cl.ft;
6936       *y = cl.rt;
6937       return TRUE;
6938     }
6939     if(cl.kind != ImpossibleMove) return FALSE;
6940     cl.pieceIn = EmptySquare;
6941     cl.rfIn = -1;
6942     cl.ffIn = -1;
6943     cl.rtIn = *y;
6944     cl.ftIn = *x;
6945     cl.promoCharIn = NULLCHAR;
6946     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6947     if( cl.kind == NormalMove ||
6948         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6949         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6950         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6951       fromX = cl.ff;
6952       fromY = cl.rf;
6953       *x = cl.ft;
6954       *y = cl.rt;
6955       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6956       return TRUE;
6957     }
6958     return FALSE;
6959 }
6960
6961 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6962 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6963 int lastLoadGameUseList = FALSE;
6964 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6965 ChessMove lastLoadGameStart = EndOfFile;
6966 int doubleClick;
6967 Boolean addToBookFlag;
6968
6969 void
6970 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6971 {
6972     ChessMove moveType;
6973     ChessSquare pup;
6974     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6975
6976     /* Check if the user is playing in turn.  This is complicated because we
6977        let the user "pick up" a piece before it is his turn.  So the piece he
6978        tried to pick up may have been captured by the time he puts it down!
6979        Therefore we use the color the user is supposed to be playing in this
6980        test, not the color of the piece that is currently on the starting
6981        square---except in EditGame mode, where the user is playing both
6982        sides; fortunately there the capture race can't happen.  (It can
6983        now happen in IcsExamining mode, but that's just too bad.  The user
6984        will get a somewhat confusing message in that case.)
6985        */
6986
6987     switch (gameMode) {
6988       case AnalyzeFile:
6989       case TwoMachinesPlay:
6990       case EndOfGame:
6991       case IcsObserving:
6992       case IcsIdle:
6993         /* We switched into a game mode where moves are not accepted,
6994            perhaps while the mouse button was down. */
6995         return;
6996
6997       case MachinePlaysWhite:
6998         /* User is moving for Black */
6999         if (WhiteOnMove(currentMove)) {
7000             DisplayMoveError(_("It is White's turn"));
7001             return;
7002         }
7003         break;
7004
7005       case MachinePlaysBlack:
7006         /* User is moving for White */
7007         if (!WhiteOnMove(currentMove)) {
7008             DisplayMoveError(_("It is Black's turn"));
7009             return;
7010         }
7011         break;
7012
7013       case PlayFromGameFile:
7014             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7015       case EditGame:
7016       case IcsExamining:
7017       case BeginningOfGame:
7018       case AnalyzeMode:
7019       case Training:
7020         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7021         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7022             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7023             /* User is moving for Black */
7024             if (WhiteOnMove(currentMove)) {
7025                 DisplayMoveError(_("It is White's turn"));
7026                 return;
7027             }
7028         } else {
7029             /* User is moving for White */
7030             if (!WhiteOnMove(currentMove)) {
7031                 DisplayMoveError(_("It is Black's turn"));
7032                 return;
7033             }
7034         }
7035         break;
7036
7037       case IcsPlayingBlack:
7038         /* User is moving for Black */
7039         if (WhiteOnMove(currentMove)) {
7040             if (!appData.premove) {
7041                 DisplayMoveError(_("It is White's turn"));
7042             } else if (toX >= 0 && toY >= 0) {
7043                 premoveToX = toX;
7044                 premoveToY = toY;
7045                 premoveFromX = fromX;
7046                 premoveFromY = fromY;
7047                 premovePromoChar = promoChar;
7048                 gotPremove = 1;
7049                 if (appData.debugMode)
7050                     fprintf(debugFP, "Got premove: fromX %d,"
7051                             "fromY %d, toX %d, toY %d\n",
7052                             fromX, fromY, toX, toY);
7053             }
7054             return;
7055         }
7056         break;
7057
7058       case IcsPlayingWhite:
7059         /* User is moving for White */
7060         if (!WhiteOnMove(currentMove)) {
7061             if (!appData.premove) {
7062                 DisplayMoveError(_("It is Black's turn"));
7063             } else if (toX >= 0 && toY >= 0) {
7064                 premoveToX = toX;
7065                 premoveToY = toY;
7066                 premoveFromX = fromX;
7067                 premoveFromY = fromY;
7068                 premovePromoChar = promoChar;
7069                 gotPremove = 1;
7070                 if (appData.debugMode)
7071                     fprintf(debugFP, "Got premove: fromX %d,"
7072                             "fromY %d, toX %d, toY %d\n",
7073                             fromX, fromY, toX, toY);
7074             }
7075             return;
7076         }
7077         break;
7078
7079       default:
7080         break;
7081
7082       case EditPosition:
7083         /* EditPosition, empty square, or different color piece;
7084            click-click move is possible */
7085         if (toX == -2 || toY == -2) {
7086             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7087             DrawPosition(FALSE, boards[currentMove]);
7088             return;
7089         } else if (toX >= 0 && toY >= 0) {
7090             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7091                 ChessSquare p = boards[0][rf][ff];
7092                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7093                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7094             }
7095             boards[0][toY][toX] = boards[0][fromY][fromX];
7096             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7097                 if(boards[0][fromY][0] != EmptySquare) {
7098                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7099                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7100                 }
7101             } else
7102             if(fromX == BOARD_RGHT+1) {
7103                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7104                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7105                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7106                 }
7107             } else
7108             boards[0][fromY][fromX] = gatingPiece;
7109             ClearHighlights();
7110             DrawPosition(FALSE, boards[currentMove]);
7111             return;
7112         }
7113         return;
7114     }
7115
7116     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7117     pup = boards[currentMove][toY][toX];
7118
7119     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7120     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7121          if( pup != EmptySquare ) return;
7122          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7123            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7124                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7125            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7126            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7127            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7128            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7129          fromY = DROP_RANK;
7130     }
7131
7132     /* [HGM] always test for legality, to get promotion info */
7133     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7134                                          fromY, fromX, toY, toX, promoChar);
7135
7136     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7137
7138     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7139
7140     /* [HGM] but possibly ignore an IllegalMove result */
7141     if (appData.testLegality) {
7142         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7143             DisplayMoveError(_("Illegal move"));
7144             return;
7145         }
7146     }
7147
7148     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7149         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7150              ClearPremoveHighlights(); // was included
7151         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7152         return;
7153     }
7154
7155     if(addToBookFlag) { // adding moves to book
7156         char buf[MSG_SIZ], move[MSG_SIZ];
7157         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7158         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7159                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7160         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7161         AddBookMove(buf);
7162         addToBookFlag = FALSE;
7163         ClearHighlights();
7164         return;
7165     }
7166
7167     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7168 }
7169
7170 /* Common tail of UserMoveEvent and DropMenuEvent */
7171 int
7172 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7173 {
7174     char *bookHit = 0;
7175
7176     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7177         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7178         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7179         if(WhiteOnMove(currentMove)) {
7180             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7181         } else {
7182             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7183         }
7184     }
7185
7186     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7187        move type in caller when we know the move is a legal promotion */
7188     if(moveType == NormalMove && promoChar)
7189         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7190
7191     /* [HGM] <popupFix> The following if has been moved here from
7192        UserMoveEvent(). Because it seemed to belong here (why not allow
7193        piece drops in training games?), and because it can only be
7194        performed after it is known to what we promote. */
7195     if (gameMode == Training) {
7196       /* compare the move played on the board to the next move in the
7197        * game. If they match, display the move and the opponent's response.
7198        * If they don't match, display an error message.
7199        */
7200       int saveAnimate;
7201       Board testBoard;
7202       CopyBoard(testBoard, boards[currentMove]);
7203       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7204
7205       if (CompareBoards(testBoard, boards[currentMove+1])) {
7206         ForwardInner(currentMove+1);
7207
7208         /* Autoplay the opponent's response.
7209          * if appData.animate was TRUE when Training mode was entered,
7210          * the response will be animated.
7211          */
7212         saveAnimate = appData.animate;
7213         appData.animate = animateTraining;
7214         ForwardInner(currentMove+1);
7215         appData.animate = saveAnimate;
7216
7217         /* check for the end of the game */
7218         if (currentMove >= forwardMostMove) {
7219           gameMode = PlayFromGameFile;
7220           ModeHighlight();
7221           SetTrainingModeOff();
7222           DisplayInformation(_("End of game"));
7223         }
7224       } else {
7225         DisplayError(_("Incorrect move"), 0);
7226       }
7227       return 1;
7228     }
7229
7230   /* Ok, now we know that the move is good, so we can kill
7231      the previous line in Analysis Mode */
7232   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7233                                 && currentMove < forwardMostMove) {
7234     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7235     else forwardMostMove = currentMove;
7236   }
7237
7238   ClearMap();
7239
7240   /* If we need the chess program but it's dead, restart it */
7241   ResurrectChessProgram();
7242
7243   /* A user move restarts a paused game*/
7244   if (pausing)
7245     PauseEvent();
7246
7247   thinkOutput[0] = NULLCHAR;
7248
7249   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7250
7251   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7252     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7253     return 1;
7254   }
7255
7256   if (gameMode == BeginningOfGame) {
7257     if (appData.noChessProgram) {
7258       gameMode = EditGame;
7259       SetGameInfo();
7260     } else {
7261       char buf[MSG_SIZ];
7262       gameMode = MachinePlaysBlack;
7263       StartClocks();
7264       SetGameInfo();
7265       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7266       DisplayTitle(buf);
7267       if (first.sendName) {
7268         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7269         SendToProgram(buf, &first);
7270       }
7271       StartClocks();
7272     }
7273     ModeHighlight();
7274   }
7275
7276   /* Relay move to ICS or chess engine */
7277   if (appData.icsActive) {
7278     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7279         gameMode == IcsExamining) {
7280       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7281         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7282         SendToICS("draw ");
7283         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7284       }
7285       // also send plain move, in case ICS does not understand atomic claims
7286       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7287       ics_user_moved = 1;
7288     }
7289   } else {
7290     if (first.sendTime && (gameMode == BeginningOfGame ||
7291                            gameMode == MachinePlaysWhite ||
7292                            gameMode == MachinePlaysBlack)) {
7293       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7294     }
7295     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7296          // [HGM] book: if program might be playing, let it use book
7297         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7298         first.maybeThinking = TRUE;
7299     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7300         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7301         SendBoard(&first, currentMove+1);
7302         if(second.analyzing) {
7303             if(!second.useSetboard) SendToProgram("undo\n", &second);
7304             SendBoard(&second, currentMove+1);
7305         }
7306     } else {
7307         SendMoveToProgram(forwardMostMove-1, &first);
7308         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7309     }
7310     if (currentMove == cmailOldMove + 1) {
7311       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7312     }
7313   }
7314
7315   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7316
7317   switch (gameMode) {
7318   case EditGame:
7319     if(appData.testLegality)
7320     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7321     case MT_NONE:
7322     case MT_CHECK:
7323       break;
7324     case MT_CHECKMATE:
7325     case MT_STAINMATE:
7326       if (WhiteOnMove(currentMove)) {
7327         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7328       } else {
7329         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7330       }
7331       break;
7332     case MT_STALEMATE:
7333       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7334       break;
7335     }
7336     break;
7337
7338   case MachinePlaysBlack:
7339   case MachinePlaysWhite:
7340     /* disable certain menu options while machine is thinking */
7341     SetMachineThinkingEnables();
7342     break;
7343
7344   default:
7345     break;
7346   }
7347
7348   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7349   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7350
7351   if(bookHit) { // [HGM] book: simulate book reply
7352         static char bookMove[MSG_SIZ]; // a bit generous?
7353
7354         programStats.nodes = programStats.depth = programStats.time =
7355         programStats.score = programStats.got_only_move = 0;
7356         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7357
7358         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7359         strcat(bookMove, bookHit);
7360         HandleMachineMove(bookMove, &first);
7361   }
7362   return 1;
7363 }
7364
7365 void
7366 MarkByFEN(char *fen)
7367 {
7368         int r, f;
7369         if(!appData.markers || !appData.highlightDragging) return;
7370         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7371         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7372         while(*fen) {
7373             int s = 0;
7374             marker[r][f] = 0;
7375             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7376             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7377             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7378             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7379             if(*fen == 'T') marker[r][f++] = 0; else
7380             if(*fen == 'Y') marker[r][f++] = 1; else
7381             if(*fen == 'G') marker[r][f++] = 3; else
7382             if(*fen == 'B') marker[r][f++] = 4; else
7383             if(*fen == 'C') marker[r][f++] = 5; else
7384             if(*fen == 'M') marker[r][f++] = 6; else
7385             if(*fen == 'W') marker[r][f++] = 7; else
7386             if(*fen == 'D') marker[r][f++] = 8; else
7387             if(*fen == 'R') marker[r][f++] = 2; else {
7388                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7389               f += s; fen -= s>0;
7390             }
7391             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7392             if(r < 0) break;
7393             fen++;
7394         }
7395         DrawPosition(TRUE, NULL);
7396 }
7397
7398 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7399
7400 void
7401 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7402 {
7403     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7404     Markers *m = (Markers *) closure;
7405     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7406         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7407                          || kind == WhiteCapturesEnPassant
7408                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7409     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7410 }
7411
7412 static int hoverSavedValid;
7413
7414 void
7415 MarkTargetSquares (int clear)
7416 {
7417   int x, y, sum=0;
7418   if(clear) { // no reason to ever suppress clearing
7419     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7420     hoverSavedValid = 0;
7421     if(!sum) return; // nothing was cleared,no redraw needed
7422   } else {
7423     int capt = 0;
7424     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7425        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7426     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7427     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7428       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7429       if(capt)
7430       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7431     }
7432   }
7433   DrawPosition(FALSE, NULL);
7434 }
7435
7436 int
7437 Explode (Board board, int fromX, int fromY, int toX, int toY)
7438 {
7439     if(gameInfo.variant == VariantAtomic &&
7440        (board[toY][toX] != EmptySquare ||                     // capture?
7441         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7442                          board[fromY][fromX] == BlackPawn   )
7443       )) {
7444         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7445         return TRUE;
7446     }
7447     return FALSE;
7448 }
7449
7450 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7451
7452 int
7453 CanPromote (ChessSquare piece, int y)
7454 {
7455         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7456         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7457         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7458         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7459            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7460           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7461            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7462         return (piece == BlackPawn && y <= zone ||
7463                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7464                 piece == BlackLance && y <= zone ||
7465                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7466 }
7467
7468 void
7469 HoverEvent (int xPix, int yPix, int x, int y)
7470 {
7471         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7472         int r, f;
7473         if(!first.highlight) return;
7474         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7475         if(x == oldX && y == oldY) return; // only do something if we enter new square
7476         oldFromX = fromX; oldFromY = fromY;
7477         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7478           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7479             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7480           hoverSavedValid = 1;
7481         } else if(oldX != x || oldY != y) {
7482           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7483           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7484           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7485             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7486           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7487             char buf[MSG_SIZ];
7488             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7489             SendToProgram(buf, &first);
7490           }
7491           oldX = x; oldY = y;
7492 //        SetHighlights(fromX, fromY, x, y);
7493         }
7494 }
7495
7496 void ReportClick(char *action, int x, int y)
7497 {
7498         char buf[MSG_SIZ]; // Inform engine of what user does
7499         int r, f;
7500         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7501           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7502             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7503         if(!first.highlight || gameMode == EditPosition) return;
7504         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7505         SendToProgram(buf, &first);
7506 }
7507
7508 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7509
7510 void
7511 LeftClick (ClickType clickType, int xPix, int yPix)
7512 {
7513     int x, y;
7514     Boolean saveAnimate;
7515     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7516     char promoChoice = NULLCHAR;
7517     ChessSquare piece;
7518     static TimeMark lastClickTime, prevClickTime;
7519
7520     x = EventToSquare(xPix, BOARD_WIDTH);
7521     y = EventToSquare(yPix, BOARD_HEIGHT);
7522     if (!flipView && y >= 0) {
7523         y = BOARD_HEIGHT - 1 - y;
7524     }
7525     if (flipView && x >= 0) {
7526         x = BOARD_WIDTH - 1 - x;
7527     }
7528
7529     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7530         static int dummy;
7531         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7532         right = TRUE;
7533         return;
7534     }
7535
7536     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7537
7538     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7539
7540     if (clickType == Press) ErrorPopDown();
7541     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7542
7543     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7544         defaultPromoChoice = promoSweep;
7545         promoSweep = EmptySquare;   // terminate sweep
7546         promoDefaultAltered = TRUE;
7547         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7548     }
7549
7550     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7551         if(clickType == Release) return; // ignore upclick of click-click destination
7552         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7553         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7554         if(gameInfo.holdingsWidth &&
7555                 (WhiteOnMove(currentMove)
7556                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7557                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7558             // click in right holdings, for determining promotion piece
7559             ChessSquare p = boards[currentMove][y][x];
7560             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7561             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7562             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7563                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7564                 fromX = fromY = -1;
7565                 return;
7566             }
7567         }
7568         DrawPosition(FALSE, boards[currentMove]);
7569         return;
7570     }
7571
7572     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7573     if(clickType == Press
7574             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7575               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7576               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7577         return;
7578
7579     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7580         // could be static click on premove from-square: abort premove
7581         gotPremove = 0;
7582         ClearPremoveHighlights();
7583     }
7584
7585     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7586         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7587
7588     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7589         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7590                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7591         defaultPromoChoice = DefaultPromoChoice(side);
7592     }
7593
7594     autoQueen = appData.alwaysPromoteToQueen;
7595
7596     if (fromX == -1) {
7597       int originalY = y;
7598       gatingPiece = EmptySquare;
7599       if (clickType != Press) {
7600         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7601             DragPieceEnd(xPix, yPix); dragging = 0;
7602             DrawPosition(FALSE, NULL);
7603         }
7604         return;
7605       }
7606       doubleClick = FALSE;
7607       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7608         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7609       }
7610       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7611       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7612          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7613          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7614             /* First square */
7615             if (OKToStartUserMove(fromX, fromY)) {
7616                 second = 0;
7617                 ReportClick("lift", x, y);
7618                 MarkTargetSquares(0);
7619                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7620                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7621                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7622                     promoSweep = defaultPromoChoice;
7623                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7624                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7625                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7626                 }
7627                 if (appData.highlightDragging) {
7628                     SetHighlights(fromX, fromY, -1, -1);
7629                 } else {
7630                     ClearHighlights();
7631                 }
7632             } else fromX = fromY = -1;
7633             return;
7634         }
7635     }
7636
7637     /* fromX != -1 */
7638     if (clickType == Press && gameMode != EditPosition) {
7639         ChessSquare fromP;
7640         ChessSquare toP;
7641         int frc;
7642
7643         // ignore off-board to clicks
7644         if(y < 0 || x < 0) return;
7645
7646         /* Check if clicking again on the same color piece */
7647         fromP = boards[currentMove][fromY][fromX];
7648         toP = boards[currentMove][y][x];
7649         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7650         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7651             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7652            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7653              WhitePawn <= toP && toP <= WhiteKing &&
7654              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7655              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7656             (BlackPawn <= fromP && fromP <= BlackKing &&
7657              BlackPawn <= toP && toP <= BlackKing &&
7658              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7659              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7660             /* Clicked again on same color piece -- changed his mind */
7661             second = (x == fromX && y == fromY);
7662             killX = killY = -1;
7663             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7664                 second = FALSE; // first double-click rather than scond click
7665                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7666             }
7667             promoDefaultAltered = FALSE;
7668             MarkTargetSquares(1);
7669            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7670             if (appData.highlightDragging) {
7671                 SetHighlights(x, y, -1, -1);
7672             } else {
7673                 ClearHighlights();
7674             }
7675             if (OKToStartUserMove(x, y)) {
7676                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7677                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7678                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7679                  gatingPiece = boards[currentMove][fromY][fromX];
7680                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7681                 fromX = x;
7682                 fromY = y; dragging = 1;
7683                 if(!second) ReportClick("lift", x, y);
7684                 MarkTargetSquares(0);
7685                 DragPieceBegin(xPix, yPix, FALSE);
7686                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7687                     promoSweep = defaultPromoChoice;
7688                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7689                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7690                 }
7691             }
7692            }
7693            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7694            second = FALSE;
7695         }
7696         // ignore clicks on holdings
7697         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7698     }
7699
7700     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7701         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7702         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7703         return;
7704     }
7705
7706     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7707         DragPieceEnd(xPix, yPix); dragging = 0;
7708         if(clearFlag) {
7709             // a deferred attempt to click-click move an empty square on top of a piece
7710             boards[currentMove][y][x] = EmptySquare;
7711             ClearHighlights();
7712             DrawPosition(FALSE, boards[currentMove]);
7713             fromX = fromY = -1; clearFlag = 0;
7714             return;
7715         }
7716         if (appData.animateDragging) {
7717             /* Undo animation damage if any */
7718             DrawPosition(FALSE, NULL);
7719         }
7720         if (second) {
7721             /* Second up/down in same square; just abort move */
7722             second = 0;
7723             fromX = fromY = -1;
7724             gatingPiece = EmptySquare;
7725             MarkTargetSquares(1);
7726             ClearHighlights();
7727             gotPremove = 0;
7728             ClearPremoveHighlights();
7729         } else {
7730             /* First upclick in same square; start click-click mode */
7731             SetHighlights(x, y, -1, -1);
7732         }
7733         return;
7734     }
7735
7736     clearFlag = 0;
7737
7738     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7739        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7740         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7741         DisplayMessage(_("only marked squares are legal"),"");
7742         DrawPosition(TRUE, NULL);
7743         return; // ignore to-click
7744     }
7745
7746     /* we now have a different from- and (possibly off-board) to-square */
7747     /* Completed move */
7748     if(!sweepSelecting) {
7749         toX = x;
7750         toY = y;
7751     }
7752
7753     piece = boards[currentMove][fromY][fromX];
7754
7755     saveAnimate = appData.animate;
7756     if (clickType == Press) {
7757         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7758         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7759             // must be Edit Position mode with empty-square selected
7760             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7761             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7762             return;
7763         }
7764         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7765             return;
7766         }
7767         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7768             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7769         } else
7770         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7771         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7772           if(appData.sweepSelect) {
7773             promoSweep = defaultPromoChoice;
7774             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7775             selectFlag = 0; lastX = xPix; lastY = yPix;
7776             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7777             Sweep(0); // Pawn that is going to promote: preview promotion piece
7778             sweepSelecting = 1;
7779             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7780             MarkTargetSquares(1);
7781           }
7782           return; // promo popup appears on up-click
7783         }
7784         /* Finish clickclick move */
7785         if (appData.animate || appData.highlightLastMove) {
7786             SetHighlights(fromX, fromY, toX, toY);
7787         } else {
7788             ClearHighlights();
7789         }
7790     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7791         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7792         *promoRestrict = 0;
7793         if (appData.animate || appData.highlightLastMove) {
7794             SetHighlights(fromX, fromY, toX, toY);
7795         } else {
7796             ClearHighlights();
7797         }
7798     } else {
7799 #if 0
7800 // [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
7801         /* Finish drag move */
7802         if (appData.highlightLastMove) {
7803             SetHighlights(fromX, fromY, toX, toY);
7804         } else {
7805             ClearHighlights();
7806         }
7807 #endif
7808         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7809           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7810         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7811         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7812           dragging *= 2;            // flag button-less dragging if we are dragging
7813           MarkTargetSquares(1);
7814           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7815           else {
7816             kill2X = killX; kill2Y = killY;
7817             killX = x; killY = y;     //remeber this square as intermediate
7818             ReportClick("put", x, y); // and inform engine
7819             ReportClick("lift", x, y);
7820             MarkTargetSquares(0);
7821             return;
7822           }
7823         }
7824         DragPieceEnd(xPix, yPix); dragging = 0;
7825         /* Don't animate move and drag both */
7826         appData.animate = FALSE;
7827     }
7828
7829     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7830     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7831         ChessSquare piece = boards[currentMove][fromY][fromX];
7832         if(gameMode == EditPosition && piece != EmptySquare &&
7833            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7834             int n;
7835
7836             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7837                 n = PieceToNumber(piece - (int)BlackPawn);
7838                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7839                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7840                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7841             } else
7842             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7843                 n = PieceToNumber(piece);
7844                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7845                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7846                 boards[currentMove][n][BOARD_WIDTH-2]++;
7847             }
7848             boards[currentMove][fromY][fromX] = EmptySquare;
7849         }
7850         ClearHighlights();
7851         fromX = fromY = -1;
7852         MarkTargetSquares(1);
7853         DrawPosition(TRUE, boards[currentMove]);
7854         return;
7855     }
7856
7857     // off-board moves should not be highlighted
7858     if(x < 0 || y < 0) ClearHighlights();
7859     else ReportClick("put", x, y);
7860
7861     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7862
7863     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7864
7865     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7866         SetHighlights(fromX, fromY, toX, toY);
7867         MarkTargetSquares(1);
7868         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7869             // [HGM] super: promotion to captured piece selected from holdings
7870             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7871             promotionChoice = TRUE;
7872             // kludge follows to temporarily execute move on display, without promoting yet
7873             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7874             boards[currentMove][toY][toX] = p;
7875             DrawPosition(FALSE, boards[currentMove]);
7876             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7877             boards[currentMove][toY][toX] = q;
7878             DisplayMessage("Click in holdings to choose piece", "");
7879             return;
7880         }
7881         PromotionPopUp(promoChoice);
7882     } else {
7883         int oldMove = currentMove;
7884         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7885         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7886         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7887         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7888            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7889             DrawPosition(TRUE, boards[currentMove]);
7890         MarkTargetSquares(1);
7891         fromX = fromY = -1;
7892     }
7893     appData.animate = saveAnimate;
7894     if (appData.animate || appData.animateDragging) {
7895         /* Undo animation damage if needed */
7896         DrawPosition(FALSE, NULL);
7897     }
7898 }
7899
7900 int
7901 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7902 {   // front-end-free part taken out of PieceMenuPopup
7903     int whichMenu; int xSqr, ySqr;
7904
7905     if(seekGraphUp) { // [HGM] seekgraph
7906         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7907         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7908         return -2;
7909     }
7910
7911     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7912          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7913         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7914         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7915         if(action == Press)   {
7916             originalFlip = flipView;
7917             flipView = !flipView; // temporarily flip board to see game from partners perspective
7918             DrawPosition(TRUE, partnerBoard);
7919             DisplayMessage(partnerStatus, "");
7920             partnerUp = TRUE;
7921         } else if(action == Release) {
7922             flipView = originalFlip;
7923             DrawPosition(TRUE, boards[currentMove]);
7924             partnerUp = FALSE;
7925         }
7926         return -2;
7927     }
7928
7929     xSqr = EventToSquare(x, BOARD_WIDTH);
7930     ySqr = EventToSquare(y, BOARD_HEIGHT);
7931     if (action == Release) {
7932         if(pieceSweep != EmptySquare) {
7933             EditPositionMenuEvent(pieceSweep, toX, toY);
7934             pieceSweep = EmptySquare;
7935         } else UnLoadPV(); // [HGM] pv
7936     }
7937     if (action != Press) return -2; // return code to be ignored
7938     switch (gameMode) {
7939       case IcsExamining:
7940         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7941       case EditPosition:
7942         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7943         if (xSqr < 0 || ySqr < 0) return -1;
7944         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7945         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7946         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7947         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7948         NextPiece(0);
7949         return 2; // grab
7950       case IcsObserving:
7951         if(!appData.icsEngineAnalyze) return -1;
7952       case IcsPlayingWhite:
7953       case IcsPlayingBlack:
7954         if(!appData.zippyPlay) goto noZip;
7955       case AnalyzeMode:
7956       case AnalyzeFile:
7957       case MachinePlaysWhite:
7958       case MachinePlaysBlack:
7959       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7960         if (!appData.dropMenu) {
7961           LoadPV(x, y);
7962           return 2; // flag front-end to grab mouse events
7963         }
7964         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7965            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7966       case EditGame:
7967       noZip:
7968         if (xSqr < 0 || ySqr < 0) return -1;
7969         if (!appData.dropMenu || appData.testLegality &&
7970             gameInfo.variant != VariantBughouse &&
7971             gameInfo.variant != VariantCrazyhouse) return -1;
7972         whichMenu = 1; // drop menu
7973         break;
7974       default:
7975         return -1;
7976     }
7977
7978     if (((*fromX = xSqr) < 0) ||
7979         ((*fromY = ySqr) < 0)) {
7980         *fromX = *fromY = -1;
7981         return -1;
7982     }
7983     if (flipView)
7984       *fromX = BOARD_WIDTH - 1 - *fromX;
7985     else
7986       *fromY = BOARD_HEIGHT - 1 - *fromY;
7987
7988     return whichMenu;
7989 }
7990
7991 void
7992 Wheel (int dir, int x, int y)
7993 {
7994     if(gameMode == EditPosition) {
7995         int xSqr = EventToSquare(x, BOARD_WIDTH);
7996         int ySqr = EventToSquare(y, BOARD_HEIGHT);
7997         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
7998         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
7999         do {
8000             boards[currentMove][ySqr][xSqr] += dir;
8001             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8002             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8003         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8004         DrawPosition(FALSE, boards[currentMove]);
8005     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8006 }
8007
8008 void
8009 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8010 {
8011 //    char * hint = lastHint;
8012     FrontEndProgramStats stats;
8013
8014     stats.which = cps == &first ? 0 : 1;
8015     stats.depth = cpstats->depth;
8016     stats.nodes = cpstats->nodes;
8017     stats.score = cpstats->score;
8018     stats.time = cpstats->time;
8019     stats.pv = cpstats->movelist;
8020     stats.hint = lastHint;
8021     stats.an_move_index = 0;
8022     stats.an_move_count = 0;
8023
8024     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8025         stats.hint = cpstats->move_name;
8026         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8027         stats.an_move_count = cpstats->nr_moves;
8028     }
8029
8030     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
8031
8032     SetProgramStats( &stats );
8033 }
8034
8035 void
8036 ClearEngineOutputPane (int which)
8037 {
8038     static FrontEndProgramStats dummyStats;
8039     dummyStats.which = which;
8040     dummyStats.pv = "#";
8041     SetProgramStats( &dummyStats );
8042 }
8043
8044 #define MAXPLAYERS 500
8045
8046 char *
8047 TourneyStandings (int display)
8048 {
8049     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8050     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8051     char result, *p, *names[MAXPLAYERS];
8052
8053     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8054         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8055     names[0] = p = strdup(appData.participants);
8056     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8057
8058     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8059
8060     while(result = appData.results[nr]) {
8061         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8062         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8063         wScore = bScore = 0;
8064         switch(result) {
8065           case '+': wScore = 2; break;
8066           case '-': bScore = 2; break;
8067           case '=': wScore = bScore = 1; break;
8068           case ' ':
8069           case '*': return strdup("busy"); // tourney not finished
8070         }
8071         score[w] += wScore;
8072         score[b] += bScore;
8073         games[w]++;
8074         games[b]++;
8075         nr++;
8076     }
8077     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8078     for(w=0; w<nPlayers; w++) {
8079         bScore = -1;
8080         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8081         ranking[w] = b; points[w] = bScore; score[b] = -2;
8082     }
8083     p = malloc(nPlayers*34+1);
8084     for(w=0; w<nPlayers && w<display; w++)
8085         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8086     free(names[0]);
8087     return p;
8088 }
8089
8090 void
8091 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8092 {       // count all piece types
8093         int p, f, r;
8094         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8095         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8096         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8097                 p = board[r][f];
8098                 pCnt[p]++;
8099                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8100                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8101                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8102                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8103                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8104                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8105         }
8106 }
8107
8108 int
8109 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8110 {
8111         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8112         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8113
8114         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8115         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8116         if(myPawns == 2 && nMine == 3) // KPP
8117             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8118         if(myPawns == 1 && nMine == 2) // KP
8119             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8120         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8121             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8122         if(myPawns) return FALSE;
8123         if(pCnt[WhiteRook+side])
8124             return pCnt[BlackRook-side] ||
8125                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8126                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8127                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8128         if(pCnt[WhiteCannon+side]) {
8129             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8130             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8131         }
8132         if(pCnt[WhiteKnight+side])
8133             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8134         return FALSE;
8135 }
8136
8137 int
8138 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8139 {
8140         VariantClass v = gameInfo.variant;
8141
8142         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8143         if(v == VariantShatranj) return TRUE; // always winnable through baring
8144         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8145         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8146
8147         if(v == VariantXiangqi) {
8148                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8149
8150                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8151                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8152                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8153                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8154                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8155                 if(stale) // we have at least one last-rank P plus perhaps C
8156                     return majors // KPKX
8157                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8158                 else // KCA*E*
8159                     return pCnt[WhiteFerz+side] // KCAK
8160                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8161                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8162                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8163
8164         } else if(v == VariantKnightmate) {
8165                 if(nMine == 1) return FALSE;
8166                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8167         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8168                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8169
8170                 if(nMine == 1) return FALSE; // bare King
8171                 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
8172                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8173                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8174                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8175                 if(pCnt[WhiteKnight+side])
8176                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8177                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8178                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8179                 if(nBishops)
8180                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8181                 if(pCnt[WhiteAlfil+side])
8182                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8183                 if(pCnt[WhiteWazir+side])
8184                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8185         }
8186
8187         return TRUE;
8188 }
8189
8190 int
8191 CompareWithRights (Board b1, Board b2)
8192 {
8193     int rights = 0;
8194     if(!CompareBoards(b1, b2)) return FALSE;
8195     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8196     /* compare castling rights */
8197     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8198            rights++; /* King lost rights, while rook still had them */
8199     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8200         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8201            rights++; /* but at least one rook lost them */
8202     }
8203     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8204            rights++;
8205     if( b1[CASTLING][5] != NoRights ) {
8206         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8207            rights++;
8208     }
8209     return rights == 0;
8210 }
8211
8212 int
8213 Adjudicate (ChessProgramState *cps)
8214 {       // [HGM] some adjudications useful with buggy engines
8215         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8216         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8217         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8218         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8219         int k, drop, count = 0; static int bare = 1;
8220         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8221         Boolean canAdjudicate = !appData.icsActive;
8222
8223         // most tests only when we understand the game, i.e. legality-checking on
8224             if( appData.testLegality )
8225             {   /* [HGM] Some more adjudications for obstinate engines */
8226                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8227                 static int moveCount = 6;
8228                 ChessMove result;
8229                 char *reason = NULL;
8230
8231                 /* Count what is on board. */
8232                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8233
8234                 /* Some material-based adjudications that have to be made before stalemate test */
8235                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8236                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8237                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8238                      if(canAdjudicate && appData.checkMates) {
8239                          if(engineOpponent)
8240                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8241                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8242                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8243                          return 1;
8244                      }
8245                 }
8246
8247                 /* Bare King in Shatranj (loses) or Losers (wins) */
8248                 if( nrW == 1 || nrB == 1) {
8249                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8250                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8251                      if(canAdjudicate && appData.checkMates) {
8252                          if(engineOpponent)
8253                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8254                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8255                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8256                          return 1;
8257                      }
8258                   } else
8259                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8260                   {    /* bare King */
8261                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8262                         if(canAdjudicate && appData.checkMates) {
8263                             /* but only adjudicate if adjudication enabled */
8264                             if(engineOpponent)
8265                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8266                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8267                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8268                             return 1;
8269                         }
8270                   }
8271                 } else bare = 1;
8272
8273
8274             // don't wait for engine to announce game end if we can judge ourselves
8275             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8276               case MT_CHECK:
8277                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8278                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8279                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8280                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8281                             checkCnt++;
8282                         if(checkCnt >= 2) {
8283                             reason = "Xboard adjudication: 3rd check";
8284                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8285                             break;
8286                         }
8287                     }
8288                 }
8289               case MT_NONE:
8290               default:
8291                 break;
8292               case MT_STEALMATE:
8293               case MT_STALEMATE:
8294               case MT_STAINMATE:
8295                 reason = "Xboard adjudication: Stalemate";
8296                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8297                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8298                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8299                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8300                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8301                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8302                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8303                                                                         EP_CHECKMATE : EP_WINS);
8304                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8305                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8306                 }
8307                 break;
8308               case MT_CHECKMATE:
8309                 reason = "Xboard adjudication: Checkmate";
8310                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8311                 if(gameInfo.variant == VariantShogi) {
8312                     if(forwardMostMove > backwardMostMove
8313                        && moveList[forwardMostMove-1][1] == '@'
8314                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8315                         reason = "XBoard adjudication: pawn-drop mate";
8316                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8317                     }
8318                 }
8319                 break;
8320             }
8321
8322                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8323                     case EP_STALEMATE:
8324                         result = GameIsDrawn; break;
8325                     case EP_CHECKMATE:
8326                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8327                     case EP_WINS:
8328                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8329                     default:
8330                         result = EndOfFile;
8331                 }
8332                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8333                     if(engineOpponent)
8334                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8335                     GameEnds( result, reason, GE_XBOARD );
8336                     return 1;
8337                 }
8338
8339                 /* Next absolutely insufficient mating material. */
8340                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8341                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8342                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8343
8344                      /* always flag draws, for judging claims */
8345                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8346
8347                      if(canAdjudicate && appData.materialDraws) {
8348                          /* but only adjudicate them if adjudication enabled */
8349                          if(engineOpponent) {
8350                            SendToProgram("force\n", engineOpponent); // suppress reply
8351                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8352                          }
8353                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8354                          return 1;
8355                      }
8356                 }
8357
8358                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8359                 if(gameInfo.variant == VariantXiangqi ?
8360                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8361                  : nrW + nrB == 4 &&
8362                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8363                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8364                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8365                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8366                    ) ) {
8367                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8368                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8369                           if(engineOpponent) {
8370                             SendToProgram("force\n", engineOpponent); // suppress reply
8371                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8372                           }
8373                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8374                           return 1;
8375                      }
8376                 } else moveCount = 6;
8377             }
8378
8379         // Repetition draws and 50-move rule can be applied independently of legality testing
8380
8381                 /* Check for rep-draws */
8382                 count = 0;
8383                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8384                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8385                 for(k = forwardMostMove-2;
8386                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8387                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8388                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8389                     k-=2)
8390                 {   int rights=0;
8391                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8392                         /* compare castling rights */
8393                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8394                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8395                                 rights++; /* King lost rights, while rook still had them */
8396                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8397                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8398                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8399                                    rights++; /* but at least one rook lost them */
8400                         }
8401                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8402                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8403                                 rights++;
8404                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8405                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8406                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8407                                    rights++;
8408                         }
8409                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8410                             && appData.drawRepeats > 1) {
8411                              /* adjudicate after user-specified nr of repeats */
8412                              int result = GameIsDrawn;
8413                              char *details = "XBoard adjudication: repetition draw";
8414                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8415                                 // [HGM] xiangqi: check for forbidden perpetuals
8416                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8417                                 for(m=forwardMostMove; m>k; m-=2) {
8418                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8419                                         ourPerpetual = 0; // the current mover did not always check
8420                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8421                                         hisPerpetual = 0; // the opponent did not always check
8422                                 }
8423                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8424                                                                         ourPerpetual, hisPerpetual);
8425                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8426                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8427                                     details = "Xboard adjudication: perpetual checking";
8428                                 } else
8429                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8430                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8431                                 } else
8432                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8433                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8434                                         result = BlackWins;
8435                                         details = "Xboard adjudication: repetition";
8436                                     }
8437                                 } else // it must be XQ
8438                                 // Now check for perpetual chases
8439                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8440                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8441                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8442                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8443                                         static char resdet[MSG_SIZ];
8444                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8445                                         details = resdet;
8446                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8447                                     } else
8448                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8449                                         break; // Abort repetition-checking loop.
8450                                 }
8451                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8452                              }
8453                              if(engineOpponent) {
8454                                SendToProgram("force\n", engineOpponent); // suppress reply
8455                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8456                              }
8457                              GameEnds( result, details, GE_XBOARD );
8458                              return 1;
8459                         }
8460                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8461                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8462                     }
8463                 }
8464
8465                 /* Now we test for 50-move draws. Determine ply count */
8466                 count = forwardMostMove;
8467                 /* look for last irreversble move */
8468                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8469                     count--;
8470                 /* if we hit starting position, add initial plies */
8471                 if( count == backwardMostMove )
8472                     count -= initialRulePlies;
8473                 count = forwardMostMove - count;
8474                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8475                         // adjust reversible move counter for checks in Xiangqi
8476                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8477                         if(i < backwardMostMove) i = backwardMostMove;
8478                         while(i <= forwardMostMove) {
8479                                 lastCheck = inCheck; // check evasion does not count
8480                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8481                                 if(inCheck || lastCheck) count--; // check does not count
8482                                 i++;
8483                         }
8484                 }
8485                 if( count >= 100)
8486                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8487                          /* this is used to judge if draw claims are legal */
8488                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8489                          if(engineOpponent) {
8490                            SendToProgram("force\n", engineOpponent); // suppress reply
8491                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8492                          }
8493                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8494                          return 1;
8495                 }
8496
8497                 /* if draw offer is pending, treat it as a draw claim
8498                  * when draw condition present, to allow engines a way to
8499                  * claim draws before making their move to avoid a race
8500                  * condition occurring after their move
8501                  */
8502                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8503                          char *p = NULL;
8504                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8505                              p = "Draw claim: 50-move rule";
8506                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8507                              p = "Draw claim: 3-fold repetition";
8508                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8509                              p = "Draw claim: insufficient mating material";
8510                          if( p != NULL && canAdjudicate) {
8511                              if(engineOpponent) {
8512                                SendToProgram("force\n", engineOpponent); // suppress reply
8513                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8514                              }
8515                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8516                              return 1;
8517                          }
8518                 }
8519
8520                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8521                     if(engineOpponent) {
8522                       SendToProgram("force\n", engineOpponent); // suppress reply
8523                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8524                     }
8525                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8526                     return 1;
8527                 }
8528         return 0;
8529 }
8530
8531 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8532 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8533 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8534
8535 static int
8536 BitbaseProbe ()
8537 {
8538     int pieces[10], squares[10], cnt=0, r, f, res;
8539     static int loaded;
8540     static PPROBE_EGBB probeBB;
8541     if(!appData.testLegality) return 10;
8542     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8543     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8544     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8545     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8546         ChessSquare piece = boards[forwardMostMove][r][f];
8547         int black = (piece >= BlackPawn);
8548         int type = piece - black*BlackPawn;
8549         if(piece == EmptySquare) continue;
8550         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8551         if(type == WhiteKing) type = WhiteQueen + 1;
8552         type = egbbCode[type];
8553         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8554         pieces[cnt] = type + black*6;
8555         if(++cnt > 5) return 11;
8556     }
8557     pieces[cnt] = squares[cnt] = 0;
8558     // probe EGBB
8559     if(loaded == 2) return 13; // loading failed before
8560     if(loaded == 0) {
8561         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8562         HMODULE lib;
8563         PLOAD_EGBB loadBB;
8564         loaded = 2; // prepare for failure
8565         if(!path) return 13; // no egbb installed
8566         strncpy(buf, path + 8, MSG_SIZ);
8567         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8568         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8569         lib = LoadLibrary(buf);
8570         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8571         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8572         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8573         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8574         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8575         loaded = 1; // success!
8576     }
8577     res = probeBB(forwardMostMove & 1, pieces, squares);
8578     return res > 0 ? 1 : res < 0 ? -1 : 0;
8579 }
8580
8581 char *
8582 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8583 {   // [HGM] book: this routine intercepts moves to simulate book replies
8584     char *bookHit = NULL;
8585
8586     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8587         char buf[MSG_SIZ];
8588         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8589         SendToProgram(buf, cps);
8590     }
8591     //first determine if the incoming move brings opponent into his book
8592     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8593         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8594     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8595     if(bookHit != NULL && !cps->bookSuspend) {
8596         // make sure opponent is not going to reply after receiving move to book position
8597         SendToProgram("force\n", cps);
8598         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8599     }
8600     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8601     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8602     // now arrange restart after book miss
8603     if(bookHit) {
8604         // after a book hit we never send 'go', and the code after the call to this routine
8605         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8606         char buf[MSG_SIZ], *move = bookHit;
8607         if(cps->useSAN) {
8608             int fromX, fromY, toX, toY;
8609             char promoChar;
8610             ChessMove moveType;
8611             move = buf + 30;
8612             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8613                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8614                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8615                                     PosFlags(forwardMostMove),
8616                                     fromY, fromX, toY, toX, promoChar, move);
8617             } else {
8618                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8619                 bookHit = NULL;
8620             }
8621         }
8622         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8623         SendToProgram(buf, cps);
8624         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8625     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8626         SendToProgram("go\n", cps);
8627         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8628     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8629         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8630             SendToProgram("go\n", cps);
8631         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8632     }
8633     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8634 }
8635
8636 int
8637 LoadError (char *errmess, ChessProgramState *cps)
8638 {   // unloads engine and switches back to -ncp mode if it was first
8639     if(cps->initDone) return FALSE;
8640     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8641     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8642     cps->pr = NoProc;
8643     if(cps == &first) {
8644         appData.noChessProgram = TRUE;
8645         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8646         gameMode = BeginningOfGame; ModeHighlight();
8647         SetNCPMode();
8648     }
8649     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8650     DisplayMessage("", ""); // erase waiting message
8651     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8652     return TRUE;
8653 }
8654
8655 char *savedMessage;
8656 ChessProgramState *savedState;
8657 void
8658 DeferredBookMove (void)
8659 {
8660         if(savedState->lastPing != savedState->lastPong)
8661                     ScheduleDelayedEvent(DeferredBookMove, 10);
8662         else
8663         HandleMachineMove(savedMessage, savedState);
8664 }
8665
8666 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8667 static ChessProgramState *stalledEngine;
8668 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8669
8670 void
8671 HandleMachineMove (char *message, ChessProgramState *cps)
8672 {
8673     static char firstLeg[20];
8674     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8675     char realname[MSG_SIZ];
8676     int fromX, fromY, toX, toY;
8677     ChessMove moveType;
8678     char promoChar, roar;
8679     char *p, *pv=buf1;
8680     int oldError;
8681     char *bookHit;
8682
8683     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8684         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8685         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8686             DisplayError(_("Invalid pairing from pairing engine"), 0);
8687             return;
8688         }
8689         pairingReceived = 1;
8690         NextMatchGame();
8691         return; // Skim the pairing messages here.
8692     }
8693
8694     oldError = cps->userError; cps->userError = 0;
8695
8696 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8697     /*
8698      * Kludge to ignore BEL characters
8699      */
8700     while (*message == '\007') message++;
8701
8702     /*
8703      * [HGM] engine debug message: ignore lines starting with '#' character
8704      */
8705     if(cps->debug && *message == '#') return;
8706
8707     /*
8708      * Look for book output
8709      */
8710     if (cps == &first && bookRequested) {
8711         if (message[0] == '\t' || message[0] == ' ') {
8712             /* Part of the book output is here; append it */
8713             strcat(bookOutput, message);
8714             strcat(bookOutput, "  \n");
8715             return;
8716         } else if (bookOutput[0] != NULLCHAR) {
8717             /* All of book output has arrived; display it */
8718             char *p = bookOutput;
8719             while (*p != NULLCHAR) {
8720                 if (*p == '\t') *p = ' ';
8721                 p++;
8722             }
8723             DisplayInformation(bookOutput);
8724             bookRequested = FALSE;
8725             /* Fall through to parse the current output */
8726         }
8727     }
8728
8729     /*
8730      * Look for machine move.
8731      */
8732     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8733         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8734     {
8735         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8736             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8737             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8738             stalledEngine = cps;
8739             if(appData.ponderNextMove) { // bring opponent out of ponder
8740                 if(gameMode == TwoMachinesPlay) {
8741                     if(cps->other->pause)
8742                         PauseEngine(cps->other);
8743                     else
8744                         SendToProgram("easy\n", cps->other);
8745                 }
8746             }
8747             StopClocks();
8748             return;
8749         }
8750
8751       if(cps->usePing) {
8752
8753         /* This method is only useful on engines that support ping */
8754         if(abortEngineThink) {
8755             if (appData.debugMode) {
8756                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8757             }
8758             SendToProgram("undo\n", cps);
8759             return;
8760         }
8761
8762         if (cps->lastPing != cps->lastPong) {
8763             /* Extra move from before last new; ignore */
8764             if (appData.debugMode) {
8765                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8766             }
8767           return;
8768         }
8769
8770       } else {
8771
8772         int machineWhite = FALSE;
8773
8774         switch (gameMode) {
8775           case BeginningOfGame:
8776             /* Extra move from before last reset; ignore */
8777             if (appData.debugMode) {
8778                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8779             }
8780             return;
8781
8782           case EndOfGame:
8783           case IcsIdle:
8784           default:
8785             /* Extra move after we tried to stop.  The mode test is
8786                not a reliable way of detecting this problem, but it's
8787                the best we can do on engines that don't support ping.
8788             */
8789             if (appData.debugMode) {
8790                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8791                         cps->which, gameMode);
8792             }
8793             SendToProgram("undo\n", cps);
8794             return;
8795
8796           case MachinePlaysWhite:
8797           case IcsPlayingWhite:
8798             machineWhite = TRUE;
8799             break;
8800
8801           case MachinePlaysBlack:
8802           case IcsPlayingBlack:
8803             machineWhite = FALSE;
8804             break;
8805
8806           case TwoMachinesPlay:
8807             machineWhite = (cps->twoMachinesColor[0] == 'w');
8808             break;
8809         }
8810         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8811             if (appData.debugMode) {
8812                 fprintf(debugFP,
8813                         "Ignoring move out of turn by %s, gameMode %d"
8814                         ", forwardMost %d\n",
8815                         cps->which, gameMode, forwardMostMove);
8816             }
8817             return;
8818         }
8819       }
8820
8821         if(cps->alphaRank) AlphaRank(machineMove, 4);
8822
8823         // [HGM] lion: (some very limited) support for Alien protocol
8824         killX = killY = kill2X = kill2Y = -1;
8825         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8826             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8827             return;
8828         }
8829         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8830             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8831             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8832         }
8833         if(firstLeg[0]) { // there was a previous leg;
8834             // only support case where same piece makes two step
8835             char buf[20], *p = machineMove+1, *q = buf+1, f;
8836             safeStrCpy(buf, machineMove, 20);
8837             while(isdigit(*q)) q++; // find start of to-square
8838             safeStrCpy(machineMove, firstLeg, 20);
8839             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8840             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8841             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8842             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8843             firstLeg[0] = NULLCHAR;
8844         }
8845
8846         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8847                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8848             /* Machine move could not be parsed; ignore it. */
8849           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8850                     machineMove, _(cps->which));
8851             DisplayMoveError(buf1);
8852             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8853                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8854             if (gameMode == TwoMachinesPlay) {
8855               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8856                        buf1, GE_XBOARD);
8857             }
8858             return;
8859         }
8860
8861         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8862         /* So we have to redo legality test with true e.p. status here,  */
8863         /* to make sure an illegal e.p. capture does not slip through,   */
8864         /* to cause a forfeit on a justified illegal-move complaint      */
8865         /* of the opponent.                                              */
8866         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8867            ChessMove moveType;
8868            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8869                              fromY, fromX, toY, toX, promoChar);
8870             if(moveType == IllegalMove) {
8871               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8872                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8873                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8874                            buf1, GE_XBOARD);
8875                 return;
8876            } else if(!appData.fischerCastling)
8877            /* [HGM] Kludge to handle engines that send FRC-style castling
8878               when they shouldn't (like TSCP-Gothic) */
8879            switch(moveType) {
8880              case WhiteASideCastleFR:
8881              case BlackASideCastleFR:
8882                toX+=2;
8883                currentMoveString[2]++;
8884                break;
8885              case WhiteHSideCastleFR:
8886              case BlackHSideCastleFR:
8887                toX--;
8888                currentMoveString[2]--;
8889                break;
8890              default: ; // nothing to do, but suppresses warning of pedantic compilers
8891            }
8892         }
8893         hintRequested = FALSE;
8894         lastHint[0] = NULLCHAR;
8895         bookRequested = FALSE;
8896         /* Program may be pondering now */
8897         cps->maybeThinking = TRUE;
8898         if (cps->sendTime == 2) cps->sendTime = 1;
8899         if (cps->offeredDraw) cps->offeredDraw--;
8900
8901         /* [AS] Save move info*/
8902         pvInfoList[ forwardMostMove ].score = programStats.score;
8903         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8904         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8905
8906         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8907
8908         /* Test suites abort the 'game' after one move */
8909         if(*appData.finger) {
8910            static FILE *f;
8911            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8912            if(!f) f = fopen(appData.finger, "w");
8913            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8914            else { DisplayFatalError("Bad output file", errno, 0); return; }
8915            free(fen);
8916            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8917         }
8918         if(appData.epd) {
8919            if(solvingTime >= 0) {
8920               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8921               totalTime += solvingTime; first.matchWins++;
8922            } else {
8923               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8924               second.matchWins++;
8925            }
8926            OutputKibitz(2, buf1);
8927            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8928         }
8929
8930         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8931         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8932             int count = 0;
8933
8934             while( count < adjudicateLossPlies ) {
8935                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8936
8937                 if( count & 1 ) {
8938                     score = -score; /* Flip score for winning side */
8939                 }
8940
8941                 if( score > appData.adjudicateLossThreshold ) {
8942                     break;
8943                 }
8944
8945                 count++;
8946             }
8947
8948             if( count >= adjudicateLossPlies ) {
8949                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8950
8951                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8952                     "Xboard adjudication",
8953                     GE_XBOARD );
8954
8955                 return;
8956             }
8957         }
8958
8959         if(Adjudicate(cps)) {
8960             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8961             return; // [HGM] adjudicate: for all automatic game ends
8962         }
8963
8964 #if ZIPPY
8965         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8966             first.initDone) {
8967           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8968                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8969                 SendToICS("draw ");
8970                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8971           }
8972           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8973           ics_user_moved = 1;
8974           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8975                 char buf[3*MSG_SIZ];
8976
8977                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8978                         programStats.score / 100.,
8979                         programStats.depth,
8980                         programStats.time / 100.,
8981                         (unsigned int)programStats.nodes,
8982                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8983                         programStats.movelist);
8984                 SendToICS(buf);
8985           }
8986         }
8987 #endif
8988
8989         /* [AS] Clear stats for next move */
8990         ClearProgramStats();
8991         thinkOutput[0] = NULLCHAR;
8992         hiddenThinkOutputState = 0;
8993
8994         bookHit = NULL;
8995         if (gameMode == TwoMachinesPlay) {
8996             /* [HGM] relaying draw offers moved to after reception of move */
8997             /* and interpreting offer as claim if it brings draw condition */
8998             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8999                 SendToProgram("draw\n", cps->other);
9000             }
9001             if (cps->other->sendTime) {
9002                 SendTimeRemaining(cps->other,
9003                                   cps->other->twoMachinesColor[0] == 'w');
9004             }
9005             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9006             if (firstMove && !bookHit) {
9007                 firstMove = FALSE;
9008                 if (cps->other->useColors) {
9009                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9010                 }
9011                 SendToProgram("go\n", cps->other);
9012             }
9013             cps->other->maybeThinking = TRUE;
9014         }
9015
9016         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9017
9018         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9019
9020         if (!pausing && appData.ringBellAfterMoves) {
9021             if(!roar) RingBell();
9022         }
9023
9024         /*
9025          * Reenable menu items that were disabled while
9026          * machine was thinking
9027          */
9028         if (gameMode != TwoMachinesPlay)
9029             SetUserThinkingEnables();
9030
9031         // [HGM] book: after book hit opponent has received move and is now in force mode
9032         // force the book reply into it, and then fake that it outputted this move by jumping
9033         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9034         if(bookHit) {
9035                 static char bookMove[MSG_SIZ]; // a bit generous?
9036
9037                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9038                 strcat(bookMove, bookHit);
9039                 message = bookMove;
9040                 cps = cps->other;
9041                 programStats.nodes = programStats.depth = programStats.time =
9042                 programStats.score = programStats.got_only_move = 0;
9043                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9044
9045                 if(cps->lastPing != cps->lastPong) {
9046                     savedMessage = message; // args for deferred call
9047                     savedState = cps;
9048                     ScheduleDelayedEvent(DeferredBookMove, 10);
9049                     return;
9050                 }
9051                 goto FakeBookMove;
9052         }
9053
9054         return;
9055     }
9056
9057     /* Set special modes for chess engines.  Later something general
9058      *  could be added here; for now there is just one kludge feature,
9059      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9060      *  when "xboard" is given as an interactive command.
9061      */
9062     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9063         cps->useSigint = FALSE;
9064         cps->useSigterm = FALSE;
9065     }
9066     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9067       ParseFeatures(message+8, cps);
9068       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9069     }
9070
9071     if (!strncmp(message, "setup ", 6) && 
9072         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9073           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9074                                         ) { // [HGM] allow first engine to define opening position
9075       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9076       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9077       *buf = NULLCHAR;
9078       if(sscanf(message, "setup (%s", buf) == 1) {
9079         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9080         ASSIGN(appData.pieceToCharTable, buf);
9081       }
9082       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9083       if(dummy >= 3) {
9084         while(message[s] && message[s++] != ' ');
9085         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9086            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9087             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9088             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9089           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9090           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9091           startedFromSetupPosition = FALSE;
9092         }
9093       }
9094       if(startedFromSetupPosition) return;
9095       ParseFEN(boards[0], &dummy, message+s, FALSE);
9096       DrawPosition(TRUE, boards[0]);
9097       CopyBoard(initialPosition, boards[0]);
9098       startedFromSetupPosition = TRUE;
9099       return;
9100     }
9101     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9102       ChessSquare piece = WhitePawn;
9103       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9104       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9105       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9106       piece += CharToPiece(ID & 255) - WhitePawn;
9107       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9108       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9109       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9110       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9111       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9112       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9113                                                && gameInfo.variant != VariantGreat
9114                                                && gameInfo.variant != VariantFairy    ) return;
9115       if(piece < EmptySquare) {
9116         pieceDefs = TRUE;
9117         ASSIGN(pieceDesc[piece], buf1);
9118         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9119       }
9120       return;
9121     }
9122     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9123       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9124       Sweep(0);
9125       return;
9126     }
9127     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9128      * want this, I was asked to put it in, and obliged.
9129      */
9130     if (!strncmp(message, "setboard ", 9)) {
9131         Board initial_position;
9132
9133         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9134
9135         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9136             DisplayError(_("Bad FEN received from engine"), 0);
9137             return ;
9138         } else {
9139            Reset(TRUE, FALSE);
9140            CopyBoard(boards[0], initial_position);
9141            initialRulePlies = FENrulePlies;
9142            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9143            else gameMode = MachinePlaysBlack;
9144            DrawPosition(FALSE, boards[currentMove]);
9145         }
9146         return;
9147     }
9148
9149     /*
9150      * Look for communication commands
9151      */
9152     if (!strncmp(message, "telluser ", 9)) {
9153         if(message[9] == '\\' && message[10] == '\\')
9154             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9155         PlayTellSound();
9156         DisplayNote(message + 9);
9157         return;
9158     }
9159     if (!strncmp(message, "tellusererror ", 14)) {
9160         cps->userError = 1;
9161         if(message[14] == '\\' && message[15] == '\\')
9162             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9163         PlayTellSound();
9164         DisplayError(message + 14, 0);
9165         return;
9166     }
9167     if (!strncmp(message, "tellopponent ", 13)) {
9168       if (appData.icsActive) {
9169         if (loggedOn) {
9170           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9171           SendToICS(buf1);
9172         }
9173       } else {
9174         DisplayNote(message + 13);
9175       }
9176       return;
9177     }
9178     if (!strncmp(message, "tellothers ", 11)) {
9179       if (appData.icsActive) {
9180         if (loggedOn) {
9181           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9182           SendToICS(buf1);
9183         }
9184       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9185       return;
9186     }
9187     if (!strncmp(message, "tellall ", 8)) {
9188       if (appData.icsActive) {
9189         if (loggedOn) {
9190           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9191           SendToICS(buf1);
9192         }
9193       } else {
9194         DisplayNote(message + 8);
9195       }
9196       return;
9197     }
9198     if (strncmp(message, "warning", 7) == 0) {
9199         /* Undocumented feature, use tellusererror in new code */
9200         DisplayError(message, 0);
9201         return;
9202     }
9203     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9204         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9205         strcat(realname, " query");
9206         AskQuestion(realname, buf2, buf1, cps->pr);
9207         return;
9208     }
9209     /* Commands from the engine directly to ICS.  We don't allow these to be
9210      *  sent until we are logged on. Crafty kibitzes have been known to
9211      *  interfere with the login process.
9212      */
9213     if (loggedOn) {
9214         if (!strncmp(message, "tellics ", 8)) {
9215             SendToICS(message + 8);
9216             SendToICS("\n");
9217             return;
9218         }
9219         if (!strncmp(message, "tellicsnoalias ", 15)) {
9220             SendToICS(ics_prefix);
9221             SendToICS(message + 15);
9222             SendToICS("\n");
9223             return;
9224         }
9225         /* The following are for backward compatibility only */
9226         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9227             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9228             SendToICS(ics_prefix);
9229             SendToICS(message);
9230             SendToICS("\n");
9231             return;
9232         }
9233     }
9234     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9235         if(initPing == cps->lastPong) {
9236             if(gameInfo.variant == VariantUnknown) {
9237                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9238                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9239                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9240             }
9241             initPing = -1;
9242         }
9243         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9244             abortEngineThink = FALSE;
9245             DisplayMessage("", "");
9246             ThawUI();
9247         }
9248         return;
9249     }
9250     if(!strncmp(message, "highlight ", 10)) {
9251         if(appData.testLegality && !*engineVariant && appData.markers) return;
9252         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9253         return;
9254     }
9255     if(!strncmp(message, "click ", 6)) {
9256         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9257         if(appData.testLegality || !appData.oneClick) return;
9258         sscanf(message+6, "%c%d%c", &f, &y, &c);
9259         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9260         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9261         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9262         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9263         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9264         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9265             LeftClick(Release, lastLeftX, lastLeftY);
9266         controlKey  = (c == ',');
9267         LeftClick(Press, x, y);
9268         LeftClick(Release, x, y);
9269         first.highlight = f;
9270         return;
9271     }
9272     /*
9273      * If the move is illegal, cancel it and redraw the board.
9274      * Also deal with other error cases.  Matching is rather loose
9275      * here to accommodate engines written before the spec.
9276      */
9277     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9278         strncmp(message, "Error", 5) == 0) {
9279         if (StrStr(message, "name") ||
9280             StrStr(message, "rating") || StrStr(message, "?") ||
9281             StrStr(message, "result") || StrStr(message, "board") ||
9282             StrStr(message, "bk") || StrStr(message, "computer") ||
9283             StrStr(message, "variant") || StrStr(message, "hint") ||
9284             StrStr(message, "random") || StrStr(message, "depth") ||
9285             StrStr(message, "accepted")) {
9286             return;
9287         }
9288         if (StrStr(message, "protover")) {
9289           /* Program is responding to input, so it's apparently done
9290              initializing, and this error message indicates it is
9291              protocol version 1.  So we don't need to wait any longer
9292              for it to initialize and send feature commands. */
9293           FeatureDone(cps, 1);
9294           cps->protocolVersion = 1;
9295           return;
9296         }
9297         cps->maybeThinking = FALSE;
9298
9299         if (StrStr(message, "draw")) {
9300             /* Program doesn't have "draw" command */
9301             cps->sendDrawOffers = 0;
9302             return;
9303         }
9304         if (cps->sendTime != 1 &&
9305             (StrStr(message, "time") || StrStr(message, "otim"))) {
9306           /* Program apparently doesn't have "time" or "otim" command */
9307           cps->sendTime = 0;
9308           return;
9309         }
9310         if (StrStr(message, "analyze")) {
9311             cps->analysisSupport = FALSE;
9312             cps->analyzing = FALSE;
9313 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9314             EditGameEvent(); // [HGM] try to preserve loaded game
9315             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9316             DisplayError(buf2, 0);
9317             return;
9318         }
9319         if (StrStr(message, "(no matching move)st")) {
9320           /* Special kludge for GNU Chess 4 only */
9321           cps->stKludge = TRUE;
9322           SendTimeControl(cps, movesPerSession, timeControl,
9323                           timeIncrement, appData.searchDepth,
9324                           searchTime);
9325           return;
9326         }
9327         if (StrStr(message, "(no matching move)sd")) {
9328           /* Special kludge for GNU Chess 4 only */
9329           cps->sdKludge = TRUE;
9330           SendTimeControl(cps, movesPerSession, timeControl,
9331                           timeIncrement, appData.searchDepth,
9332                           searchTime);
9333           return;
9334         }
9335         if (!StrStr(message, "llegal")) {
9336             return;
9337         }
9338         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9339             gameMode == IcsIdle) return;
9340         if (forwardMostMove <= backwardMostMove) return;
9341         if (pausing) PauseEvent();
9342       if(appData.forceIllegal) {
9343             // [HGM] illegal: machine refused move; force position after move into it
9344           SendToProgram("force\n", cps);
9345           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9346                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9347                 // when black is to move, while there might be nothing on a2 or black
9348                 // might already have the move. So send the board as if white has the move.
9349                 // But first we must change the stm of the engine, as it refused the last move
9350                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9351                 if(WhiteOnMove(forwardMostMove)) {
9352                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9353                     SendBoard(cps, forwardMostMove); // kludgeless board
9354                 } else {
9355                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9356                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9357                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9358                 }
9359           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9360             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9361                  gameMode == TwoMachinesPlay)
9362               SendToProgram("go\n", cps);
9363             return;
9364       } else
9365         if (gameMode == PlayFromGameFile) {
9366             /* Stop reading this game file */
9367             gameMode = EditGame;
9368             ModeHighlight();
9369         }
9370         /* [HGM] illegal-move claim should forfeit game when Xboard */
9371         /* only passes fully legal moves                            */
9372         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9373             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9374                                 "False illegal-move claim", GE_XBOARD );
9375             return; // do not take back move we tested as valid
9376         }
9377         currentMove = forwardMostMove-1;
9378         DisplayMove(currentMove-1); /* before DisplayMoveError */
9379         SwitchClocks(forwardMostMove-1); // [HGM] race
9380         DisplayBothClocks();
9381         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9382                 parseList[currentMove], _(cps->which));
9383         DisplayMoveError(buf1);
9384         DrawPosition(FALSE, boards[currentMove]);
9385
9386         SetUserThinkingEnables();
9387         return;
9388     }
9389     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9390         /* Program has a broken "time" command that
9391            outputs a string not ending in newline.
9392            Don't use it. */
9393         cps->sendTime = 0;
9394     }
9395     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9396         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9397             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9398     }
9399
9400     /*
9401      * If chess program startup fails, exit with an error message.
9402      * Attempts to recover here are futile. [HGM] Well, we try anyway
9403      */
9404     if ((StrStr(message, "unknown host") != NULL)
9405         || (StrStr(message, "No remote directory") != NULL)
9406         || (StrStr(message, "not found") != NULL)
9407         || (StrStr(message, "No such file") != NULL)
9408         || (StrStr(message, "can't alloc") != NULL)
9409         || (StrStr(message, "Permission denied") != NULL)) {
9410
9411         cps->maybeThinking = FALSE;
9412         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9413                 _(cps->which), cps->program, cps->host, message);
9414         RemoveInputSource(cps->isr);
9415         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9416             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9417             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9418         }
9419         return;
9420     }
9421
9422     /*
9423      * Look for hint output
9424      */
9425     if (sscanf(message, "Hint: %s", buf1) == 1) {
9426         if (cps == &first && hintRequested) {
9427             hintRequested = FALSE;
9428             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9429                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9430                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9431                                     PosFlags(forwardMostMove),
9432                                     fromY, fromX, toY, toX, promoChar, buf1);
9433                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9434                 DisplayInformation(buf2);
9435             } else {
9436                 /* Hint move could not be parsed!? */
9437               snprintf(buf2, sizeof(buf2),
9438                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9439                         buf1, _(cps->which));
9440                 DisplayError(buf2, 0);
9441             }
9442         } else {
9443           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9444         }
9445         return;
9446     }
9447
9448     /*
9449      * Ignore other messages if game is not in progress
9450      */
9451     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9452         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9453
9454     /*
9455      * look for win, lose, draw, or draw offer
9456      */
9457     if (strncmp(message, "1-0", 3) == 0) {
9458         char *p, *q, *r = "";
9459         p = strchr(message, '{');
9460         if (p) {
9461             q = strchr(p, '}');
9462             if (q) {
9463                 *q = NULLCHAR;
9464                 r = p + 1;
9465             }
9466         }
9467         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9468         return;
9469     } else if (strncmp(message, "0-1", 3) == 0) {
9470         char *p, *q, *r = "";
9471         p = strchr(message, '{');
9472         if (p) {
9473             q = strchr(p, '}');
9474             if (q) {
9475                 *q = NULLCHAR;
9476                 r = p + 1;
9477             }
9478         }
9479         /* Kludge for Arasan 4.1 bug */
9480         if (strcmp(r, "Black resigns") == 0) {
9481             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9482             return;
9483         }
9484         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9485         return;
9486     } else if (strncmp(message, "1/2", 3) == 0) {
9487         char *p, *q, *r = "";
9488         p = strchr(message, '{');
9489         if (p) {
9490             q = strchr(p, '}');
9491             if (q) {
9492                 *q = NULLCHAR;
9493                 r = p + 1;
9494             }
9495         }
9496
9497         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9498         return;
9499
9500     } else if (strncmp(message, "White resign", 12) == 0) {
9501         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9502         return;
9503     } else if (strncmp(message, "Black resign", 12) == 0) {
9504         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9505         return;
9506     } else if (strncmp(message, "White matches", 13) == 0 ||
9507                strncmp(message, "Black matches", 13) == 0   ) {
9508         /* [HGM] ignore GNUShogi noises */
9509         return;
9510     } else if (strncmp(message, "White", 5) == 0 &&
9511                message[5] != '(' &&
9512                StrStr(message, "Black") == NULL) {
9513         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9514         return;
9515     } else if (strncmp(message, "Black", 5) == 0 &&
9516                message[5] != '(') {
9517         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9518         return;
9519     } else if (strcmp(message, "resign") == 0 ||
9520                strcmp(message, "computer resigns") == 0) {
9521         switch (gameMode) {
9522           case MachinePlaysBlack:
9523           case IcsPlayingBlack:
9524             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9525             break;
9526           case MachinePlaysWhite:
9527           case IcsPlayingWhite:
9528             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9529             break;
9530           case TwoMachinesPlay:
9531             if (cps->twoMachinesColor[0] == 'w')
9532               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9533             else
9534               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9535             break;
9536           default:
9537             /* can't happen */
9538             break;
9539         }
9540         return;
9541     } else if (strncmp(message, "opponent mates", 14) == 0) {
9542         switch (gameMode) {
9543           case MachinePlaysBlack:
9544           case IcsPlayingBlack:
9545             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9546             break;
9547           case MachinePlaysWhite:
9548           case IcsPlayingWhite:
9549             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9550             break;
9551           case TwoMachinesPlay:
9552             if (cps->twoMachinesColor[0] == 'w')
9553               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9554             else
9555               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9556             break;
9557           default:
9558             /* can't happen */
9559             break;
9560         }
9561         return;
9562     } else if (strncmp(message, "computer mates", 14) == 0) {
9563         switch (gameMode) {
9564           case MachinePlaysBlack:
9565           case IcsPlayingBlack:
9566             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9567             break;
9568           case MachinePlaysWhite:
9569           case IcsPlayingWhite:
9570             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9571             break;
9572           case TwoMachinesPlay:
9573             if (cps->twoMachinesColor[0] == 'w')
9574               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9575             else
9576               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9577             break;
9578           default:
9579             /* can't happen */
9580             break;
9581         }
9582         return;
9583     } else if (strncmp(message, "checkmate", 9) == 0) {
9584         if (WhiteOnMove(forwardMostMove)) {
9585             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9586         } else {
9587             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9588         }
9589         return;
9590     } else if (strstr(message, "Draw") != NULL ||
9591                strstr(message, "game is a draw") != NULL) {
9592         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9593         return;
9594     } else if (strstr(message, "offer") != NULL &&
9595                strstr(message, "draw") != NULL) {
9596 #if ZIPPY
9597         if (appData.zippyPlay && first.initDone) {
9598             /* Relay offer to ICS */
9599             SendToICS(ics_prefix);
9600             SendToICS("draw\n");
9601         }
9602 #endif
9603         cps->offeredDraw = 2; /* valid until this engine moves twice */
9604         if (gameMode == TwoMachinesPlay) {
9605             if (cps->other->offeredDraw) {
9606                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9607             /* [HGM] in two-machine mode we delay relaying draw offer      */
9608             /* until after we also have move, to see if it is really claim */
9609             }
9610         } else if (gameMode == MachinePlaysWhite ||
9611                    gameMode == MachinePlaysBlack) {
9612           if (userOfferedDraw) {
9613             DisplayInformation(_("Machine accepts your draw offer"));
9614             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9615           } else {
9616             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9617           }
9618         }
9619     }
9620
9621
9622     /*
9623      * Look for thinking output
9624      */
9625     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9626           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9627                                 ) {
9628         int plylev, mvleft, mvtot, curscore, time;
9629         char mvname[MOVE_LEN];
9630         u64 nodes; // [DM]
9631         char plyext;
9632         int ignore = FALSE;
9633         int prefixHint = FALSE;
9634         mvname[0] = NULLCHAR;
9635
9636         switch (gameMode) {
9637           case MachinePlaysBlack:
9638           case IcsPlayingBlack:
9639             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9640             break;
9641           case MachinePlaysWhite:
9642           case IcsPlayingWhite:
9643             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9644             break;
9645           case AnalyzeMode:
9646           case AnalyzeFile:
9647             break;
9648           case IcsObserving: /* [DM] icsEngineAnalyze */
9649             if (!appData.icsEngineAnalyze) ignore = TRUE;
9650             break;
9651           case TwoMachinesPlay:
9652             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9653                 ignore = TRUE;
9654             }
9655             break;
9656           default:
9657             ignore = TRUE;
9658             break;
9659         }
9660
9661         if (!ignore) {
9662             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9663             buf1[0] = NULLCHAR;
9664             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9665                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9666                 char score_buf[MSG_SIZ];
9667
9668                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9669                     nodes += u64Const(0x100000000);
9670
9671                 if (plyext != ' ' && plyext != '\t') {
9672                     time *= 100;
9673                 }
9674
9675                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9676                 if( cps->scoreIsAbsolute &&
9677                     ( gameMode == MachinePlaysBlack ||
9678                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9679                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9680                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9681                      !WhiteOnMove(currentMove)
9682                     ) )
9683                 {
9684                     curscore = -curscore;
9685                 }
9686
9687                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9688
9689                 if(*bestMove) { // rememer time best EPD move was first found
9690                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9691                     ChessMove mt;
9692                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9693                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9694                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9695                 }
9696
9697                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9698                         char buf[MSG_SIZ];
9699                         FILE *f;
9700                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9701                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9702                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9703                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9704                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9705                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9706                                 fclose(f);
9707                         }
9708                         else
9709                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9710                           DisplayError(_("failed writing PV"), 0);
9711                 }
9712
9713                 tempStats.depth = plylev;
9714                 tempStats.nodes = nodes;
9715                 tempStats.time = time;
9716                 tempStats.score = curscore;
9717                 tempStats.got_only_move = 0;
9718
9719                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9720                         int ticklen;
9721
9722                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9723                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9724                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9725                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9726                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9727                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9728                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9729                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9730                 }
9731
9732                 /* Buffer overflow protection */
9733                 if (pv[0] != NULLCHAR) {
9734                     if (strlen(pv) >= sizeof(tempStats.movelist)
9735                         && appData.debugMode) {
9736                         fprintf(debugFP,
9737                                 "PV is too long; using the first %u bytes.\n",
9738                                 (unsigned) sizeof(tempStats.movelist) - 1);
9739                     }
9740
9741                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9742                 } else {
9743                     sprintf(tempStats.movelist, " no PV\n");
9744                 }
9745
9746                 if (tempStats.seen_stat) {
9747                     tempStats.ok_to_send = 1;
9748                 }
9749
9750                 if (strchr(tempStats.movelist, '(') != NULL) {
9751                     tempStats.line_is_book = 1;
9752                     tempStats.nr_moves = 0;
9753                     tempStats.moves_left = 0;
9754                 } else {
9755                     tempStats.line_is_book = 0;
9756                 }
9757
9758                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9759                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9760
9761                 SendProgramStatsToFrontend( cps, &tempStats );
9762
9763                 /*
9764                     [AS] Protect the thinkOutput buffer from overflow... this
9765                     is only useful if buf1 hasn't overflowed first!
9766                 */
9767                 if(curscore >= MATE_SCORE) 
9768                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9769                 else if(curscore <= -MATE_SCORE) 
9770                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9771                 else
9772                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9773                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9774                          plylev,
9775                          (gameMode == TwoMachinesPlay ?
9776                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9777                          score_buf,
9778                          prefixHint ? lastHint : "",
9779                          prefixHint ? " " : "" );
9780
9781                 if( buf1[0] != NULLCHAR ) {
9782                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9783
9784                     if( strlen(pv) > max_len ) {
9785                         if( appData.debugMode) {
9786                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9787                         }
9788                         pv[max_len+1] = '\0';
9789                     }
9790
9791                     strcat( thinkOutput, pv);
9792                 }
9793
9794                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9795                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9796                     DisplayMove(currentMove - 1);
9797                 }
9798                 return;
9799
9800             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9801                 /* crafty (9.25+) says "(only move) <move>"
9802                  * if there is only 1 legal move
9803                  */
9804                 sscanf(p, "(only move) %s", buf1);
9805                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9806                 sprintf(programStats.movelist, "%s (only move)", buf1);
9807                 programStats.depth = 1;
9808                 programStats.nr_moves = 1;
9809                 programStats.moves_left = 1;
9810                 programStats.nodes = 1;
9811                 programStats.time = 1;
9812                 programStats.got_only_move = 1;
9813
9814                 /* Not really, but we also use this member to
9815                    mean "line isn't going to change" (Crafty
9816                    isn't searching, so stats won't change) */
9817                 programStats.line_is_book = 1;
9818
9819                 SendProgramStatsToFrontend( cps, &programStats );
9820
9821                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9822                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9823                     DisplayMove(currentMove - 1);
9824                 }
9825                 return;
9826             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9827                               &time, &nodes, &plylev, &mvleft,
9828                               &mvtot, mvname) >= 5) {
9829                 /* The stat01: line is from Crafty (9.29+) in response
9830                    to the "." command */
9831                 programStats.seen_stat = 1;
9832                 cps->maybeThinking = TRUE;
9833
9834                 if (programStats.got_only_move || !appData.periodicUpdates)
9835                   return;
9836
9837                 programStats.depth = plylev;
9838                 programStats.time = time;
9839                 programStats.nodes = nodes;
9840                 programStats.moves_left = mvleft;
9841                 programStats.nr_moves = mvtot;
9842                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9843                 programStats.ok_to_send = 1;
9844                 programStats.movelist[0] = '\0';
9845
9846                 SendProgramStatsToFrontend( cps, &programStats );
9847
9848                 return;
9849
9850             } else if (strncmp(message,"++",2) == 0) {
9851                 /* Crafty 9.29+ outputs this */
9852                 programStats.got_fail = 2;
9853                 return;
9854
9855             } else if (strncmp(message,"--",2) == 0) {
9856                 /* Crafty 9.29+ outputs this */
9857                 programStats.got_fail = 1;
9858                 return;
9859
9860             } else if (thinkOutput[0] != NULLCHAR &&
9861                        strncmp(message, "    ", 4) == 0) {
9862                 unsigned message_len;
9863
9864                 p = message;
9865                 while (*p && *p == ' ') p++;
9866
9867                 message_len = strlen( p );
9868
9869                 /* [AS] Avoid buffer overflow */
9870                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9871                     strcat(thinkOutput, " ");
9872                     strcat(thinkOutput, p);
9873                 }
9874
9875                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9876                     strcat(programStats.movelist, " ");
9877                     strcat(programStats.movelist, p);
9878                 }
9879
9880                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9881                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9882                     DisplayMove(currentMove - 1);
9883                 }
9884                 return;
9885             }
9886         }
9887         else {
9888             buf1[0] = NULLCHAR;
9889
9890             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9891                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9892             {
9893                 ChessProgramStats cpstats;
9894
9895                 if (plyext != ' ' && plyext != '\t') {
9896                     time *= 100;
9897                 }
9898
9899                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9900                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9901                     curscore = -curscore;
9902                 }
9903
9904                 cpstats.depth = plylev;
9905                 cpstats.nodes = nodes;
9906                 cpstats.time = time;
9907                 cpstats.score = curscore;
9908                 cpstats.got_only_move = 0;
9909                 cpstats.movelist[0] = '\0';
9910
9911                 if (buf1[0] != NULLCHAR) {
9912                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9913                 }
9914
9915                 cpstats.ok_to_send = 0;
9916                 cpstats.line_is_book = 0;
9917                 cpstats.nr_moves = 0;
9918                 cpstats.moves_left = 0;
9919
9920                 SendProgramStatsToFrontend( cps, &cpstats );
9921             }
9922         }
9923     }
9924 }
9925
9926
9927 /* Parse a game score from the character string "game", and
9928    record it as the history of the current game.  The game
9929    score is NOT assumed to start from the standard position.
9930    The display is not updated in any way.
9931    */
9932 void
9933 ParseGameHistory (char *game)
9934 {
9935     ChessMove moveType;
9936     int fromX, fromY, toX, toY, boardIndex;
9937     char promoChar;
9938     char *p, *q;
9939     char buf[MSG_SIZ];
9940
9941     if (appData.debugMode)
9942       fprintf(debugFP, "Parsing game history: %s\n", game);
9943
9944     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9945     gameInfo.site = StrSave(appData.icsHost);
9946     gameInfo.date = PGNDate();
9947     gameInfo.round = StrSave("-");
9948
9949     /* Parse out names of players */
9950     while (*game == ' ') game++;
9951     p = buf;
9952     while (*game != ' ') *p++ = *game++;
9953     *p = NULLCHAR;
9954     gameInfo.white = StrSave(buf);
9955     while (*game == ' ') game++;
9956     p = buf;
9957     while (*game != ' ' && *game != '\n') *p++ = *game++;
9958     *p = NULLCHAR;
9959     gameInfo.black = StrSave(buf);
9960
9961     /* Parse moves */
9962     boardIndex = blackPlaysFirst ? 1 : 0;
9963     yynewstr(game);
9964     for (;;) {
9965         yyboardindex = boardIndex;
9966         moveType = (ChessMove) Myylex();
9967         switch (moveType) {
9968           case IllegalMove:             /* maybe suicide chess, etc. */
9969   if (appData.debugMode) {
9970     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9971     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9972     setbuf(debugFP, NULL);
9973   }
9974           case WhitePromotion:
9975           case BlackPromotion:
9976           case WhiteNonPromotion:
9977           case BlackNonPromotion:
9978           case NormalMove:
9979           case FirstLeg:
9980           case WhiteCapturesEnPassant:
9981           case BlackCapturesEnPassant:
9982           case WhiteKingSideCastle:
9983           case WhiteQueenSideCastle:
9984           case BlackKingSideCastle:
9985           case BlackQueenSideCastle:
9986           case WhiteKingSideCastleWild:
9987           case WhiteQueenSideCastleWild:
9988           case BlackKingSideCastleWild:
9989           case BlackQueenSideCastleWild:
9990           /* PUSH Fabien */
9991           case WhiteHSideCastleFR:
9992           case WhiteASideCastleFR:
9993           case BlackHSideCastleFR:
9994           case BlackASideCastleFR:
9995           /* POP Fabien */
9996             fromX = currentMoveString[0] - AAA;
9997             fromY = currentMoveString[1] - ONE;
9998             toX = currentMoveString[2] - AAA;
9999             toY = currentMoveString[3] - ONE;
10000             promoChar = currentMoveString[4];
10001             break;
10002           case WhiteDrop:
10003           case BlackDrop:
10004             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10005             fromX = moveType == WhiteDrop ?
10006               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10007             (int) CharToPiece(ToLower(currentMoveString[0]));
10008             fromY = DROP_RANK;
10009             toX = currentMoveString[2] - AAA;
10010             toY = currentMoveString[3] - ONE;
10011             promoChar = NULLCHAR;
10012             break;
10013           case AmbiguousMove:
10014             /* bug? */
10015             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10016   if (appData.debugMode) {
10017     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10018     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10019     setbuf(debugFP, NULL);
10020   }
10021             DisplayError(buf, 0);
10022             return;
10023           case ImpossibleMove:
10024             /* bug? */
10025             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10026   if (appData.debugMode) {
10027     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10028     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10029     setbuf(debugFP, NULL);
10030   }
10031             DisplayError(buf, 0);
10032             return;
10033           case EndOfFile:
10034             if (boardIndex < backwardMostMove) {
10035                 /* Oops, gap.  How did that happen? */
10036                 DisplayError(_("Gap in move list"), 0);
10037                 return;
10038             }
10039             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10040             if (boardIndex > forwardMostMove) {
10041                 forwardMostMove = boardIndex;
10042             }
10043             return;
10044           case ElapsedTime:
10045             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10046                 strcat(parseList[boardIndex-1], " ");
10047                 strcat(parseList[boardIndex-1], yy_text);
10048             }
10049             continue;
10050           case Comment:
10051           case PGNTag:
10052           case NAG:
10053           default:
10054             /* ignore */
10055             continue;
10056           case WhiteWins:
10057           case BlackWins:
10058           case GameIsDrawn:
10059           case GameUnfinished:
10060             if (gameMode == IcsExamining) {
10061                 if (boardIndex < backwardMostMove) {
10062                     /* Oops, gap.  How did that happen? */
10063                     return;
10064                 }
10065                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10066                 return;
10067             }
10068             gameInfo.result = moveType;
10069             p = strchr(yy_text, '{');
10070             if (p == NULL) p = strchr(yy_text, '(');
10071             if (p == NULL) {
10072                 p = yy_text;
10073                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10074             } else {
10075                 q = strchr(p, *p == '{' ? '}' : ')');
10076                 if (q != NULL) *q = NULLCHAR;
10077                 p++;
10078             }
10079             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10080             gameInfo.resultDetails = StrSave(p);
10081             continue;
10082         }
10083         if (boardIndex >= forwardMostMove &&
10084             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10085             backwardMostMove = blackPlaysFirst ? 1 : 0;
10086             return;
10087         }
10088         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10089                                  fromY, fromX, toY, toX, promoChar,
10090                                  parseList[boardIndex]);
10091         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10092         /* currentMoveString is set as a side-effect of yylex */
10093         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10094         strcat(moveList[boardIndex], "\n");
10095         boardIndex++;
10096         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10097         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10098           case MT_NONE:
10099           case MT_STALEMATE:
10100           default:
10101             break;
10102           case MT_CHECK:
10103             if(!IS_SHOGI(gameInfo.variant))
10104                 strcat(parseList[boardIndex - 1], "+");
10105             break;
10106           case MT_CHECKMATE:
10107           case MT_STAINMATE:
10108             strcat(parseList[boardIndex - 1], "#");
10109             break;
10110         }
10111     }
10112 }
10113
10114
10115 /* Apply a move to the given board  */
10116 void
10117 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10118 {
10119   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10120   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10121
10122     /* [HGM] compute & store e.p. status and castling rights for new position */
10123     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10124
10125       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10126       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10127       board[EP_STATUS] = EP_NONE;
10128       board[EP_FILE] = board[EP_RANK] = 100;
10129
10130   if (fromY == DROP_RANK) {
10131         /* must be first */
10132         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10133             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10134             return;
10135         }
10136         piece = board[toY][toX] = (ChessSquare) fromX;
10137   } else {
10138 //      ChessSquare victim;
10139       int i;
10140
10141       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10142 //           victim = board[killY][killX],
10143            killed = board[killY][killX],
10144            board[killY][killX] = EmptySquare,
10145            board[EP_STATUS] = EP_CAPTURE;
10146            if( kill2X >= 0 && kill2Y >= 0)
10147              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10148       }
10149
10150       if( board[toY][toX] != EmptySquare ) {
10151            board[EP_STATUS] = EP_CAPTURE;
10152            if( (fromX != toX || fromY != toY) && // not igui!
10153                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10154                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10155                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10156            }
10157       }
10158
10159       pawn = board[fromY][fromX];
10160       if( pawn == WhiteLance || pawn == BlackLance ) {
10161            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10162                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10163                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10164            }
10165       }
10166       if( pawn == WhitePawn ) {
10167            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10168                board[EP_STATUS] = EP_PAWN_MOVE;
10169            if( toY-fromY>=2) {
10170                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10171                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10172                         gameInfo.variant != VariantBerolina || toX < fromX)
10173                       board[EP_STATUS] = toX | berolina;
10174                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10175                         gameInfo.variant != VariantBerolina || toX > fromX)
10176                       board[EP_STATUS] = toX;
10177            }
10178       } else
10179       if( pawn == BlackPawn ) {
10180            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10181                board[EP_STATUS] = EP_PAWN_MOVE;
10182            if( toY-fromY<= -2) {
10183                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10184                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10185                         gameInfo.variant != VariantBerolina || toX < fromX)
10186                       board[EP_STATUS] = toX | berolina;
10187                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10188                         gameInfo.variant != VariantBerolina || toX > fromX)
10189                       board[EP_STATUS] = toX;
10190            }
10191        }
10192
10193        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10194        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10195        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10196        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10197
10198        for(i=0; i<nrCastlingRights; i++) {
10199            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10200               board[CASTLING][i] == toX   && castlingRank[i] == toY
10201              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10202        }
10203
10204        if(gameInfo.variant == VariantSChess) { // update virginity
10205            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10206            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10207            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10208            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10209        }
10210
10211      if (fromX == toX && fromY == toY) return;
10212
10213      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10214      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10215      if(gameInfo.variant == VariantKnightmate)
10216          king += (int) WhiteUnicorn - (int) WhiteKing;
10217
10218     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10219        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10220         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10221         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10222         board[EP_STATUS] = EP_NONE; // capture was fake!
10223     } else
10224     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10225         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10226         board[toY][toX] = piece;
10227         board[EP_STATUS] = EP_NONE; // capture was fake!
10228     } else
10229     /* Code added by Tord: */
10230     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10231     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10232         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10233       board[EP_STATUS] = EP_NONE; // capture was fake!
10234       board[fromY][fromX] = EmptySquare;
10235       board[toY][toX] = EmptySquare;
10236       if((toX > fromX) != (piece == WhiteRook)) {
10237         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10238       } else {
10239         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10240       }
10241     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10242                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10243       board[EP_STATUS] = EP_NONE;
10244       board[fromY][fromX] = EmptySquare;
10245       board[toY][toX] = EmptySquare;
10246       if((toX > fromX) != (piece == BlackRook)) {
10247         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10248       } else {
10249         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10250       }
10251     /* End of code added by Tord */
10252
10253     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10254         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10255         board[toY][toX] = piece;
10256     } else if (board[fromY][fromX] == king
10257         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10258         && toY == fromY && toX > fromX+1) {
10259         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10260         board[fromY][toX-1] = board[fromY][rookX];
10261         board[fromY][rookX] = EmptySquare;
10262         board[fromY][fromX] = EmptySquare;
10263         board[toY][toX] = king;
10264     } else if (board[fromY][fromX] == king
10265         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10266                && toY == fromY && toX < fromX-1) {
10267         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10268         board[fromY][toX+1] = board[fromY][rookX];
10269         board[fromY][rookX] = EmptySquare;
10270         board[fromY][fromX] = EmptySquare;
10271         board[toY][toX] = king;
10272     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10273                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10274                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10275                ) {
10276         /* white pawn promotion */
10277         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10278         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10279             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10280         board[fromY][fromX] = EmptySquare;
10281     } else if ((fromY >= BOARD_HEIGHT>>1)
10282                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10283                && (toX != fromX)
10284                && gameInfo.variant != VariantXiangqi
10285                && gameInfo.variant != VariantBerolina
10286                && (pawn == WhitePawn)
10287                && (board[toY][toX] == EmptySquare)) {
10288         board[fromY][fromX] = EmptySquare;
10289         board[toY][toX] = piece;
10290         if(toY == epRank - 128 + 1)
10291             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10292         else
10293             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10294     } else if ((fromY == BOARD_HEIGHT-4)
10295                && (toX == fromX)
10296                && gameInfo.variant == VariantBerolina
10297                && (board[fromY][fromX] == WhitePawn)
10298                && (board[toY][toX] == EmptySquare)) {
10299         board[fromY][fromX] = EmptySquare;
10300         board[toY][toX] = WhitePawn;
10301         if(oldEP & EP_BEROLIN_A) {
10302                 captured = board[fromY][fromX-1];
10303                 board[fromY][fromX-1] = EmptySquare;
10304         }else{  captured = board[fromY][fromX+1];
10305                 board[fromY][fromX+1] = EmptySquare;
10306         }
10307     } else if (board[fromY][fromX] == king
10308         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10309                && toY == fromY && toX > fromX+1) {
10310         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10311         board[fromY][toX-1] = board[fromY][rookX];
10312         board[fromY][rookX] = EmptySquare;
10313         board[fromY][fromX] = EmptySquare;
10314         board[toY][toX] = king;
10315     } else if (board[fromY][fromX] == king
10316         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10317                && toY == fromY && toX < fromX-1) {
10318         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10319         board[fromY][toX+1] = board[fromY][rookX];
10320         board[fromY][rookX] = EmptySquare;
10321         board[fromY][fromX] = EmptySquare;
10322         board[toY][toX] = king;
10323     } else if (fromY == 7 && fromX == 3
10324                && board[fromY][fromX] == BlackKing
10325                && toY == 7 && toX == 5) {
10326         board[fromY][fromX] = EmptySquare;
10327         board[toY][toX] = BlackKing;
10328         board[fromY][7] = EmptySquare;
10329         board[toY][4] = BlackRook;
10330     } else if (fromY == 7 && fromX == 3
10331                && board[fromY][fromX] == BlackKing
10332                && toY == 7 && toX == 1) {
10333         board[fromY][fromX] = EmptySquare;
10334         board[toY][toX] = BlackKing;
10335         board[fromY][0] = EmptySquare;
10336         board[toY][2] = BlackRook;
10337     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10338                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10339                && toY < promoRank && promoChar
10340                ) {
10341         /* black pawn promotion */
10342         board[toY][toX] = CharToPiece(ToLower(promoChar));
10343         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10344             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10345         board[fromY][fromX] = EmptySquare;
10346     } else if ((fromY < BOARD_HEIGHT>>1)
10347                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10348                && (toX != fromX)
10349                && gameInfo.variant != VariantXiangqi
10350                && gameInfo.variant != VariantBerolina
10351                && (pawn == BlackPawn)
10352                && (board[toY][toX] == EmptySquare)) {
10353         board[fromY][fromX] = EmptySquare;
10354         board[toY][toX] = piece;
10355         if(toY == epRank - 128 - 1)
10356             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10357         else
10358             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10359     } else if ((fromY == 3)
10360                && (toX == fromX)
10361                && gameInfo.variant == VariantBerolina
10362                && (board[fromY][fromX] == BlackPawn)
10363                && (board[toY][toX] == EmptySquare)) {
10364         board[fromY][fromX] = EmptySquare;
10365         board[toY][toX] = BlackPawn;
10366         if(oldEP & EP_BEROLIN_A) {
10367                 captured = board[fromY][fromX-1];
10368                 board[fromY][fromX-1] = EmptySquare;
10369         }else{  captured = board[fromY][fromX+1];
10370                 board[fromY][fromX+1] = EmptySquare;
10371         }
10372     } else {
10373         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10374         board[fromY][fromX] = EmptySquare;
10375         board[toY][toX] = piece;
10376     }
10377   }
10378
10379     if (gameInfo.holdingsWidth != 0) {
10380
10381       /* !!A lot more code needs to be written to support holdings  */
10382       /* [HGM] OK, so I have written it. Holdings are stored in the */
10383       /* penultimate board files, so they are automaticlly stored   */
10384       /* in the game history.                                       */
10385       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10386                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10387         /* Delete from holdings, by decreasing count */
10388         /* and erasing image if necessary            */
10389         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10390         if(p < (int) BlackPawn) { /* white drop */
10391              p -= (int)WhitePawn;
10392                  p = PieceToNumber((ChessSquare)p);
10393              if(p >= gameInfo.holdingsSize) p = 0;
10394              if(--board[p][BOARD_WIDTH-2] <= 0)
10395                   board[p][BOARD_WIDTH-1] = EmptySquare;
10396              if((int)board[p][BOARD_WIDTH-2] < 0)
10397                         board[p][BOARD_WIDTH-2] = 0;
10398         } else {                  /* black drop */
10399              p -= (int)BlackPawn;
10400                  p = PieceToNumber((ChessSquare)p);
10401              if(p >= gameInfo.holdingsSize) p = 0;
10402              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10403                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10404              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10405                         board[BOARD_HEIGHT-1-p][1] = 0;
10406         }
10407       }
10408       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10409           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10410         /* [HGM] holdings: Add to holdings, if holdings exist */
10411         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10412                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10413                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10414         }
10415         p = (int) captured;
10416         if (p >= (int) BlackPawn) {
10417           p -= (int)BlackPawn;
10418           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10419                   /* Restore shogi-promoted piece to its original  first */
10420                   captured = (ChessSquare) (DEMOTED(captured));
10421                   p = DEMOTED(p);
10422           }
10423           p = PieceToNumber((ChessSquare)p);
10424           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10425           board[p][BOARD_WIDTH-2]++;
10426           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10427         } else {
10428           p -= (int)WhitePawn;
10429           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10430                   captured = (ChessSquare) (DEMOTED(captured));
10431                   p = DEMOTED(p);
10432           }
10433           p = PieceToNumber((ChessSquare)p);
10434           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10435           board[BOARD_HEIGHT-1-p][1]++;
10436           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10437         }
10438       }
10439     } else if (gameInfo.variant == VariantAtomic) {
10440       if (captured != EmptySquare) {
10441         int y, x;
10442         for (y = toY-1; y <= toY+1; y++) {
10443           for (x = toX-1; x <= toX+1; x++) {
10444             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10445                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10446               board[y][x] = EmptySquare;
10447             }
10448           }
10449         }
10450         board[toY][toX] = EmptySquare;
10451       }
10452     }
10453
10454     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10455         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10456     } else
10457     if(promoChar == '+') {
10458         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10459         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10460         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10461           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10462     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10463         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10464         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10465            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10466         board[toY][toX] = newPiece;
10467     }
10468     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10469                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10470         // [HGM] superchess: take promotion piece out of holdings
10471         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10472         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10473             if(!--board[k][BOARD_WIDTH-2])
10474                 board[k][BOARD_WIDTH-1] = EmptySquare;
10475         } else {
10476             if(!--board[BOARD_HEIGHT-1-k][1])
10477                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10478         }
10479     }
10480 }
10481
10482 /* Updates forwardMostMove */
10483 void
10484 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10485 {
10486     int x = toX, y = toY;
10487     char *s = parseList[forwardMostMove];
10488     ChessSquare p = boards[forwardMostMove][toY][toX];
10489 //    forwardMostMove++; // [HGM] bare: moved downstream
10490
10491     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10492     (void) CoordsToAlgebraic(boards[forwardMostMove],
10493                              PosFlags(forwardMostMove),
10494                              fromY, fromX, y, x, (killX < 0)*promoChar,
10495                              s);
10496     if(killX >= 0 && killY >= 0)
10497         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0', promoChar);
10498
10499     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10500         int timeLeft; static int lastLoadFlag=0; int king, piece;
10501         piece = boards[forwardMostMove][fromY][fromX];
10502         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10503         if(gameInfo.variant == VariantKnightmate)
10504             king += (int) WhiteUnicorn - (int) WhiteKing;
10505         if(forwardMostMove == 0) {
10506             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10507                 fprintf(serverMoves, "%s;", UserName());
10508             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10509                 fprintf(serverMoves, "%s;", second.tidy);
10510             fprintf(serverMoves, "%s;", first.tidy);
10511             if(gameMode == MachinePlaysWhite)
10512                 fprintf(serverMoves, "%s;", UserName());
10513             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10514                 fprintf(serverMoves, "%s;", second.tidy);
10515         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10516         lastLoadFlag = loadFlag;
10517         // print base move
10518         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10519         // print castling suffix
10520         if( toY == fromY && piece == king ) {
10521             if(toX-fromX > 1)
10522                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10523             if(fromX-toX >1)
10524                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10525         }
10526         // e.p. suffix
10527         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10528              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10529              boards[forwardMostMove][toY][toX] == EmptySquare
10530              && fromX != toX && fromY != toY)
10531                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10532         // promotion suffix
10533         if(promoChar != NULLCHAR) {
10534             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10535                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10536                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10537             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10538         }
10539         if(!loadFlag) {
10540                 char buf[MOVE_LEN*2], *p; int len;
10541             fprintf(serverMoves, "/%d/%d",
10542                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10543             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10544             else                      timeLeft = blackTimeRemaining/1000;
10545             fprintf(serverMoves, "/%d", timeLeft);
10546                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10547                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10548                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10549                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10550             fprintf(serverMoves, "/%s", buf);
10551         }
10552         fflush(serverMoves);
10553     }
10554
10555     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10556         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10557       return;
10558     }
10559     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10560     if (commentList[forwardMostMove+1] != NULL) {
10561         free(commentList[forwardMostMove+1]);
10562         commentList[forwardMostMove+1] = NULL;
10563     }
10564     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10565     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10566     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10567     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10568     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10569     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10570     adjustedClock = FALSE;
10571     gameInfo.result = GameUnfinished;
10572     if (gameInfo.resultDetails != NULL) {
10573         free(gameInfo.resultDetails);
10574         gameInfo.resultDetails = NULL;
10575     }
10576     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10577                               moveList[forwardMostMove - 1]);
10578     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10579       case MT_NONE:
10580       case MT_STALEMATE:
10581       default:
10582         break;
10583       case MT_CHECK:
10584         if(!IS_SHOGI(gameInfo.variant))
10585             strcat(parseList[forwardMostMove - 1], "+");
10586         break;
10587       case MT_CHECKMATE:
10588       case MT_STAINMATE:
10589         strcat(parseList[forwardMostMove - 1], "#");
10590         break;
10591     }
10592 }
10593
10594 /* Updates currentMove if not pausing */
10595 void
10596 ShowMove (int fromX, int fromY, int toX, int toY)
10597 {
10598     int instant = (gameMode == PlayFromGameFile) ?
10599         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10600     if(appData.noGUI) return;
10601     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10602         if (!instant) {
10603             if (forwardMostMove == currentMove + 1) {
10604                 AnimateMove(boards[forwardMostMove - 1],
10605                             fromX, fromY, toX, toY);
10606             }
10607         }
10608         currentMove = forwardMostMove;
10609     }
10610
10611     killX = killY = -1; // [HGM] lion: used up
10612
10613     if (instant) return;
10614
10615     DisplayMove(currentMove - 1);
10616     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10617             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10618                 SetHighlights(fromX, fromY, toX, toY);
10619             }
10620     }
10621     DrawPosition(FALSE, boards[currentMove]);
10622     DisplayBothClocks();
10623     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10624 }
10625
10626 void
10627 SendEgtPath (ChessProgramState *cps)
10628 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10629         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10630
10631         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10632
10633         while(*p) {
10634             char c, *q = name+1, *r, *s;
10635
10636             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10637             while(*p && *p != ',') *q++ = *p++;
10638             *q++ = ':'; *q = 0;
10639             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10640                 strcmp(name, ",nalimov:") == 0 ) {
10641                 // take nalimov path from the menu-changeable option first, if it is defined
10642               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10643                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10644             } else
10645             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10646                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10647                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10648                 s = r = StrStr(s, ":") + 1; // beginning of path info
10649                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10650                 c = *r; *r = 0;             // temporarily null-terminate path info
10651                     *--q = 0;               // strip of trailig ':' from name
10652                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10653                 *r = c;
10654                 SendToProgram(buf,cps);     // send egtbpath command for this format
10655             }
10656             if(*p == ',') p++; // read away comma to position for next format name
10657         }
10658 }
10659
10660 static int
10661 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10662 {
10663       int width = 8, height = 8, holdings = 0;             // most common sizes
10664       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10665       // correct the deviations default for each variant
10666       if( v == VariantXiangqi ) width = 9,  height = 10;
10667       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10668       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10669       if( v == VariantCapablanca || v == VariantCapaRandom ||
10670           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10671                                 width = 10;
10672       if( v == VariantCourier ) width = 12;
10673       if( v == VariantSuper )                            holdings = 8;
10674       if( v == VariantGreat )   width = 10,              holdings = 8;
10675       if( v == VariantSChess )                           holdings = 7;
10676       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10677       if( v == VariantChuChess) width = 10, height = 10;
10678       if( v == VariantChu )     width = 12, height = 12;
10679       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10680              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10681              holdingsSize >= 0 && holdingsSize != holdings;
10682 }
10683
10684 char variantError[MSG_SIZ];
10685
10686 char *
10687 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10688 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10689       char *p, *variant = VariantName(v);
10690       static char b[MSG_SIZ];
10691       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10692            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10693                                                holdingsSize, variant); // cook up sized variant name
10694            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10695            if(StrStr(list, b) == NULL) {
10696                // specific sized variant not known, check if general sizing allowed
10697                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10698                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10699                             boardWidth, boardHeight, holdingsSize, engine);
10700                    return NULL;
10701                }
10702                /* [HGM] here we really should compare with the maximum supported board size */
10703            }
10704       } else snprintf(b, MSG_SIZ,"%s", variant);
10705       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10706       p = StrStr(list, b);
10707       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10708       if(p == NULL) {
10709           // occurs not at all in list, or only as sub-string
10710           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10711           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10712               int l = strlen(variantError);
10713               char *q;
10714               while(p != list && p[-1] != ',') p--;
10715               q = strchr(p, ',');
10716               if(q) *q = NULLCHAR;
10717               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10718               if(q) *q= ',';
10719           }
10720           return NULL;
10721       }
10722       return b;
10723 }
10724
10725 void
10726 InitChessProgram (ChessProgramState *cps, int setup)
10727 /* setup needed to setup FRC opening position */
10728 {
10729     char buf[MSG_SIZ], *b;
10730     if (appData.noChessProgram) return;
10731     hintRequested = FALSE;
10732     bookRequested = FALSE;
10733
10734     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10735     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10736     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10737     if(cps->memSize) { /* [HGM] memory */
10738       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10739         SendToProgram(buf, cps);
10740     }
10741     SendEgtPath(cps); /* [HGM] EGT */
10742     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10743       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10744         SendToProgram(buf, cps);
10745     }
10746
10747     setboardSpoiledMachineBlack = FALSE;
10748     SendToProgram(cps->initString, cps);
10749     if (gameInfo.variant != VariantNormal &&
10750         gameInfo.variant != VariantLoadable
10751         /* [HGM] also send variant if board size non-standard */
10752         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10753
10754       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10755                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10756       if (b == NULL) {
10757         VariantClass v;
10758         char c, *q = cps->variants, *p = strchr(q, ',');
10759         if(p) *p = NULLCHAR;
10760         v = StringToVariant(q);
10761         DisplayError(variantError, 0);
10762         if(v != VariantUnknown && cps == &first) {
10763             int w, h, s;
10764             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10765                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10766             ASSIGN(appData.variant, q);
10767             Reset(TRUE, FALSE);
10768         }
10769         if(p) *p = ',';
10770         return;
10771       }
10772
10773       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10774       SendToProgram(buf, cps);
10775     }
10776     currentlyInitializedVariant = gameInfo.variant;
10777
10778     /* [HGM] send opening position in FRC to first engine */
10779     if(setup) {
10780           SendToProgram("force\n", cps);
10781           SendBoard(cps, 0);
10782           /* engine is now in force mode! Set flag to wake it up after first move. */
10783           setboardSpoiledMachineBlack = 1;
10784     }
10785
10786     if (cps->sendICS) {
10787       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10788       SendToProgram(buf, cps);
10789     }
10790     cps->maybeThinking = FALSE;
10791     cps->offeredDraw = 0;
10792     if (!appData.icsActive) {
10793         SendTimeControl(cps, movesPerSession, timeControl,
10794                         timeIncrement, appData.searchDepth,
10795                         searchTime);
10796     }
10797     if (appData.showThinking
10798         // [HGM] thinking: four options require thinking output to be sent
10799         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10800                                 ) {
10801         SendToProgram("post\n", cps);
10802     }
10803     SendToProgram("hard\n", cps);
10804     if (!appData.ponderNextMove) {
10805         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10806            it without being sure what state we are in first.  "hard"
10807            is not a toggle, so that one is OK.
10808          */
10809         SendToProgram("easy\n", cps);
10810     }
10811     if (cps->usePing) {
10812       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10813       SendToProgram(buf, cps);
10814     }
10815     cps->initDone = TRUE;
10816     ClearEngineOutputPane(cps == &second);
10817 }
10818
10819
10820 void
10821 ResendOptions (ChessProgramState *cps)
10822 { // send the stored value of the options
10823   int i;
10824   char buf[MSG_SIZ];
10825   Option *opt = cps->option;
10826   for(i=0; i<cps->nrOptions; i++, opt++) {
10827       switch(opt->type) {
10828         case Spin:
10829         case Slider:
10830         case CheckBox:
10831             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10832           break;
10833         case ComboBox:
10834           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10835           break;
10836         default:
10837             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10838           break;
10839         case Button:
10840         case SaveButton:
10841           continue;
10842       }
10843       SendToProgram(buf, cps);
10844   }
10845 }
10846
10847 void
10848 StartChessProgram (ChessProgramState *cps)
10849 {
10850     char buf[MSG_SIZ];
10851     int err;
10852
10853     if (appData.noChessProgram) return;
10854     cps->initDone = FALSE;
10855
10856     if (strcmp(cps->host, "localhost") == 0) {
10857         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10858     } else if (*appData.remoteShell == NULLCHAR) {
10859         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10860     } else {
10861         if (*appData.remoteUser == NULLCHAR) {
10862           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10863                     cps->program);
10864         } else {
10865           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10866                     cps->host, appData.remoteUser, cps->program);
10867         }
10868         err = StartChildProcess(buf, "", &cps->pr);
10869     }
10870
10871     if (err != 0) {
10872       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10873         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10874         if(cps != &first) return;
10875         appData.noChessProgram = TRUE;
10876         ThawUI();
10877         SetNCPMode();
10878 //      DisplayFatalError(buf, err, 1);
10879 //      cps->pr = NoProc;
10880 //      cps->isr = NULL;
10881         return;
10882     }
10883
10884     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10885     if (cps->protocolVersion > 1) {
10886       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10887       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10888         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10889         cps->comboCnt = 0;  //                and values of combo boxes
10890       }
10891       SendToProgram(buf, cps);
10892       if(cps->reload) ResendOptions(cps);
10893     } else {
10894       SendToProgram("xboard\n", cps);
10895     }
10896 }
10897
10898 void
10899 TwoMachinesEventIfReady P((void))
10900 {
10901   static int curMess = 0;
10902   if (first.lastPing != first.lastPong) {
10903     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10904     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10905     return;
10906   }
10907   if (second.lastPing != second.lastPong) {
10908     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10909     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10910     return;
10911   }
10912   DisplayMessage("", ""); curMess = 0;
10913   TwoMachinesEvent();
10914 }
10915
10916 char *
10917 MakeName (char *template)
10918 {
10919     time_t clock;
10920     struct tm *tm;
10921     static char buf[MSG_SIZ];
10922     char *p = buf;
10923     int i;
10924
10925     clock = time((time_t *)NULL);
10926     tm = localtime(&clock);
10927
10928     while(*p++ = *template++) if(p[-1] == '%') {
10929         switch(*template++) {
10930           case 0:   *p = 0; return buf;
10931           case 'Y': i = tm->tm_year+1900; break;
10932           case 'y': i = tm->tm_year-100; break;
10933           case 'M': i = tm->tm_mon+1; break;
10934           case 'd': i = tm->tm_mday; break;
10935           case 'h': i = tm->tm_hour; break;
10936           case 'm': i = tm->tm_min; break;
10937           case 's': i = tm->tm_sec; break;
10938           default:  i = 0;
10939         }
10940         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10941     }
10942     return buf;
10943 }
10944
10945 int
10946 CountPlayers (char *p)
10947 {
10948     int n = 0;
10949     while(p = strchr(p, '\n')) p++, n++; // count participants
10950     return n;
10951 }
10952
10953 FILE *
10954 WriteTourneyFile (char *results, FILE *f)
10955 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10956     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10957     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10958         // create a file with tournament description
10959         fprintf(f, "-participants {%s}\n", appData.participants);
10960         fprintf(f, "-seedBase %d\n", appData.seedBase);
10961         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10962         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10963         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10964         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10965         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10966         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10967         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10968         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10969         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10970         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10971         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10972         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10973         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10974         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10975         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10976         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10977         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10978         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10979         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10980         fprintf(f, "-smpCores %d\n", appData.smpCores);
10981         if(searchTime > 0)
10982                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10983         else {
10984                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10985                 fprintf(f, "-tc %s\n", appData.timeControl);
10986                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10987         }
10988         fprintf(f, "-results \"%s\"\n", results);
10989     }
10990     return f;
10991 }
10992
10993 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10994
10995 void
10996 Substitute (char *participants, int expunge)
10997 {
10998     int i, changed, changes=0, nPlayers=0;
10999     char *p, *q, *r, buf[MSG_SIZ];
11000     if(participants == NULL) return;
11001     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11002     r = p = participants; q = appData.participants;
11003     while(*p && *p == *q) {
11004         if(*p == '\n') r = p+1, nPlayers++;
11005         p++; q++;
11006     }
11007     if(*p) { // difference
11008         while(*p && *p++ != '\n');
11009         while(*q && *q++ != '\n');
11010       changed = nPlayers;
11011         changes = 1 + (strcmp(p, q) != 0);
11012     }
11013     if(changes == 1) { // a single engine mnemonic was changed
11014         q = r; while(*q) nPlayers += (*q++ == '\n');
11015         p = buf; while(*r && (*p = *r++) != '\n') p++;
11016         *p = NULLCHAR;
11017         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11018         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11019         if(mnemonic[i]) { // The substitute is valid
11020             FILE *f;
11021             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11022                 flock(fileno(f), LOCK_EX);
11023                 ParseArgsFromFile(f);
11024                 fseek(f, 0, SEEK_SET);
11025                 FREE(appData.participants); appData.participants = participants;
11026                 if(expunge) { // erase results of replaced engine
11027                     int len = strlen(appData.results), w, b, dummy;
11028                     for(i=0; i<len; i++) {
11029                         Pairing(i, nPlayers, &w, &b, &dummy);
11030                         if((w == changed || b == changed) && appData.results[i] == '*') {
11031                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11032                             fclose(f);
11033                             return;
11034                         }
11035                     }
11036                     for(i=0; i<len; i++) {
11037                         Pairing(i, nPlayers, &w, &b, &dummy);
11038                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11039                     }
11040                 }
11041                 WriteTourneyFile(appData.results, f);
11042                 fclose(f); // release lock
11043                 return;
11044             }
11045         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11046     }
11047     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11048     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11049     free(participants);
11050     return;
11051 }
11052
11053 int
11054 CheckPlayers (char *participants)
11055 {
11056         int i;
11057         char buf[MSG_SIZ], *p;
11058         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11059         while(p = strchr(participants, '\n')) {
11060             *p = NULLCHAR;
11061             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11062             if(!mnemonic[i]) {
11063                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11064                 *p = '\n';
11065                 DisplayError(buf, 0);
11066                 return 1;
11067             }
11068             *p = '\n';
11069             participants = p + 1;
11070         }
11071         return 0;
11072 }
11073
11074 int
11075 CreateTourney (char *name)
11076 {
11077         FILE *f;
11078         if(matchMode && strcmp(name, appData.tourneyFile)) {
11079              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11080         }
11081         if(name[0] == NULLCHAR) {
11082             if(appData.participants[0])
11083                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11084             return 0;
11085         }
11086         f = fopen(name, "r");
11087         if(f) { // file exists
11088             ASSIGN(appData.tourneyFile, name);
11089             ParseArgsFromFile(f); // parse it
11090         } else {
11091             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11092             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11093                 DisplayError(_("Not enough participants"), 0);
11094                 return 0;
11095             }
11096             if(CheckPlayers(appData.participants)) return 0;
11097             ASSIGN(appData.tourneyFile, name);
11098             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11099             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11100         }
11101         fclose(f);
11102         appData.noChessProgram = FALSE;
11103         appData.clockMode = TRUE;
11104         SetGNUMode();
11105         return 1;
11106 }
11107
11108 int
11109 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11110 {
11111     char buf[MSG_SIZ], *p, *q;
11112     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11113     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11114     skip = !all && group[0]; // if group requested, we start in skip mode
11115     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11116         p = names; q = buf; header = 0;
11117         while(*p && *p != '\n') *q++ = *p++;
11118         *q = 0;
11119         if(*p == '\n') p++;
11120         if(buf[0] == '#') {
11121             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11122             depth++; // we must be entering a new group
11123             if(all) continue; // suppress printing group headers when complete list requested
11124             header = 1;
11125             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11126         }
11127         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11128         if(engineList[i]) free(engineList[i]);
11129         engineList[i] = strdup(buf);
11130         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11131         if(engineMnemonic[i]) free(engineMnemonic[i]);
11132         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11133             strcat(buf, " (");
11134             sscanf(q + 8, "%s", buf + strlen(buf));
11135             strcat(buf, ")");
11136         }
11137         engineMnemonic[i] = strdup(buf);
11138         i++;
11139     }
11140     engineList[i] = engineMnemonic[i] = NULL;
11141     return i;
11142 }
11143
11144 // following implemented as macro to avoid type limitations
11145 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11146
11147 void
11148 SwapEngines (int n)
11149 {   // swap settings for first engine and other engine (so far only some selected options)
11150     int h;
11151     char *p;
11152     if(n == 0) return;
11153     SWAP(directory, p)
11154     SWAP(chessProgram, p)
11155     SWAP(isUCI, h)
11156     SWAP(hasOwnBookUCI, h)
11157     SWAP(protocolVersion, h)
11158     SWAP(reuse, h)
11159     SWAP(scoreIsAbsolute, h)
11160     SWAP(timeOdds, h)
11161     SWAP(logo, p)
11162     SWAP(pgnName, p)
11163     SWAP(pvSAN, h)
11164     SWAP(engOptions, p)
11165     SWAP(engInitString, p)
11166     SWAP(computerString, p)
11167     SWAP(features, p)
11168     SWAP(fenOverride, p)
11169     SWAP(NPS, h)
11170     SWAP(accumulateTC, h)
11171     SWAP(drawDepth, h)
11172     SWAP(host, p)
11173     SWAP(pseudo, h)
11174 }
11175
11176 int
11177 GetEngineLine (char *s, int n)
11178 {
11179     int i;
11180     char buf[MSG_SIZ];
11181     extern char *icsNames;
11182     if(!s || !*s) return 0;
11183     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11184     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11185     if(!mnemonic[i]) return 0;
11186     if(n == 11) return 1; // just testing if there was a match
11187     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11188     if(n == 1) SwapEngines(n);
11189     ParseArgsFromString(buf);
11190     if(n == 1) SwapEngines(n);
11191     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11192         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11193         ParseArgsFromString(buf);
11194     }
11195     return 1;
11196 }
11197
11198 int
11199 SetPlayer (int player, char *p)
11200 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11201     int i;
11202     char buf[MSG_SIZ], *engineName;
11203     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11204     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11205     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11206     if(mnemonic[i]) {
11207         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11208         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11209         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11210         ParseArgsFromString(buf);
11211     } else { // no engine with this nickname is installed!
11212         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11213         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11214         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11215         ModeHighlight();
11216         DisplayError(buf, 0);
11217         return 0;
11218     }
11219     free(engineName);
11220     return i;
11221 }
11222
11223 char *recentEngines;
11224
11225 void
11226 RecentEngineEvent (int nr)
11227 {
11228     int n;
11229 //    SwapEngines(1); // bump first to second
11230 //    ReplaceEngine(&second, 1); // and load it there
11231     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11232     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11233     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11234         ReplaceEngine(&first, 0);
11235         FloatToFront(&appData.recentEngineList, command[n]);
11236     }
11237 }
11238
11239 int
11240 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11241 {   // determine players from game number
11242     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11243
11244     if(appData.tourneyType == 0) {
11245         roundsPerCycle = (nPlayers - 1) | 1;
11246         pairingsPerRound = nPlayers / 2;
11247     } else if(appData.tourneyType > 0) {
11248         roundsPerCycle = nPlayers - appData.tourneyType;
11249         pairingsPerRound = appData.tourneyType;
11250     }
11251     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11252     gamesPerCycle = gamesPerRound * roundsPerCycle;
11253     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11254     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11255     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11256     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11257     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11258     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11259
11260     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11261     if(appData.roundSync) *syncInterval = gamesPerRound;
11262
11263     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11264
11265     if(appData.tourneyType == 0) {
11266         if(curPairing == (nPlayers-1)/2 ) {
11267             *whitePlayer = curRound;
11268             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11269         } else {
11270             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11271             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11272             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11273             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11274         }
11275     } else if(appData.tourneyType > 1) {
11276         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11277         *whitePlayer = curRound + appData.tourneyType;
11278     } else if(appData.tourneyType > 0) {
11279         *whitePlayer = curPairing;
11280         *blackPlayer = curRound + appData.tourneyType;
11281     }
11282
11283     // take care of white/black alternation per round.
11284     // For cycles and games this is already taken care of by default, derived from matchGame!
11285     return curRound & 1;
11286 }
11287
11288 int
11289 NextTourneyGame (int nr, int *swapColors)
11290 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11291     char *p, *q;
11292     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11293     FILE *tf;
11294     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11295     tf = fopen(appData.tourneyFile, "r");
11296     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11297     ParseArgsFromFile(tf); fclose(tf);
11298     InitTimeControls(); // TC might be altered from tourney file
11299
11300     nPlayers = CountPlayers(appData.participants); // count participants
11301     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11302     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11303
11304     if(syncInterval) {
11305         p = q = appData.results;
11306         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11307         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11308             DisplayMessage(_("Waiting for other game(s)"),"");
11309             waitingForGame = TRUE;
11310             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11311             return 0;
11312         }
11313         waitingForGame = FALSE;
11314     }
11315
11316     if(appData.tourneyType < 0) {
11317         if(nr>=0 && !pairingReceived) {
11318             char buf[1<<16];
11319             if(pairing.pr == NoProc) {
11320                 if(!appData.pairingEngine[0]) {
11321                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11322                     return 0;
11323                 }
11324                 StartChessProgram(&pairing); // starts the pairing engine
11325             }
11326             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11327             SendToProgram(buf, &pairing);
11328             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11329             SendToProgram(buf, &pairing);
11330             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11331         }
11332         pairingReceived = 0;                              // ... so we continue here
11333         *swapColors = 0;
11334         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11335         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11336         matchGame = 1; roundNr = nr / syncInterval + 1;
11337     }
11338
11339     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11340
11341     // redefine engines, engine dir, etc.
11342     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11343     if(first.pr == NoProc) {
11344       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11345       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11346     }
11347     if(second.pr == NoProc) {
11348       SwapEngines(1);
11349       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11350       SwapEngines(1);         // and make that valid for second engine by swapping
11351       InitEngine(&second, 1);
11352     }
11353     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11354     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11355     return OK;
11356 }
11357
11358 void
11359 NextMatchGame ()
11360 {   // performs game initialization that does not invoke engines, and then tries to start the game
11361     int res, firstWhite, swapColors = 0;
11362     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11363     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
11364         char buf[MSG_SIZ];
11365         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11366         if(strcmp(buf, currentDebugFile)) { // name has changed
11367             FILE *f = fopen(buf, "w");
11368             if(f) { // if opening the new file failed, just keep using the old one
11369                 ASSIGN(currentDebugFile, buf);
11370                 fclose(debugFP);
11371                 debugFP = f;
11372             }
11373             if(appData.serverFileName) {
11374                 if(serverFP) fclose(serverFP);
11375                 serverFP = fopen(appData.serverFileName, "w");
11376                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11377                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11378             }
11379         }
11380     }
11381     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11382     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11383     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11384     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11385     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11386     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11387     Reset(FALSE, first.pr != NoProc);
11388     res = LoadGameOrPosition(matchGame); // setup game
11389     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11390     if(!res) return; // abort when bad game/pos file
11391     TwoMachinesEvent();
11392 }
11393
11394 void
11395 UserAdjudicationEvent (int result)
11396 {
11397     ChessMove gameResult = GameIsDrawn;
11398
11399     if( result > 0 ) {
11400         gameResult = WhiteWins;
11401     }
11402     else if( result < 0 ) {
11403         gameResult = BlackWins;
11404     }
11405
11406     if( gameMode == TwoMachinesPlay ) {
11407         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11408     }
11409 }
11410
11411
11412 // [HGM] save: calculate checksum of game to make games easily identifiable
11413 int
11414 StringCheckSum (char *s)
11415 {
11416         int i = 0;
11417         if(s==NULL) return 0;
11418         while(*s) i = i*259 + *s++;
11419         return i;
11420 }
11421
11422 int
11423 GameCheckSum ()
11424 {
11425         int i, sum=0;
11426         for(i=backwardMostMove; i<forwardMostMove; i++) {
11427                 sum += pvInfoList[i].depth;
11428                 sum += StringCheckSum(parseList[i]);
11429                 sum += StringCheckSum(commentList[i]);
11430                 sum *= 261;
11431         }
11432         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11433         return sum + StringCheckSum(commentList[i]);
11434 } // end of save patch
11435
11436 void
11437 GameEnds (ChessMove result, char *resultDetails, int whosays)
11438 {
11439     GameMode nextGameMode;
11440     int isIcsGame;
11441     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11442
11443     if(endingGame) return; /* [HGM] crash: forbid recursion */
11444     endingGame = 1;
11445     if(twoBoards) { // [HGM] dual: switch back to one board
11446         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11447         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11448     }
11449     if (appData.debugMode) {
11450       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11451               result, resultDetails ? resultDetails : "(null)", whosays);
11452     }
11453
11454     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11455
11456     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11457
11458     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11459         /* If we are playing on ICS, the server decides when the
11460            game is over, but the engine can offer to draw, claim
11461            a draw, or resign.
11462          */
11463 #if ZIPPY
11464         if (appData.zippyPlay && first.initDone) {
11465             if (result == GameIsDrawn) {
11466                 /* In case draw still needs to be claimed */
11467                 SendToICS(ics_prefix);
11468                 SendToICS("draw\n");
11469             } else if (StrCaseStr(resultDetails, "resign")) {
11470                 SendToICS(ics_prefix);
11471                 SendToICS("resign\n");
11472             }
11473         }
11474 #endif
11475         endingGame = 0; /* [HGM] crash */
11476         return;
11477     }
11478
11479     /* If we're loading the game from a file, stop */
11480     if (whosays == GE_FILE) {
11481       (void) StopLoadGameTimer();
11482       gameFileFP = NULL;
11483     }
11484
11485     /* Cancel draw offers */
11486     first.offeredDraw = second.offeredDraw = 0;
11487
11488     /* If this is an ICS game, only ICS can really say it's done;
11489        if not, anyone can. */
11490     isIcsGame = (gameMode == IcsPlayingWhite ||
11491                  gameMode == IcsPlayingBlack ||
11492                  gameMode == IcsObserving    ||
11493                  gameMode == IcsExamining);
11494
11495     if (!isIcsGame || whosays == GE_ICS) {
11496         /* OK -- not an ICS game, or ICS said it was done */
11497         StopClocks();
11498         if (!isIcsGame && !appData.noChessProgram)
11499           SetUserThinkingEnables();
11500
11501         /* [HGM] if a machine claims the game end we verify this claim */
11502         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11503             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11504                 char claimer;
11505                 ChessMove trueResult = (ChessMove) -1;
11506
11507                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11508                                             first.twoMachinesColor[0] :
11509                                             second.twoMachinesColor[0] ;
11510
11511                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11512                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11513                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11514                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11515                 } else
11516                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11517                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11518                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11519                 } else
11520                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11521                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11522                 }
11523
11524                 // now verify win claims, but not in drop games, as we don't understand those yet
11525                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11526                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11527                     (result == WhiteWins && claimer == 'w' ||
11528                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11529                       if (appData.debugMode) {
11530                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11531                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11532                       }
11533                       if(result != trueResult) {
11534                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11535                               result = claimer == 'w' ? BlackWins : WhiteWins;
11536                               resultDetails = buf;
11537                       }
11538                 } else
11539                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11540                     && (forwardMostMove <= backwardMostMove ||
11541                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11542                         (claimer=='b')==(forwardMostMove&1))
11543                                                                                   ) {
11544                       /* [HGM] verify: draws that were not flagged are false claims */
11545                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11546                       result = claimer == 'w' ? BlackWins : WhiteWins;
11547                       resultDetails = buf;
11548                 }
11549                 /* (Claiming a loss is accepted no questions asked!) */
11550             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11551                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11552                 result = GameUnfinished;
11553                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11554             }
11555             /* [HGM] bare: don't allow bare King to win */
11556             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11557                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11558                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11559                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11560                && result != GameIsDrawn)
11561             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11562                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11563                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11564                         if(p >= 0 && p <= (int)WhiteKing) k++;
11565                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11566                 }
11567                 if (appData.debugMode) {
11568                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11569                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11570                 }
11571                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11572                         result = GameIsDrawn;
11573                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11574                         resultDetails = buf;
11575                 }
11576             }
11577         }
11578
11579
11580         if(serverMoves != NULL && !loadFlag) { char c = '=';
11581             if(result==WhiteWins) c = '+';
11582             if(result==BlackWins) c = '-';
11583             if(resultDetails != NULL)
11584                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11585         }
11586         if (resultDetails != NULL) {
11587             gameInfo.result = result;
11588             gameInfo.resultDetails = StrSave(resultDetails);
11589
11590             /* display last move only if game was not loaded from file */
11591             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11592                 DisplayMove(currentMove - 1);
11593
11594             if (forwardMostMove != 0) {
11595                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11596                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11597                                                                 ) {
11598                     if (*appData.saveGameFile != NULLCHAR) {
11599                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11600                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11601                         else
11602                         SaveGameToFile(appData.saveGameFile, TRUE);
11603                     } else if (appData.autoSaveGames) {
11604                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11605                     }
11606                     if (*appData.savePositionFile != NULLCHAR) {
11607                         SavePositionToFile(appData.savePositionFile);
11608                     }
11609                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11610                 }
11611             }
11612
11613             /* Tell program how game ended in case it is learning */
11614             /* [HGM] Moved this to after saving the PGN, just in case */
11615             /* engine died and we got here through time loss. In that */
11616             /* case we will get a fatal error writing the pipe, which */
11617             /* would otherwise lose us the PGN.                       */
11618             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11619             /* output during GameEnds should never be fatal anymore   */
11620             if (gameMode == MachinePlaysWhite ||
11621                 gameMode == MachinePlaysBlack ||
11622                 gameMode == TwoMachinesPlay ||
11623                 gameMode == IcsPlayingWhite ||
11624                 gameMode == IcsPlayingBlack ||
11625                 gameMode == BeginningOfGame) {
11626                 char buf[MSG_SIZ];
11627                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11628                         resultDetails);
11629                 if (first.pr != NoProc) {
11630                     SendToProgram(buf, &first);
11631                 }
11632                 if (second.pr != NoProc &&
11633                     gameMode == TwoMachinesPlay) {
11634                     SendToProgram(buf, &second);
11635                 }
11636             }
11637         }
11638
11639         if (appData.icsActive) {
11640             if (appData.quietPlay &&
11641                 (gameMode == IcsPlayingWhite ||
11642                  gameMode == IcsPlayingBlack)) {
11643                 SendToICS(ics_prefix);
11644                 SendToICS("set shout 1\n");
11645             }
11646             nextGameMode = IcsIdle;
11647             ics_user_moved = FALSE;
11648             /* clean up premove.  It's ugly when the game has ended and the
11649              * premove highlights are still on the board.
11650              */
11651             if (gotPremove) {
11652               gotPremove = FALSE;
11653               ClearPremoveHighlights();
11654               DrawPosition(FALSE, boards[currentMove]);
11655             }
11656             if (whosays == GE_ICS) {
11657                 switch (result) {
11658                 case WhiteWins:
11659                     if (gameMode == IcsPlayingWhite)
11660                         PlayIcsWinSound();
11661                     else if(gameMode == IcsPlayingBlack)
11662                         PlayIcsLossSound();
11663                     break;
11664                 case BlackWins:
11665                     if (gameMode == IcsPlayingBlack)
11666                         PlayIcsWinSound();
11667                     else if(gameMode == IcsPlayingWhite)
11668                         PlayIcsLossSound();
11669                     break;
11670                 case GameIsDrawn:
11671                     PlayIcsDrawSound();
11672                     break;
11673                 default:
11674                     PlayIcsUnfinishedSound();
11675                 }
11676             }
11677             if(appData.quitNext) { ExitEvent(0); return; }
11678         } else if (gameMode == EditGame ||
11679                    gameMode == PlayFromGameFile ||
11680                    gameMode == AnalyzeMode ||
11681                    gameMode == AnalyzeFile) {
11682             nextGameMode = gameMode;
11683         } else {
11684             nextGameMode = EndOfGame;
11685         }
11686         pausing = FALSE;
11687         ModeHighlight();
11688     } else {
11689         nextGameMode = gameMode;
11690     }
11691
11692     if (appData.noChessProgram) {
11693         gameMode = nextGameMode;
11694         ModeHighlight();
11695         endingGame = 0; /* [HGM] crash */
11696         return;
11697     }
11698
11699     if (first.reuse) {
11700         /* Put first chess program into idle state */
11701         if (first.pr != NoProc &&
11702             (gameMode == MachinePlaysWhite ||
11703              gameMode == MachinePlaysBlack ||
11704              gameMode == TwoMachinesPlay ||
11705              gameMode == IcsPlayingWhite ||
11706              gameMode == IcsPlayingBlack ||
11707              gameMode == BeginningOfGame)) {
11708             SendToProgram("force\n", &first);
11709             if (first.usePing) {
11710               char buf[MSG_SIZ];
11711               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11712               SendToProgram(buf, &first);
11713             }
11714         }
11715     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11716         /* Kill off first chess program */
11717         if (first.isr != NULL)
11718           RemoveInputSource(first.isr);
11719         first.isr = NULL;
11720
11721         if (first.pr != NoProc) {
11722             ExitAnalyzeMode();
11723             DoSleep( appData.delayBeforeQuit );
11724             SendToProgram("quit\n", &first);
11725             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11726             first.reload = TRUE;
11727         }
11728         first.pr = NoProc;
11729     }
11730     if (second.reuse) {
11731         /* Put second chess program into idle state */
11732         if (second.pr != NoProc &&
11733             gameMode == TwoMachinesPlay) {
11734             SendToProgram("force\n", &second);
11735             if (second.usePing) {
11736               char buf[MSG_SIZ];
11737               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11738               SendToProgram(buf, &second);
11739             }
11740         }
11741     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11742         /* Kill off second chess program */
11743         if (second.isr != NULL)
11744           RemoveInputSource(second.isr);
11745         second.isr = NULL;
11746
11747         if (second.pr != NoProc) {
11748             DoSleep( appData.delayBeforeQuit );
11749             SendToProgram("quit\n", &second);
11750             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11751             second.reload = TRUE;
11752         }
11753         second.pr = NoProc;
11754     }
11755
11756     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11757         char resChar = '=';
11758         switch (result) {
11759         case WhiteWins:
11760           resChar = '+';
11761           if (first.twoMachinesColor[0] == 'w') {
11762             first.matchWins++;
11763           } else {
11764             second.matchWins++;
11765           }
11766           break;
11767         case BlackWins:
11768           resChar = '-';
11769           if (first.twoMachinesColor[0] == 'b') {
11770             first.matchWins++;
11771           } else {
11772             second.matchWins++;
11773           }
11774           break;
11775         case GameUnfinished:
11776           resChar = ' ';
11777         default:
11778           break;
11779         }
11780
11781         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11782         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11783             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11784             ReserveGame(nextGame, resChar); // sets nextGame
11785             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11786             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11787         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11788
11789         if (nextGame <= appData.matchGames && !abortMatch) {
11790             gameMode = nextGameMode;
11791             matchGame = nextGame; // this will be overruled in tourney mode!
11792             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11793             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11794             endingGame = 0; /* [HGM] crash */
11795             return;
11796         } else {
11797             gameMode = nextGameMode;
11798             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11799                      first.tidy, second.tidy,
11800                      first.matchWins, second.matchWins,
11801                      appData.matchGames - (first.matchWins + second.matchWins));
11802             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11803             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11804             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11805             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11806                 first.twoMachinesColor = "black\n";
11807                 second.twoMachinesColor = "white\n";
11808             } else {
11809                 first.twoMachinesColor = "white\n";
11810                 second.twoMachinesColor = "black\n";
11811             }
11812         }
11813     }
11814     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11815         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11816       ExitAnalyzeMode();
11817     gameMode = nextGameMode;
11818     ModeHighlight();
11819     endingGame = 0;  /* [HGM] crash */
11820     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11821         if(matchMode == TRUE) { // match through command line: exit with or without popup
11822             if(ranking) {
11823                 ToNrEvent(forwardMostMove);
11824                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11825                 else ExitEvent(0);
11826             } else DisplayFatalError(buf, 0, 0);
11827         } else { // match through menu; just stop, with or without popup
11828             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11829             ModeHighlight();
11830             if(ranking){
11831                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11832             } else DisplayNote(buf);
11833       }
11834       if(ranking) free(ranking);
11835     }
11836 }
11837
11838 /* Assumes program was just initialized (initString sent).
11839    Leaves program in force mode. */
11840 void
11841 FeedMovesToProgram (ChessProgramState *cps, int upto)
11842 {
11843     int i;
11844
11845     if (appData.debugMode)
11846       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11847               startedFromSetupPosition ? "position and " : "",
11848               backwardMostMove, upto, cps->which);
11849     if(currentlyInitializedVariant != gameInfo.variant) {
11850       char buf[MSG_SIZ];
11851         // [HGM] variantswitch: make engine aware of new variant
11852         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11853                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11854                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11855         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11856         SendToProgram(buf, cps);
11857         currentlyInitializedVariant = gameInfo.variant;
11858     }
11859     SendToProgram("force\n", cps);
11860     if (startedFromSetupPosition) {
11861         SendBoard(cps, backwardMostMove);
11862     if (appData.debugMode) {
11863         fprintf(debugFP, "feedMoves\n");
11864     }
11865     }
11866     for (i = backwardMostMove; i < upto; i++) {
11867         SendMoveToProgram(i, cps);
11868     }
11869 }
11870
11871
11872 int
11873 ResurrectChessProgram ()
11874 {
11875      /* The chess program may have exited.
11876         If so, restart it and feed it all the moves made so far. */
11877     static int doInit = 0;
11878
11879     if (appData.noChessProgram) return 1;
11880
11881     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11882         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11883         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11884         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11885     } else {
11886         if (first.pr != NoProc) return 1;
11887         StartChessProgram(&first);
11888     }
11889     InitChessProgram(&first, FALSE);
11890     FeedMovesToProgram(&first, currentMove);
11891
11892     if (!first.sendTime) {
11893         /* can't tell gnuchess what its clock should read,
11894            so we bow to its notion. */
11895         ResetClocks();
11896         timeRemaining[0][currentMove] = whiteTimeRemaining;
11897         timeRemaining[1][currentMove] = blackTimeRemaining;
11898     }
11899
11900     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11901                 appData.icsEngineAnalyze) && first.analysisSupport) {
11902       SendToProgram("analyze\n", &first);
11903       first.analyzing = TRUE;
11904     }
11905     return 1;
11906 }
11907
11908 /*
11909  * Button procedures
11910  */
11911 void
11912 Reset (int redraw, int init)
11913 {
11914     int i;
11915
11916     if (appData.debugMode) {
11917         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11918                 redraw, init, gameMode);
11919     }
11920     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11921     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11922     CleanupTail(); // [HGM] vari: delete any stored variations
11923     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11924     pausing = pauseExamInvalid = FALSE;
11925     startedFromSetupPosition = blackPlaysFirst = FALSE;
11926     firstMove = TRUE;
11927     whiteFlag = blackFlag = FALSE;
11928     userOfferedDraw = FALSE;
11929     hintRequested = bookRequested = FALSE;
11930     first.maybeThinking = FALSE;
11931     second.maybeThinking = FALSE;
11932     first.bookSuspend = FALSE; // [HGM] book
11933     second.bookSuspend = FALSE;
11934     thinkOutput[0] = NULLCHAR;
11935     lastHint[0] = NULLCHAR;
11936     ClearGameInfo(&gameInfo);
11937     gameInfo.variant = StringToVariant(appData.variant);
11938     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11939     ics_user_moved = ics_clock_paused = FALSE;
11940     ics_getting_history = H_FALSE;
11941     ics_gamenum = -1;
11942     white_holding[0] = black_holding[0] = NULLCHAR;
11943     ClearProgramStats();
11944     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11945
11946     ResetFrontEnd();
11947     ClearHighlights();
11948     flipView = appData.flipView;
11949     ClearPremoveHighlights();
11950     gotPremove = FALSE;
11951     alarmSounded = FALSE;
11952     killX = killY = -1; // [HGM] lion
11953
11954     GameEnds(EndOfFile, NULL, GE_PLAYER);
11955     if(appData.serverMovesName != NULL) {
11956         /* [HGM] prepare to make moves file for broadcasting */
11957         clock_t t = clock();
11958         if(serverMoves != NULL) fclose(serverMoves);
11959         serverMoves = fopen(appData.serverMovesName, "r");
11960         if(serverMoves != NULL) {
11961             fclose(serverMoves);
11962             /* delay 15 sec before overwriting, so all clients can see end */
11963             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11964         }
11965         serverMoves = fopen(appData.serverMovesName, "w");
11966     }
11967
11968     ExitAnalyzeMode();
11969     gameMode = BeginningOfGame;
11970     ModeHighlight();
11971     if(appData.icsActive) gameInfo.variant = VariantNormal;
11972     currentMove = forwardMostMove = backwardMostMove = 0;
11973     MarkTargetSquares(1);
11974     InitPosition(redraw);
11975     for (i = 0; i < MAX_MOVES; i++) {
11976         if (commentList[i] != NULL) {
11977             free(commentList[i]);
11978             commentList[i] = NULL;
11979         }
11980     }
11981     ResetClocks();
11982     timeRemaining[0][0] = whiteTimeRemaining;
11983     timeRemaining[1][0] = blackTimeRemaining;
11984
11985     if (first.pr == NoProc) {
11986         StartChessProgram(&first);
11987     }
11988     if (init) {
11989             InitChessProgram(&first, startedFromSetupPosition);
11990     }
11991     DisplayTitle("");
11992     DisplayMessage("", "");
11993     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11994     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11995     ClearMap();        // [HGM] exclude: invalidate map
11996 }
11997
11998 void
11999 AutoPlayGameLoop ()
12000 {
12001     for (;;) {
12002         if (!AutoPlayOneMove())
12003           return;
12004         if (matchMode || appData.timeDelay == 0)
12005           continue;
12006         if (appData.timeDelay < 0)
12007           return;
12008         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12009         break;
12010     }
12011 }
12012
12013 void
12014 AnalyzeNextGame()
12015 {
12016     ReloadGame(1); // next game
12017 }
12018
12019 int
12020 AutoPlayOneMove ()
12021 {
12022     int fromX, fromY, toX, toY;
12023
12024     if (appData.debugMode) {
12025       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12026     }
12027
12028     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12029       return FALSE;
12030
12031     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12032       pvInfoList[currentMove].depth = programStats.depth;
12033       pvInfoList[currentMove].score = programStats.score;
12034       pvInfoList[currentMove].time  = 0;
12035       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12036       else { // append analysis of final position as comment
12037         char buf[MSG_SIZ];
12038         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12039         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12040       }
12041       programStats.depth = 0;
12042     }
12043
12044     if (currentMove >= forwardMostMove) {
12045       if(gameMode == AnalyzeFile) {
12046           if(appData.loadGameIndex == -1) {
12047             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12048           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12049           } else {
12050           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12051         }
12052       }
12053 //      gameMode = EndOfGame;
12054 //      ModeHighlight();
12055
12056       /* [AS] Clear current move marker at the end of a game */
12057       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12058
12059       return FALSE;
12060     }
12061
12062     toX = moveList[currentMove][2] - AAA;
12063     toY = moveList[currentMove][3] - ONE;
12064
12065     if (moveList[currentMove][1] == '@') {
12066         if (appData.highlightLastMove) {
12067             SetHighlights(-1, -1, toX, toY);
12068         }
12069     } else {
12070         int viaX = moveList[currentMove][5] - AAA;
12071         int viaY = moveList[currentMove][6] - ONE;
12072         fromX = moveList[currentMove][0] - AAA;
12073         fromY = moveList[currentMove][1] - ONE;
12074
12075         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12076
12077         if(moveList[currentMove][4] == ';') { // multi-leg
12078             ChessSquare piece = boards[currentMove][viaY][viaX];
12079             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12080             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12081             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12082             boards[currentMove][viaY][viaX] = piece;
12083         } else
12084         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12085
12086         if (appData.highlightLastMove) {
12087             SetHighlights(fromX, fromY, toX, toY);
12088         }
12089     }
12090     DisplayMove(currentMove);
12091     SendMoveToProgram(currentMove++, &first);
12092     DisplayBothClocks();
12093     DrawPosition(FALSE, boards[currentMove]);
12094     // [HGM] PV info: always display, routine tests if empty
12095     DisplayComment(currentMove - 1, commentList[currentMove]);
12096     return TRUE;
12097 }
12098
12099
12100 int
12101 LoadGameOneMove (ChessMove readAhead)
12102 {
12103     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12104     char promoChar = NULLCHAR;
12105     ChessMove moveType;
12106     char move[MSG_SIZ];
12107     char *p, *q;
12108
12109     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12110         gameMode != AnalyzeMode && gameMode != Training) {
12111         gameFileFP = NULL;
12112         return FALSE;
12113     }
12114
12115     yyboardindex = forwardMostMove;
12116     if (readAhead != EndOfFile) {
12117       moveType = readAhead;
12118     } else {
12119       if (gameFileFP == NULL)
12120           return FALSE;
12121       moveType = (ChessMove) Myylex();
12122     }
12123
12124     done = FALSE;
12125     switch (moveType) {
12126       case Comment:
12127         if (appData.debugMode)
12128           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12129         p = yy_text;
12130
12131         /* append the comment but don't display it */
12132         AppendComment(currentMove, p, FALSE);
12133         return TRUE;
12134
12135       case WhiteCapturesEnPassant:
12136       case BlackCapturesEnPassant:
12137       case WhitePromotion:
12138       case BlackPromotion:
12139       case WhiteNonPromotion:
12140       case BlackNonPromotion:
12141       case NormalMove:
12142       case FirstLeg:
12143       case WhiteKingSideCastle:
12144       case WhiteQueenSideCastle:
12145       case BlackKingSideCastle:
12146       case BlackQueenSideCastle:
12147       case WhiteKingSideCastleWild:
12148       case WhiteQueenSideCastleWild:
12149       case BlackKingSideCastleWild:
12150       case BlackQueenSideCastleWild:
12151       /* PUSH Fabien */
12152       case WhiteHSideCastleFR:
12153       case WhiteASideCastleFR:
12154       case BlackHSideCastleFR:
12155       case BlackASideCastleFR:
12156       /* POP Fabien */
12157         if (appData.debugMode)
12158           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12159         fromX = currentMoveString[0] - AAA;
12160         fromY = currentMoveString[1] - ONE;
12161         toX = currentMoveString[2] - AAA;
12162         toY = currentMoveString[3] - ONE;
12163         promoChar = currentMoveString[4];
12164         if(promoChar == ';') promoChar = currentMoveString[7];
12165         break;
12166
12167       case WhiteDrop:
12168       case BlackDrop:
12169         if (appData.debugMode)
12170           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12171         fromX = moveType == WhiteDrop ?
12172           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12173         (int) CharToPiece(ToLower(currentMoveString[0]));
12174         fromY = DROP_RANK;
12175         toX = currentMoveString[2] - AAA;
12176         toY = currentMoveString[3] - ONE;
12177         break;
12178
12179       case WhiteWins:
12180       case BlackWins:
12181       case GameIsDrawn:
12182       case GameUnfinished:
12183         if (appData.debugMode)
12184           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12185         p = strchr(yy_text, '{');
12186         if (p == NULL) p = strchr(yy_text, '(');
12187         if (p == NULL) {
12188             p = yy_text;
12189             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12190         } else {
12191             q = strchr(p, *p == '{' ? '}' : ')');
12192             if (q != NULL) *q = NULLCHAR;
12193             p++;
12194         }
12195         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12196         GameEnds(moveType, p, GE_FILE);
12197         done = TRUE;
12198         if (cmailMsgLoaded) {
12199             ClearHighlights();
12200             flipView = WhiteOnMove(currentMove);
12201             if (moveType == GameUnfinished) flipView = !flipView;
12202             if (appData.debugMode)
12203               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12204         }
12205         break;
12206
12207       case EndOfFile:
12208         if (appData.debugMode)
12209           fprintf(debugFP, "Parser hit end of file\n");
12210         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12211           case MT_NONE:
12212           case MT_CHECK:
12213             break;
12214           case MT_CHECKMATE:
12215           case MT_STAINMATE:
12216             if (WhiteOnMove(currentMove)) {
12217                 GameEnds(BlackWins, "Black mates", GE_FILE);
12218             } else {
12219                 GameEnds(WhiteWins, "White mates", GE_FILE);
12220             }
12221             break;
12222           case MT_STALEMATE:
12223             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12224             break;
12225         }
12226         done = TRUE;
12227         break;
12228
12229       case MoveNumberOne:
12230         if (lastLoadGameStart == GNUChessGame) {
12231             /* GNUChessGames have numbers, but they aren't move numbers */
12232             if (appData.debugMode)
12233               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12234                       yy_text, (int) moveType);
12235             return LoadGameOneMove(EndOfFile); /* tail recursion */
12236         }
12237         /* else fall thru */
12238
12239       case XBoardGame:
12240       case GNUChessGame:
12241       case PGNTag:
12242         /* Reached start of next game in file */
12243         if (appData.debugMode)
12244           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12245         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12246           case MT_NONE:
12247           case MT_CHECK:
12248             break;
12249           case MT_CHECKMATE:
12250           case MT_STAINMATE:
12251             if (WhiteOnMove(currentMove)) {
12252                 GameEnds(BlackWins, "Black mates", GE_FILE);
12253             } else {
12254                 GameEnds(WhiteWins, "White mates", GE_FILE);
12255             }
12256             break;
12257           case MT_STALEMATE:
12258             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12259             break;
12260         }
12261         done = TRUE;
12262         break;
12263
12264       case PositionDiagram:     /* should not happen; ignore */
12265       case ElapsedTime:         /* ignore */
12266       case NAG:                 /* ignore */
12267         if (appData.debugMode)
12268           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12269                   yy_text, (int) moveType);
12270         return LoadGameOneMove(EndOfFile); /* tail recursion */
12271
12272       case IllegalMove:
12273         if (appData.testLegality) {
12274             if (appData.debugMode)
12275               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12276             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12277                     (forwardMostMove / 2) + 1,
12278                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12279             DisplayError(move, 0);
12280             done = TRUE;
12281         } else {
12282             if (appData.debugMode)
12283               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12284                       yy_text, currentMoveString);
12285             if(currentMoveString[1] == '@') {
12286                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12287                 fromY = DROP_RANK;
12288             } else {
12289                 fromX = currentMoveString[0] - AAA;
12290                 fromY = currentMoveString[1] - ONE;
12291             }
12292             toX = currentMoveString[2] - AAA;
12293             toY = currentMoveString[3] - ONE;
12294             promoChar = currentMoveString[4];
12295         }
12296         break;
12297
12298       case AmbiguousMove:
12299         if (appData.debugMode)
12300           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12301         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12302                 (forwardMostMove / 2) + 1,
12303                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12304         DisplayError(move, 0);
12305         done = TRUE;
12306         break;
12307
12308       default:
12309       case ImpossibleMove:
12310         if (appData.debugMode)
12311           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12312         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12313                 (forwardMostMove / 2) + 1,
12314                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12315         DisplayError(move, 0);
12316         done = TRUE;
12317         break;
12318     }
12319
12320     if (done) {
12321         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12322             DrawPosition(FALSE, boards[currentMove]);
12323             DisplayBothClocks();
12324             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12325               DisplayComment(currentMove - 1, commentList[currentMove]);
12326         }
12327         (void) StopLoadGameTimer();
12328         gameFileFP = NULL;
12329         cmailOldMove = forwardMostMove;
12330         return FALSE;
12331     } else {
12332         /* currentMoveString is set as a side-effect of yylex */
12333
12334         thinkOutput[0] = NULLCHAR;
12335         MakeMove(fromX, fromY, toX, toY, promoChar);
12336         killX = killY = -1; // [HGM] lion: used up
12337         currentMove = forwardMostMove;
12338         return TRUE;
12339     }
12340 }
12341
12342 /* Load the nth game from the given file */
12343 int
12344 LoadGameFromFile (char *filename, int n, char *title, int useList)
12345 {
12346     FILE *f;
12347     char buf[MSG_SIZ];
12348
12349     if (strcmp(filename, "-") == 0) {
12350         f = stdin;
12351         title = "stdin";
12352     } else {
12353         f = fopen(filename, "rb");
12354         if (f == NULL) {
12355           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12356             DisplayError(buf, errno);
12357             return FALSE;
12358         }
12359     }
12360     if (fseek(f, 0, 0) == -1) {
12361         /* f is not seekable; probably a pipe */
12362         useList = FALSE;
12363     }
12364     if (useList && n == 0) {
12365         int error = GameListBuild(f);
12366         if (error) {
12367             DisplayError(_("Cannot build game list"), error);
12368         } else if (!ListEmpty(&gameList) &&
12369                    ((ListGame *) gameList.tailPred)->number > 1) {
12370             GameListPopUp(f, title);
12371             return TRUE;
12372         }
12373         GameListDestroy();
12374         n = 1;
12375     }
12376     if (n == 0) n = 1;
12377     return LoadGame(f, n, title, FALSE);
12378 }
12379
12380
12381 void
12382 MakeRegisteredMove ()
12383 {
12384     int fromX, fromY, toX, toY;
12385     char promoChar;
12386     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12387         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12388           case CMAIL_MOVE:
12389           case CMAIL_DRAW:
12390             if (appData.debugMode)
12391               fprintf(debugFP, "Restoring %s for game %d\n",
12392                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12393
12394             thinkOutput[0] = NULLCHAR;
12395             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12396             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12397             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12398             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12399             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12400             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12401             MakeMove(fromX, fromY, toX, toY, promoChar);
12402             ShowMove(fromX, fromY, toX, toY);
12403
12404             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12405               case MT_NONE:
12406               case MT_CHECK:
12407                 break;
12408
12409               case MT_CHECKMATE:
12410               case MT_STAINMATE:
12411                 if (WhiteOnMove(currentMove)) {
12412                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12413                 } else {
12414                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12415                 }
12416                 break;
12417
12418               case MT_STALEMATE:
12419                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12420                 break;
12421             }
12422
12423             break;
12424
12425           case CMAIL_RESIGN:
12426             if (WhiteOnMove(currentMove)) {
12427                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12428             } else {
12429                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12430             }
12431             break;
12432
12433           case CMAIL_ACCEPT:
12434             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12435             break;
12436
12437           default:
12438             break;
12439         }
12440     }
12441
12442     return;
12443 }
12444
12445 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12446 int
12447 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12448 {
12449     int retVal;
12450
12451     if (gameNumber > nCmailGames) {
12452         DisplayError(_("No more games in this message"), 0);
12453         return FALSE;
12454     }
12455     if (f == lastLoadGameFP) {
12456         int offset = gameNumber - lastLoadGameNumber;
12457         if (offset == 0) {
12458             cmailMsg[0] = NULLCHAR;
12459             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12460                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12461                 nCmailMovesRegistered--;
12462             }
12463             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12464             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12465                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12466             }
12467         } else {
12468             if (! RegisterMove()) return FALSE;
12469         }
12470     }
12471
12472     retVal = LoadGame(f, gameNumber, title, useList);
12473
12474     /* Make move registered during previous look at this game, if any */
12475     MakeRegisteredMove();
12476
12477     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12478         commentList[currentMove]
12479           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12480         DisplayComment(currentMove - 1, commentList[currentMove]);
12481     }
12482
12483     return retVal;
12484 }
12485
12486 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12487 int
12488 ReloadGame (int offset)
12489 {
12490     int gameNumber = lastLoadGameNumber + offset;
12491     if (lastLoadGameFP == NULL) {
12492         DisplayError(_("No game has been loaded yet"), 0);
12493         return FALSE;
12494     }
12495     if (gameNumber <= 0) {
12496         DisplayError(_("Can't back up any further"), 0);
12497         return FALSE;
12498     }
12499     if (cmailMsgLoaded) {
12500         return CmailLoadGame(lastLoadGameFP, gameNumber,
12501                              lastLoadGameTitle, lastLoadGameUseList);
12502     } else {
12503         return LoadGame(lastLoadGameFP, gameNumber,
12504                         lastLoadGameTitle, lastLoadGameUseList);
12505     }
12506 }
12507
12508 int keys[EmptySquare+1];
12509
12510 int
12511 PositionMatches (Board b1, Board b2)
12512 {
12513     int r, f, sum=0;
12514     switch(appData.searchMode) {
12515         case 1: return CompareWithRights(b1, b2);
12516         case 2:
12517             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12518                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12519             }
12520             return TRUE;
12521         case 3:
12522             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12523               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12524                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12525             }
12526             return sum==0;
12527         case 4:
12528             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12529                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12530             }
12531             return sum==0;
12532     }
12533     return TRUE;
12534 }
12535
12536 #define Q_PROMO  4
12537 #define Q_EP     3
12538 #define Q_BCASTL 2
12539 #define Q_WCASTL 1
12540
12541 int pieceList[256], quickBoard[256];
12542 ChessSquare pieceType[256] = { EmptySquare };
12543 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12544 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12545 int soughtTotal, turn;
12546 Boolean epOK, flipSearch;
12547
12548 typedef struct {
12549     unsigned char piece, to;
12550 } Move;
12551
12552 #define DSIZE (250000)
12553
12554 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12555 Move *moveDatabase = initialSpace;
12556 unsigned int movePtr, dataSize = DSIZE;
12557
12558 int
12559 MakePieceList (Board board, int *counts)
12560 {
12561     int r, f, n=Q_PROMO, total=0;
12562     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12563     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12564         int sq = f + (r<<4);
12565         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12566             quickBoard[sq] = ++n;
12567             pieceList[n] = sq;
12568             pieceType[n] = board[r][f];
12569             counts[board[r][f]]++;
12570             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12571             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12572             total++;
12573         }
12574     }
12575     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12576     return total;
12577 }
12578
12579 void
12580 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12581 {
12582     int sq = fromX + (fromY<<4);
12583     int piece = quickBoard[sq], rook;
12584     quickBoard[sq] = 0;
12585     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12586     if(piece == pieceList[1] && fromY == toY) {
12587       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12588         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12589         moveDatabase[movePtr++].piece = Q_WCASTL;
12590         quickBoard[sq] = piece;
12591         piece = quickBoard[from]; quickBoard[from] = 0;
12592         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12593       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12594         quickBoard[sq] = 0; // remove Rook
12595         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12596         moveDatabase[movePtr++].piece = Q_WCASTL;
12597         quickBoard[sq] = pieceList[1]; // put King
12598         piece = rook;
12599         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12600       }
12601     } else
12602     if(piece == pieceList[2] && fromY == toY) {
12603       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12604         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12605         moveDatabase[movePtr++].piece = Q_BCASTL;
12606         quickBoard[sq] = piece;
12607         piece = quickBoard[from]; quickBoard[from] = 0;
12608         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12609       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12610         quickBoard[sq] = 0; // remove Rook
12611         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12612         moveDatabase[movePtr++].piece = Q_BCASTL;
12613         quickBoard[sq] = pieceList[2]; // put King
12614         piece = rook;
12615         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12616       }
12617     } else
12618     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12619         quickBoard[(fromY<<4)+toX] = 0;
12620         moveDatabase[movePtr].piece = Q_EP;
12621         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12622         moveDatabase[movePtr].to = sq;
12623     } else
12624     if(promoPiece != pieceType[piece]) {
12625         moveDatabase[movePtr++].piece = Q_PROMO;
12626         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12627     }
12628     moveDatabase[movePtr].piece = piece;
12629     quickBoard[sq] = piece;
12630     movePtr++;
12631 }
12632
12633 int
12634 PackGame (Board board)
12635 {
12636     Move *newSpace = NULL;
12637     moveDatabase[movePtr].piece = 0; // terminate previous game
12638     if(movePtr > dataSize) {
12639         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12640         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12641         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12642         if(newSpace) {
12643             int i;
12644             Move *p = moveDatabase, *q = newSpace;
12645             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12646             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12647             moveDatabase = newSpace;
12648         } else { // calloc failed, we must be out of memory. Too bad...
12649             dataSize = 0; // prevent calloc events for all subsequent games
12650             return 0;     // and signal this one isn't cached
12651         }
12652     }
12653     movePtr++;
12654     MakePieceList(board, counts);
12655     return movePtr;
12656 }
12657
12658 int
12659 QuickCompare (Board board, int *minCounts, int *maxCounts)
12660 {   // compare according to search mode
12661     int r, f;
12662     switch(appData.searchMode)
12663     {
12664       case 1: // exact position match
12665         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12666         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12667             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12668         }
12669         break;
12670       case 2: // can have extra material on empty squares
12671         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12672             if(board[r][f] == EmptySquare) continue;
12673             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12674         }
12675         break;
12676       case 3: // material with exact Pawn structure
12677         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12678             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12679             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12680         } // fall through to material comparison
12681       case 4: // exact material
12682         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12683         break;
12684       case 6: // material range with given imbalance
12685         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12686         // fall through to range comparison
12687       case 5: // material range
12688         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12689     }
12690     return TRUE;
12691 }
12692
12693 int
12694 QuickScan (Board board, Move *move)
12695 {   // reconstruct game,and compare all positions in it
12696     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12697     do {
12698         int piece = move->piece;
12699         int to = move->to, from = pieceList[piece];
12700         if(found < 0) { // if already found just scan to game end for final piece count
12701           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12702            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12703            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12704                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12705             ) {
12706             static int lastCounts[EmptySquare+1];
12707             int i;
12708             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12709             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12710           } else stretch = 0;
12711           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12712           if(found >= 0 && !appData.minPieces) return found;
12713         }
12714         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12715           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12716           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12717             piece = (++move)->piece;
12718             from = pieceList[piece];
12719             counts[pieceType[piece]]--;
12720             pieceType[piece] = (ChessSquare) move->to;
12721             counts[move->to]++;
12722           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12723             counts[pieceType[quickBoard[to]]]--;
12724             quickBoard[to] = 0; total--;
12725             move++;
12726             continue;
12727           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12728             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12729             from  = pieceList[piece]; // so this must be King
12730             quickBoard[from] = 0;
12731             pieceList[piece] = to;
12732             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12733             quickBoard[from] = 0; // rook
12734             quickBoard[to] = piece;
12735             to = move->to; piece = move->piece;
12736             goto aftercastle;
12737           }
12738         }
12739         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12740         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12741         quickBoard[from] = 0;
12742       aftercastle:
12743         quickBoard[to] = piece;
12744         pieceList[piece] = to;
12745         cnt++; turn ^= 3;
12746         move++;
12747     } while(1);
12748 }
12749
12750 void
12751 InitSearch ()
12752 {
12753     int r, f;
12754     flipSearch = FALSE;
12755     CopyBoard(soughtBoard, boards[currentMove]);
12756     soughtTotal = MakePieceList(soughtBoard, maxSought);
12757     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12758     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12759     CopyBoard(reverseBoard, boards[currentMove]);
12760     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12761         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12762         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12763         reverseBoard[r][f] = piece;
12764     }
12765     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12766     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12767     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12768                  || (boards[currentMove][CASTLING][2] == NoRights ||
12769                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12770                  && (boards[currentMove][CASTLING][5] == NoRights ||
12771                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12772       ) {
12773         flipSearch = TRUE;
12774         CopyBoard(flipBoard, soughtBoard);
12775         CopyBoard(rotateBoard, reverseBoard);
12776         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12777             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12778             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12779         }
12780     }
12781     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12782     if(appData.searchMode >= 5) {
12783         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12784         MakePieceList(soughtBoard, minSought);
12785         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12786     }
12787     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12788         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12789 }
12790
12791 GameInfo dummyInfo;
12792 static int creatingBook;
12793
12794 int
12795 GameContainsPosition (FILE *f, ListGame *lg)
12796 {
12797     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12798     int fromX, fromY, toX, toY;
12799     char promoChar;
12800     static int initDone=FALSE;
12801
12802     // weed out games based on numerical tag comparison
12803     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12804     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12805     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12806     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12807     if(!initDone) {
12808         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12809         initDone = TRUE;
12810     }
12811     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12812     else CopyBoard(boards[scratch], initialPosition); // default start position
12813     if(lg->moves) {
12814         turn = btm + 1;
12815         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12816         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12817     }
12818     if(btm) plyNr++;
12819     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12820     fseek(f, lg->offset, 0);
12821     yynewfile(f);
12822     while(1) {
12823         yyboardindex = scratch;
12824         quickFlag = plyNr+1;
12825         next = Myylex();
12826         quickFlag = 0;
12827         switch(next) {
12828             case PGNTag:
12829                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12830             default:
12831                 continue;
12832
12833             case XBoardGame:
12834             case GNUChessGame:
12835                 if(plyNr) return -1; // after we have seen moves, this is for new game
12836               continue;
12837
12838             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12839             case ImpossibleMove:
12840             case WhiteWins: // game ends here with these four
12841             case BlackWins:
12842             case GameIsDrawn:
12843             case GameUnfinished:
12844                 return -1;
12845
12846             case IllegalMove:
12847                 if(appData.testLegality) return -1;
12848             case WhiteCapturesEnPassant:
12849             case BlackCapturesEnPassant:
12850             case WhitePromotion:
12851             case BlackPromotion:
12852             case WhiteNonPromotion:
12853             case BlackNonPromotion:
12854             case NormalMove:
12855             case FirstLeg:
12856             case WhiteKingSideCastle:
12857             case WhiteQueenSideCastle:
12858             case BlackKingSideCastle:
12859             case BlackQueenSideCastle:
12860             case WhiteKingSideCastleWild:
12861             case WhiteQueenSideCastleWild:
12862             case BlackKingSideCastleWild:
12863             case BlackQueenSideCastleWild:
12864             case WhiteHSideCastleFR:
12865             case WhiteASideCastleFR:
12866             case BlackHSideCastleFR:
12867             case BlackASideCastleFR:
12868                 fromX = currentMoveString[0] - AAA;
12869                 fromY = currentMoveString[1] - ONE;
12870                 toX = currentMoveString[2] - AAA;
12871                 toY = currentMoveString[3] - ONE;
12872                 promoChar = currentMoveString[4];
12873                 break;
12874             case WhiteDrop:
12875             case BlackDrop:
12876                 fromX = next == WhiteDrop ?
12877                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12878                   (int) CharToPiece(ToLower(currentMoveString[0]));
12879                 fromY = DROP_RANK;
12880                 toX = currentMoveString[2] - AAA;
12881                 toY = currentMoveString[3] - ONE;
12882                 promoChar = 0;
12883                 break;
12884         }
12885         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12886         plyNr++;
12887         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12888         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12889         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12890         if(appData.findMirror) {
12891             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12892             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12893         }
12894     }
12895 }
12896
12897 /* Load the nth game from open file f */
12898 int
12899 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12900 {
12901     ChessMove cm;
12902     char buf[MSG_SIZ];
12903     int gn = gameNumber;
12904     ListGame *lg = NULL;
12905     int numPGNTags = 0;
12906     int err, pos = -1;
12907     GameMode oldGameMode;
12908     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12909     char oldName[MSG_SIZ];
12910
12911     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12912
12913     if (appData.debugMode)
12914         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12915
12916     if (gameMode == Training )
12917         SetTrainingModeOff();
12918
12919     oldGameMode = gameMode;
12920     if (gameMode != BeginningOfGame) {
12921       Reset(FALSE, TRUE);
12922     }
12923     killX = killY = -1; // [HGM] lion: in case we did not Reset
12924
12925     gameFileFP = f;
12926     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12927         fclose(lastLoadGameFP);
12928     }
12929
12930     if (useList) {
12931         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12932
12933         if (lg) {
12934             fseek(f, lg->offset, 0);
12935             GameListHighlight(gameNumber);
12936             pos = lg->position;
12937             gn = 1;
12938         }
12939         else {
12940             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12941               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12942             else
12943             DisplayError(_("Game number out of range"), 0);
12944             return FALSE;
12945         }
12946     } else {
12947         GameListDestroy();
12948         if (fseek(f, 0, 0) == -1) {
12949             if (f == lastLoadGameFP ?
12950                 gameNumber == lastLoadGameNumber + 1 :
12951                 gameNumber == 1) {
12952                 gn = 1;
12953             } else {
12954                 DisplayError(_("Can't seek on game file"), 0);
12955                 return FALSE;
12956             }
12957         }
12958     }
12959     lastLoadGameFP = f;
12960     lastLoadGameNumber = gameNumber;
12961     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12962     lastLoadGameUseList = useList;
12963
12964     yynewfile(f);
12965
12966     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12967       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12968                 lg->gameInfo.black);
12969             DisplayTitle(buf);
12970     } else if (*title != NULLCHAR) {
12971         if (gameNumber > 1) {
12972           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12973             DisplayTitle(buf);
12974         } else {
12975             DisplayTitle(title);
12976         }
12977     }
12978
12979     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12980         gameMode = PlayFromGameFile;
12981         ModeHighlight();
12982     }
12983
12984     currentMove = forwardMostMove = backwardMostMove = 0;
12985     CopyBoard(boards[0], initialPosition);
12986     StopClocks();
12987
12988     /*
12989      * Skip the first gn-1 games in the file.
12990      * Also skip over anything that precedes an identifiable
12991      * start of game marker, to avoid being confused by
12992      * garbage at the start of the file.  Currently
12993      * recognized start of game markers are the move number "1",
12994      * the pattern "gnuchess .* game", the pattern
12995      * "^[#;%] [^ ]* game file", and a PGN tag block.
12996      * A game that starts with one of the latter two patterns
12997      * will also have a move number 1, possibly
12998      * following a position diagram.
12999      * 5-4-02: Let's try being more lenient and allowing a game to
13000      * start with an unnumbered move.  Does that break anything?
13001      */
13002     cm = lastLoadGameStart = EndOfFile;
13003     while (gn > 0) {
13004         yyboardindex = forwardMostMove;
13005         cm = (ChessMove) Myylex();
13006         switch (cm) {
13007           case EndOfFile:
13008             if (cmailMsgLoaded) {
13009                 nCmailGames = CMAIL_MAX_GAMES - gn;
13010             } else {
13011                 Reset(TRUE, TRUE);
13012                 DisplayError(_("Game not found in file"), 0);
13013             }
13014             return FALSE;
13015
13016           case GNUChessGame:
13017           case XBoardGame:
13018             gn--;
13019             lastLoadGameStart = cm;
13020             break;
13021
13022           case MoveNumberOne:
13023             switch (lastLoadGameStart) {
13024               case GNUChessGame:
13025               case XBoardGame:
13026               case PGNTag:
13027                 break;
13028               case MoveNumberOne:
13029               case EndOfFile:
13030                 gn--;           /* count this game */
13031                 lastLoadGameStart = cm;
13032                 break;
13033               default:
13034                 /* impossible */
13035                 break;
13036             }
13037             break;
13038
13039           case PGNTag:
13040             switch (lastLoadGameStart) {
13041               case GNUChessGame:
13042               case PGNTag:
13043               case MoveNumberOne:
13044               case EndOfFile:
13045                 gn--;           /* count this game */
13046                 lastLoadGameStart = cm;
13047                 break;
13048               case XBoardGame:
13049                 lastLoadGameStart = cm; /* game counted already */
13050                 break;
13051               default:
13052                 /* impossible */
13053                 break;
13054             }
13055             if (gn > 0) {
13056                 do {
13057                     yyboardindex = forwardMostMove;
13058                     cm = (ChessMove) Myylex();
13059                 } while (cm == PGNTag || cm == Comment);
13060             }
13061             break;
13062
13063           case WhiteWins:
13064           case BlackWins:
13065           case GameIsDrawn:
13066             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13067                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13068                     != CMAIL_OLD_RESULT) {
13069                     nCmailResults ++ ;
13070                     cmailResult[  CMAIL_MAX_GAMES
13071                                 - gn - 1] = CMAIL_OLD_RESULT;
13072                 }
13073             }
13074             break;
13075
13076           case NormalMove:
13077           case FirstLeg:
13078             /* Only a NormalMove can be at the start of a game
13079              * without a position diagram. */
13080             if (lastLoadGameStart == EndOfFile ) {
13081               gn--;
13082               lastLoadGameStart = MoveNumberOne;
13083             }
13084             break;
13085
13086           default:
13087             break;
13088         }
13089     }
13090
13091     if (appData.debugMode)
13092       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13093
13094     if (cm == XBoardGame) {
13095         /* Skip any header junk before position diagram and/or move 1 */
13096         for (;;) {
13097             yyboardindex = forwardMostMove;
13098             cm = (ChessMove) Myylex();
13099
13100             if (cm == EndOfFile ||
13101                 cm == GNUChessGame || cm == XBoardGame) {
13102                 /* Empty game; pretend end-of-file and handle later */
13103                 cm = EndOfFile;
13104                 break;
13105             }
13106
13107             if (cm == MoveNumberOne || cm == PositionDiagram ||
13108                 cm == PGNTag || cm == Comment)
13109               break;
13110         }
13111     } else if (cm == GNUChessGame) {
13112         if (gameInfo.event != NULL) {
13113             free(gameInfo.event);
13114         }
13115         gameInfo.event = StrSave(yy_text);
13116     }
13117
13118     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13119     while (cm == PGNTag) {
13120         if (appData.debugMode)
13121           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13122         err = ParsePGNTag(yy_text, &gameInfo);
13123         if (!err) numPGNTags++;
13124
13125         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13126         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13127             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13128             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13129             InitPosition(TRUE);
13130             oldVariant = gameInfo.variant;
13131             if (appData.debugMode)
13132               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13133         }
13134
13135
13136         if (gameInfo.fen != NULL) {
13137           Board initial_position;
13138           startedFromSetupPosition = TRUE;
13139           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13140             Reset(TRUE, TRUE);
13141             DisplayError(_("Bad FEN position in file"), 0);
13142             return FALSE;
13143           }
13144           CopyBoard(boards[0], initial_position);
13145           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13146             CopyBoard(initialPosition, initial_position);
13147           if (blackPlaysFirst) {
13148             currentMove = forwardMostMove = backwardMostMove = 1;
13149             CopyBoard(boards[1], initial_position);
13150             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13151             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13152             timeRemaining[0][1] = whiteTimeRemaining;
13153             timeRemaining[1][1] = blackTimeRemaining;
13154             if (commentList[0] != NULL) {
13155               commentList[1] = commentList[0];
13156               commentList[0] = NULL;
13157             }
13158           } else {
13159             currentMove = forwardMostMove = backwardMostMove = 0;
13160           }
13161           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13162           {   int i;
13163               initialRulePlies = FENrulePlies;
13164               for( i=0; i< nrCastlingRights; i++ )
13165                   initialRights[i] = initial_position[CASTLING][i];
13166           }
13167           yyboardindex = forwardMostMove;
13168           free(gameInfo.fen);
13169           gameInfo.fen = NULL;
13170         }
13171
13172         yyboardindex = forwardMostMove;
13173         cm = (ChessMove) Myylex();
13174
13175         /* Handle comments interspersed among the tags */
13176         while (cm == Comment) {
13177             char *p;
13178             if (appData.debugMode)
13179               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13180             p = yy_text;
13181             AppendComment(currentMove, p, FALSE);
13182             yyboardindex = forwardMostMove;
13183             cm = (ChessMove) Myylex();
13184         }
13185     }
13186
13187     /* don't rely on existence of Event tag since if game was
13188      * pasted from clipboard the Event tag may not exist
13189      */
13190     if (numPGNTags > 0){
13191         char *tags;
13192         if (gameInfo.variant == VariantNormal) {
13193           VariantClass v = StringToVariant(gameInfo.event);
13194           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13195           if(v < VariantShogi) gameInfo.variant = v;
13196         }
13197         if (!matchMode) {
13198           if( appData.autoDisplayTags ) {
13199             tags = PGNTags(&gameInfo);
13200             TagsPopUp(tags, CmailMsg());
13201             free(tags);
13202           }
13203         }
13204     } else {
13205         /* Make something up, but don't display it now */
13206         SetGameInfo();
13207         TagsPopDown();
13208     }
13209
13210     if (cm == PositionDiagram) {
13211         int i, j;
13212         char *p;
13213         Board initial_position;
13214
13215         if (appData.debugMode)
13216           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13217
13218         if (!startedFromSetupPosition) {
13219             p = yy_text;
13220             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13221               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13222                 switch (*p) {
13223                   case '{':
13224                   case '[':
13225                   case '-':
13226                   case ' ':
13227                   case '\t':
13228                   case '\n':
13229                   case '\r':
13230                     break;
13231                   default:
13232                     initial_position[i][j++] = CharToPiece(*p);
13233                     break;
13234                 }
13235             while (*p == ' ' || *p == '\t' ||
13236                    *p == '\n' || *p == '\r') p++;
13237
13238             if (strncmp(p, "black", strlen("black"))==0)
13239               blackPlaysFirst = TRUE;
13240             else
13241               blackPlaysFirst = FALSE;
13242             startedFromSetupPosition = TRUE;
13243
13244             CopyBoard(boards[0], initial_position);
13245             if (blackPlaysFirst) {
13246                 currentMove = forwardMostMove = backwardMostMove = 1;
13247                 CopyBoard(boards[1], initial_position);
13248                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13249                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13250                 timeRemaining[0][1] = whiteTimeRemaining;
13251                 timeRemaining[1][1] = blackTimeRemaining;
13252                 if (commentList[0] != NULL) {
13253                     commentList[1] = commentList[0];
13254                     commentList[0] = NULL;
13255                 }
13256             } else {
13257                 currentMove = forwardMostMove = backwardMostMove = 0;
13258             }
13259         }
13260         yyboardindex = forwardMostMove;
13261         cm = (ChessMove) Myylex();
13262     }
13263
13264   if(!creatingBook) {
13265     if (first.pr == NoProc) {
13266         StartChessProgram(&first);
13267     }
13268     InitChessProgram(&first, FALSE);
13269     if(gameInfo.variant == VariantUnknown && *oldName) {
13270         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13271         gameInfo.variant = v;
13272     }
13273     SendToProgram("force\n", &first);
13274     if (startedFromSetupPosition) {
13275         SendBoard(&first, forwardMostMove);
13276     if (appData.debugMode) {
13277         fprintf(debugFP, "Load Game\n");
13278     }
13279         DisplayBothClocks();
13280     }
13281   }
13282
13283     /* [HGM] server: flag to write setup moves in broadcast file as one */
13284     loadFlag = appData.suppressLoadMoves;
13285
13286     while (cm == Comment) {
13287         char *p;
13288         if (appData.debugMode)
13289           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13290         p = yy_text;
13291         AppendComment(currentMove, p, FALSE);
13292         yyboardindex = forwardMostMove;
13293         cm = (ChessMove) Myylex();
13294     }
13295
13296     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13297         cm == WhiteWins || cm == BlackWins ||
13298         cm == GameIsDrawn || cm == GameUnfinished) {
13299         DisplayMessage("", _("No moves in game"));
13300         if (cmailMsgLoaded) {
13301             if (appData.debugMode)
13302               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13303             ClearHighlights();
13304             flipView = FALSE;
13305         }
13306         DrawPosition(FALSE, boards[currentMove]);
13307         DisplayBothClocks();
13308         gameMode = EditGame;
13309         ModeHighlight();
13310         gameFileFP = NULL;
13311         cmailOldMove = 0;
13312         return TRUE;
13313     }
13314
13315     // [HGM] PV info: routine tests if comment empty
13316     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13317         DisplayComment(currentMove - 1, commentList[currentMove]);
13318     }
13319     if (!matchMode && appData.timeDelay != 0)
13320       DrawPosition(FALSE, boards[currentMove]);
13321
13322     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13323       programStats.ok_to_send = 1;
13324     }
13325
13326     /* if the first token after the PGN tags is a move
13327      * and not move number 1, retrieve it from the parser
13328      */
13329     if (cm != MoveNumberOne)
13330         LoadGameOneMove(cm);
13331
13332     /* load the remaining moves from the file */
13333     while (LoadGameOneMove(EndOfFile)) {
13334       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13335       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13336     }
13337
13338     /* rewind to the start of the game */
13339     currentMove = backwardMostMove;
13340
13341     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13342
13343     if (oldGameMode == AnalyzeFile) {
13344       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13345       AnalyzeFileEvent();
13346     } else
13347     if (oldGameMode == AnalyzeMode) {
13348       AnalyzeFileEvent();
13349     }
13350
13351     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13352         long int w, b; // [HGM] adjourn: restore saved clock times
13353         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13354         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13355             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13356             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13357         }
13358     }
13359
13360     if(creatingBook) return TRUE;
13361     if (!matchMode && pos > 0) {
13362         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13363     } else
13364     if (matchMode || appData.timeDelay == 0) {
13365       ToEndEvent();
13366     } else if (appData.timeDelay > 0) {
13367       AutoPlayGameLoop();
13368     }
13369
13370     if (appData.debugMode)
13371         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13372
13373     loadFlag = 0; /* [HGM] true game starts */
13374     return TRUE;
13375 }
13376
13377 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13378 int
13379 ReloadPosition (int offset)
13380 {
13381     int positionNumber = lastLoadPositionNumber + offset;
13382     if (lastLoadPositionFP == NULL) {
13383         DisplayError(_("No position has been loaded yet"), 0);
13384         return FALSE;
13385     }
13386     if (positionNumber <= 0) {
13387         DisplayError(_("Can't back up any further"), 0);
13388         return FALSE;
13389     }
13390     return LoadPosition(lastLoadPositionFP, positionNumber,
13391                         lastLoadPositionTitle);
13392 }
13393
13394 /* Load the nth position from the given file */
13395 int
13396 LoadPositionFromFile (char *filename, int n, char *title)
13397 {
13398     FILE *f;
13399     char buf[MSG_SIZ];
13400
13401     if (strcmp(filename, "-") == 0) {
13402         return LoadPosition(stdin, n, "stdin");
13403     } else {
13404         f = fopen(filename, "rb");
13405         if (f == NULL) {
13406             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13407             DisplayError(buf, errno);
13408             return FALSE;
13409         } else {
13410             return LoadPosition(f, n, title);
13411         }
13412     }
13413 }
13414
13415 /* Load the nth position from the given open file, and close it */
13416 int
13417 LoadPosition (FILE *f, int positionNumber, char *title)
13418 {
13419     char *p, line[MSG_SIZ];
13420     Board initial_position;
13421     int i, j, fenMode, pn;
13422
13423     if (gameMode == Training )
13424         SetTrainingModeOff();
13425
13426     if (gameMode != BeginningOfGame) {
13427         Reset(FALSE, TRUE);
13428     }
13429     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13430         fclose(lastLoadPositionFP);
13431     }
13432     if (positionNumber == 0) positionNumber = 1;
13433     lastLoadPositionFP = f;
13434     lastLoadPositionNumber = positionNumber;
13435     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13436     if (first.pr == NoProc && !appData.noChessProgram) {
13437       StartChessProgram(&first);
13438       InitChessProgram(&first, FALSE);
13439     }
13440     pn = positionNumber;
13441     if (positionNumber < 0) {
13442         /* Negative position number means to seek to that byte offset */
13443         if (fseek(f, -positionNumber, 0) == -1) {
13444             DisplayError(_("Can't seek on position file"), 0);
13445             return FALSE;
13446         };
13447         pn = 1;
13448     } else {
13449         if (fseek(f, 0, 0) == -1) {
13450             if (f == lastLoadPositionFP ?
13451                 positionNumber == lastLoadPositionNumber + 1 :
13452                 positionNumber == 1) {
13453                 pn = 1;
13454             } else {
13455                 DisplayError(_("Can't seek on position file"), 0);
13456                 return FALSE;
13457             }
13458         }
13459     }
13460     /* See if this file is FEN or old-style xboard */
13461     if (fgets(line, MSG_SIZ, f) == NULL) {
13462         DisplayError(_("Position not found in file"), 0);
13463         return FALSE;
13464     }
13465     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13466     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13467
13468     if (pn >= 2) {
13469         if (fenMode || line[0] == '#') pn--;
13470         while (pn > 0) {
13471             /* skip positions before number pn */
13472             if (fgets(line, MSG_SIZ, f) == NULL) {
13473                 Reset(TRUE, TRUE);
13474                 DisplayError(_("Position not found in file"), 0);
13475                 return FALSE;
13476             }
13477             if (fenMode || line[0] == '#') pn--;
13478         }
13479     }
13480
13481     if (fenMode) {
13482         char *p;
13483         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13484             DisplayError(_("Bad FEN position in file"), 0);
13485             return FALSE;
13486         }
13487         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13488             sscanf(p+3, "%s", bestMove);
13489         } else *bestMove = NULLCHAR;
13490     } else {
13491         (void) fgets(line, MSG_SIZ, f);
13492         (void) fgets(line, MSG_SIZ, f);
13493
13494         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13495             (void) fgets(line, MSG_SIZ, f);
13496             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13497                 if (*p == ' ')
13498                   continue;
13499                 initial_position[i][j++] = CharToPiece(*p);
13500             }
13501         }
13502
13503         blackPlaysFirst = FALSE;
13504         if (!feof(f)) {
13505             (void) fgets(line, MSG_SIZ, f);
13506             if (strncmp(line, "black", strlen("black"))==0)
13507               blackPlaysFirst = TRUE;
13508         }
13509     }
13510     startedFromSetupPosition = TRUE;
13511
13512     CopyBoard(boards[0], initial_position);
13513     if (blackPlaysFirst) {
13514         currentMove = forwardMostMove = backwardMostMove = 1;
13515         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13516         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13517         CopyBoard(boards[1], initial_position);
13518         DisplayMessage("", _("Black to play"));
13519     } else {
13520         currentMove = forwardMostMove = backwardMostMove = 0;
13521         DisplayMessage("", _("White to play"));
13522     }
13523     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13524     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13525         SendToProgram("force\n", &first);
13526         SendBoard(&first, forwardMostMove);
13527     }
13528     if (appData.debugMode) {
13529 int i, j;
13530   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13531   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13532         fprintf(debugFP, "Load Position\n");
13533     }
13534
13535     if (positionNumber > 1) {
13536       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13537         DisplayTitle(line);
13538     } else {
13539         DisplayTitle(title);
13540     }
13541     gameMode = EditGame;
13542     ModeHighlight();
13543     ResetClocks();
13544     timeRemaining[0][1] = whiteTimeRemaining;
13545     timeRemaining[1][1] = blackTimeRemaining;
13546     DrawPosition(FALSE, boards[currentMove]);
13547
13548     return TRUE;
13549 }
13550
13551
13552 void
13553 CopyPlayerNameIntoFileName (char **dest, char *src)
13554 {
13555     while (*src != NULLCHAR && *src != ',') {
13556         if (*src == ' ') {
13557             *(*dest)++ = '_';
13558             src++;
13559         } else {
13560             *(*dest)++ = *src++;
13561         }
13562     }
13563 }
13564
13565 char *
13566 DefaultFileName (char *ext)
13567 {
13568     static char def[MSG_SIZ];
13569     char *p;
13570
13571     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13572         p = def;
13573         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13574         *p++ = '-';
13575         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13576         *p++ = '.';
13577         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13578     } else {
13579         def[0] = NULLCHAR;
13580     }
13581     return def;
13582 }
13583
13584 /* Save the current game to the given file */
13585 int
13586 SaveGameToFile (char *filename, int append)
13587 {
13588     FILE *f;
13589     char buf[MSG_SIZ];
13590     int result, i, t,tot=0;
13591
13592     if (strcmp(filename, "-") == 0) {
13593         return SaveGame(stdout, 0, NULL);
13594     } else {
13595         for(i=0; i<10; i++) { // upto 10 tries
13596              f = fopen(filename, append ? "a" : "w");
13597              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13598              if(f || errno != 13) break;
13599              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13600              tot += t;
13601         }
13602         if (f == NULL) {
13603             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13604             DisplayError(buf, errno);
13605             return FALSE;
13606         } else {
13607             safeStrCpy(buf, lastMsg, MSG_SIZ);
13608             DisplayMessage(_("Waiting for access to save file"), "");
13609             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13610             DisplayMessage(_("Saving game"), "");
13611             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13612             result = SaveGame(f, 0, NULL);
13613             DisplayMessage(buf, "");
13614             return result;
13615         }
13616     }
13617 }
13618
13619 char *
13620 SavePart (char *str)
13621 {
13622     static char buf[MSG_SIZ];
13623     char *p;
13624
13625     p = strchr(str, ' ');
13626     if (p == NULL) return str;
13627     strncpy(buf, str, p - str);
13628     buf[p - str] = NULLCHAR;
13629     return buf;
13630 }
13631
13632 #define PGN_MAX_LINE 75
13633
13634 #define PGN_SIDE_WHITE  0
13635 #define PGN_SIDE_BLACK  1
13636
13637 static int
13638 FindFirstMoveOutOfBook (int side)
13639 {
13640     int result = -1;
13641
13642     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13643         int index = backwardMostMove;
13644         int has_book_hit = 0;
13645
13646         if( (index % 2) != side ) {
13647             index++;
13648         }
13649
13650         while( index < forwardMostMove ) {
13651             /* Check to see if engine is in book */
13652             int depth = pvInfoList[index].depth;
13653             int score = pvInfoList[index].score;
13654             int in_book = 0;
13655
13656             if( depth <= 2 ) {
13657                 in_book = 1;
13658             }
13659             else if( score == 0 && depth == 63 ) {
13660                 in_book = 1; /* Zappa */
13661             }
13662             else if( score == 2 && depth == 99 ) {
13663                 in_book = 1; /* Abrok */
13664             }
13665
13666             has_book_hit += in_book;
13667
13668             if( ! in_book ) {
13669                 result = index;
13670
13671                 break;
13672             }
13673
13674             index += 2;
13675         }
13676     }
13677
13678     return result;
13679 }
13680
13681 void
13682 GetOutOfBookInfo (char * buf)
13683 {
13684     int oob[2];
13685     int i;
13686     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13687
13688     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13689     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13690
13691     *buf = '\0';
13692
13693     if( oob[0] >= 0 || oob[1] >= 0 ) {
13694         for( i=0; i<2; i++ ) {
13695             int idx = oob[i];
13696
13697             if( idx >= 0 ) {
13698                 if( i > 0 && oob[0] >= 0 ) {
13699                     strcat( buf, "   " );
13700                 }
13701
13702                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13703                 sprintf( buf+strlen(buf), "%s%.2f",
13704                     pvInfoList[idx].score >= 0 ? "+" : "",
13705                     pvInfoList[idx].score / 100.0 );
13706             }
13707         }
13708     }
13709 }
13710
13711 /* Save game in PGN style */
13712 static void
13713 SaveGamePGN2 (FILE *f)
13714 {
13715     int i, offset, linelen, newblock;
13716 //    char *movetext;
13717     char numtext[32];
13718     int movelen, numlen, blank;
13719     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13720
13721     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13722
13723     PrintPGNTags(f, &gameInfo);
13724
13725     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13726
13727     if (backwardMostMove > 0 || startedFromSetupPosition) {
13728         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13729         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13730         fprintf(f, "\n{--------------\n");
13731         PrintPosition(f, backwardMostMove);
13732         fprintf(f, "--------------}\n");
13733         free(fen);
13734     }
13735     else {
13736         /* [AS] Out of book annotation */
13737         if( appData.saveOutOfBookInfo ) {
13738             char buf[64];
13739
13740             GetOutOfBookInfo( buf );
13741
13742             if( buf[0] != '\0' ) {
13743                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13744             }
13745         }
13746
13747         fprintf(f, "\n");
13748     }
13749
13750     i = backwardMostMove;
13751     linelen = 0;
13752     newblock = TRUE;
13753
13754     while (i < forwardMostMove) {
13755         /* Print comments preceding this move */
13756         if (commentList[i] != NULL) {
13757             if (linelen > 0) fprintf(f, "\n");
13758             fprintf(f, "%s", commentList[i]);
13759             linelen = 0;
13760             newblock = TRUE;
13761         }
13762
13763         /* Format move number */
13764         if ((i % 2) == 0)
13765           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13766         else
13767           if (newblock)
13768             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13769           else
13770             numtext[0] = NULLCHAR;
13771
13772         numlen = strlen(numtext);
13773         newblock = FALSE;
13774
13775         /* Print move number */
13776         blank = linelen > 0 && numlen > 0;
13777         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13778             fprintf(f, "\n");
13779             linelen = 0;
13780             blank = 0;
13781         }
13782         if (blank) {
13783             fprintf(f, " ");
13784             linelen++;
13785         }
13786         fprintf(f, "%s", numtext);
13787         linelen += numlen;
13788
13789         /* Get move */
13790         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13791         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13792
13793         /* Print move */
13794         blank = linelen > 0 && movelen > 0;
13795         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13796             fprintf(f, "\n");
13797             linelen = 0;
13798             blank = 0;
13799         }
13800         if (blank) {
13801             fprintf(f, " ");
13802             linelen++;
13803         }
13804         fprintf(f, "%s", move_buffer);
13805         linelen += movelen;
13806
13807         /* [AS] Add PV info if present */
13808         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13809             /* [HGM] add time */
13810             char buf[MSG_SIZ]; int seconds;
13811
13812             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13813
13814             if( seconds <= 0)
13815               buf[0] = 0;
13816             else
13817               if( seconds < 30 )
13818                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13819               else
13820                 {
13821                   seconds = (seconds + 4)/10; // round to full seconds
13822                   if( seconds < 60 )
13823                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13824                   else
13825                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13826                 }
13827
13828             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13829                       pvInfoList[i].score >= 0 ? "+" : "",
13830                       pvInfoList[i].score / 100.0,
13831                       pvInfoList[i].depth,
13832                       buf );
13833
13834             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13835
13836             /* Print score/depth */
13837             blank = linelen > 0 && movelen > 0;
13838             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13839                 fprintf(f, "\n");
13840                 linelen = 0;
13841                 blank = 0;
13842             }
13843             if (blank) {
13844                 fprintf(f, " ");
13845                 linelen++;
13846             }
13847             fprintf(f, "%s", move_buffer);
13848             linelen += movelen;
13849         }
13850
13851         i++;
13852     }
13853
13854     /* Start a new line */
13855     if (linelen > 0) fprintf(f, "\n");
13856
13857     /* Print comments after last move */
13858     if (commentList[i] != NULL) {
13859         fprintf(f, "%s\n", commentList[i]);
13860     }
13861
13862     /* Print result */
13863     if (gameInfo.resultDetails != NULL &&
13864         gameInfo.resultDetails[0] != NULLCHAR) {
13865         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13866         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13867            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13868             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13869         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13870     } else {
13871         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13872     }
13873 }
13874
13875 /* Save game in PGN style and close the file */
13876 int
13877 SaveGamePGN (FILE *f)
13878 {
13879     SaveGamePGN2(f);
13880     fclose(f);
13881     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13882     return TRUE;
13883 }
13884
13885 /* Save game in old style and close the file */
13886 int
13887 SaveGameOldStyle (FILE *f)
13888 {
13889     int i, offset;
13890     time_t tm;
13891
13892     tm = time((time_t *) NULL);
13893
13894     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13895     PrintOpponents(f);
13896
13897     if (backwardMostMove > 0 || startedFromSetupPosition) {
13898         fprintf(f, "\n[--------------\n");
13899         PrintPosition(f, backwardMostMove);
13900         fprintf(f, "--------------]\n");
13901     } else {
13902         fprintf(f, "\n");
13903     }
13904
13905     i = backwardMostMove;
13906     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13907
13908     while (i < forwardMostMove) {
13909         if (commentList[i] != NULL) {
13910             fprintf(f, "[%s]\n", commentList[i]);
13911         }
13912
13913         if ((i % 2) == 1) {
13914             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13915             i++;
13916         } else {
13917             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13918             i++;
13919             if (commentList[i] != NULL) {
13920                 fprintf(f, "\n");
13921                 continue;
13922             }
13923             if (i >= forwardMostMove) {
13924                 fprintf(f, "\n");
13925                 break;
13926             }
13927             fprintf(f, "%s\n", parseList[i]);
13928             i++;
13929         }
13930     }
13931
13932     if (commentList[i] != NULL) {
13933         fprintf(f, "[%s]\n", commentList[i]);
13934     }
13935
13936     /* This isn't really the old style, but it's close enough */
13937     if (gameInfo.resultDetails != NULL &&
13938         gameInfo.resultDetails[0] != NULLCHAR) {
13939         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13940                 gameInfo.resultDetails);
13941     } else {
13942         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13943     }
13944
13945     fclose(f);
13946     return TRUE;
13947 }
13948
13949 /* Save the current game to open file f and close the file */
13950 int
13951 SaveGame (FILE *f, int dummy, char *dummy2)
13952 {
13953     if (gameMode == EditPosition) EditPositionDone(TRUE);
13954     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13955     if (appData.oldSaveStyle)
13956       return SaveGameOldStyle(f);
13957     else
13958       return SaveGamePGN(f);
13959 }
13960
13961 /* Save the current position to the given file */
13962 int
13963 SavePositionToFile (char *filename)
13964 {
13965     FILE *f;
13966     char buf[MSG_SIZ];
13967
13968     if (strcmp(filename, "-") == 0) {
13969         return SavePosition(stdout, 0, NULL);
13970     } else {
13971         f = fopen(filename, "a");
13972         if (f == NULL) {
13973             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13974             DisplayError(buf, errno);
13975             return FALSE;
13976         } else {
13977             safeStrCpy(buf, lastMsg, MSG_SIZ);
13978             DisplayMessage(_("Waiting for access to save file"), "");
13979             flock(fileno(f), LOCK_EX); // [HGM] lock
13980             DisplayMessage(_("Saving position"), "");
13981             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13982             SavePosition(f, 0, NULL);
13983             DisplayMessage(buf, "");
13984             return TRUE;
13985         }
13986     }
13987 }
13988
13989 /* Save the current position to the given open file and close the file */
13990 int
13991 SavePosition (FILE *f, int dummy, char *dummy2)
13992 {
13993     time_t tm;
13994     char *fen;
13995
13996     if (gameMode == EditPosition) EditPositionDone(TRUE);
13997     if (appData.oldSaveStyle) {
13998         tm = time((time_t *) NULL);
13999
14000         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14001         PrintOpponents(f);
14002         fprintf(f, "[--------------\n");
14003         PrintPosition(f, currentMove);
14004         fprintf(f, "--------------]\n");
14005     } else {
14006         fen = PositionToFEN(currentMove, NULL, 1);
14007         fprintf(f, "%s\n", fen);
14008         free(fen);
14009     }
14010     fclose(f);
14011     return TRUE;
14012 }
14013
14014 void
14015 ReloadCmailMsgEvent (int unregister)
14016 {
14017 #if !WIN32
14018     static char *inFilename = NULL;
14019     static char *outFilename;
14020     int i;
14021     struct stat inbuf, outbuf;
14022     int status;
14023
14024     /* Any registered moves are unregistered if unregister is set, */
14025     /* i.e. invoked by the signal handler */
14026     if (unregister) {
14027         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14028             cmailMoveRegistered[i] = FALSE;
14029             if (cmailCommentList[i] != NULL) {
14030                 free(cmailCommentList[i]);
14031                 cmailCommentList[i] = NULL;
14032             }
14033         }
14034         nCmailMovesRegistered = 0;
14035     }
14036
14037     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14038         cmailResult[i] = CMAIL_NOT_RESULT;
14039     }
14040     nCmailResults = 0;
14041
14042     if (inFilename == NULL) {
14043         /* Because the filenames are static they only get malloced once  */
14044         /* and they never get freed                                      */
14045         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14046         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14047
14048         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14049         sprintf(outFilename, "%s.out", appData.cmailGameName);
14050     }
14051
14052     status = stat(outFilename, &outbuf);
14053     if (status < 0) {
14054         cmailMailedMove = FALSE;
14055     } else {
14056         status = stat(inFilename, &inbuf);
14057         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14058     }
14059
14060     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14061        counts the games, notes how each one terminated, etc.
14062
14063        It would be nice to remove this kludge and instead gather all
14064        the information while building the game list.  (And to keep it
14065        in the game list nodes instead of having a bunch of fixed-size
14066        parallel arrays.)  Note this will require getting each game's
14067        termination from the PGN tags, as the game list builder does
14068        not process the game moves.  --mann
14069        */
14070     cmailMsgLoaded = TRUE;
14071     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14072
14073     /* Load first game in the file or popup game menu */
14074     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14075
14076 #endif /* !WIN32 */
14077     return;
14078 }
14079
14080 int
14081 RegisterMove ()
14082 {
14083     FILE *f;
14084     char string[MSG_SIZ];
14085
14086     if (   cmailMailedMove
14087         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14088         return TRUE;            /* Allow free viewing  */
14089     }
14090
14091     /* Unregister move to ensure that we don't leave RegisterMove        */
14092     /* with the move registered when the conditions for registering no   */
14093     /* longer hold                                                       */
14094     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14095         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14096         nCmailMovesRegistered --;
14097
14098         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14099           {
14100               free(cmailCommentList[lastLoadGameNumber - 1]);
14101               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14102           }
14103     }
14104
14105     if (cmailOldMove == -1) {
14106         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14107         return FALSE;
14108     }
14109
14110     if (currentMove > cmailOldMove + 1) {
14111         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14112         return FALSE;
14113     }
14114
14115     if (currentMove < cmailOldMove) {
14116         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14117         return FALSE;
14118     }
14119
14120     if (forwardMostMove > currentMove) {
14121         /* Silently truncate extra moves */
14122         TruncateGame();
14123     }
14124
14125     if (   (currentMove == cmailOldMove + 1)
14126         || (   (currentMove == cmailOldMove)
14127             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14128                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14129         if (gameInfo.result != GameUnfinished) {
14130             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14131         }
14132
14133         if (commentList[currentMove] != NULL) {
14134             cmailCommentList[lastLoadGameNumber - 1]
14135               = StrSave(commentList[currentMove]);
14136         }
14137         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14138
14139         if (appData.debugMode)
14140           fprintf(debugFP, "Saving %s for game %d\n",
14141                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14142
14143         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14144
14145         f = fopen(string, "w");
14146         if (appData.oldSaveStyle) {
14147             SaveGameOldStyle(f); /* also closes the file */
14148
14149             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14150             f = fopen(string, "w");
14151             SavePosition(f, 0, NULL); /* also closes the file */
14152         } else {
14153             fprintf(f, "{--------------\n");
14154             PrintPosition(f, currentMove);
14155             fprintf(f, "--------------}\n\n");
14156
14157             SaveGame(f, 0, NULL); /* also closes the file*/
14158         }
14159
14160         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14161         nCmailMovesRegistered ++;
14162     } else if (nCmailGames == 1) {
14163         DisplayError(_("You have not made a move yet"), 0);
14164         return FALSE;
14165     }
14166
14167     return TRUE;
14168 }
14169
14170 void
14171 MailMoveEvent ()
14172 {
14173 #if !WIN32
14174     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14175     FILE *commandOutput;
14176     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14177     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14178     int nBuffers;
14179     int i;
14180     int archived;
14181     char *arcDir;
14182
14183     if (! cmailMsgLoaded) {
14184         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14185         return;
14186     }
14187
14188     if (nCmailGames == nCmailResults) {
14189         DisplayError(_("No unfinished games"), 0);
14190         return;
14191     }
14192
14193 #if CMAIL_PROHIBIT_REMAIL
14194     if (cmailMailedMove) {
14195       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);
14196         DisplayError(msg, 0);
14197         return;
14198     }
14199 #endif
14200
14201     if (! (cmailMailedMove || RegisterMove())) return;
14202
14203     if (   cmailMailedMove
14204         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14205       snprintf(string, MSG_SIZ, partCommandString,
14206                appData.debugMode ? " -v" : "", appData.cmailGameName);
14207         commandOutput = popen(string, "r");
14208
14209         if (commandOutput == NULL) {
14210             DisplayError(_("Failed to invoke cmail"), 0);
14211         } else {
14212             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14213                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14214             }
14215             if (nBuffers > 1) {
14216                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14217                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14218                 nBytes = MSG_SIZ - 1;
14219             } else {
14220                 (void) memcpy(msg, buffer, nBytes);
14221             }
14222             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14223
14224             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14225                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14226
14227                 archived = TRUE;
14228                 for (i = 0; i < nCmailGames; i ++) {
14229                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14230                         archived = FALSE;
14231                     }
14232                 }
14233                 if (   archived
14234                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14235                         != NULL)) {
14236                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14237                            arcDir,
14238                            appData.cmailGameName,
14239                            gameInfo.date);
14240                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14241                     cmailMsgLoaded = FALSE;
14242                 }
14243             }
14244
14245             DisplayInformation(msg);
14246             pclose(commandOutput);
14247         }
14248     } else {
14249         if ((*cmailMsg) != '\0') {
14250             DisplayInformation(cmailMsg);
14251         }
14252     }
14253
14254     return;
14255 #endif /* !WIN32 */
14256 }
14257
14258 char *
14259 CmailMsg ()
14260 {
14261 #if WIN32
14262     return NULL;
14263 #else
14264     int  prependComma = 0;
14265     char number[5];
14266     char string[MSG_SIZ];       /* Space for game-list */
14267     int  i;
14268
14269     if (!cmailMsgLoaded) return "";
14270
14271     if (cmailMailedMove) {
14272       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14273     } else {
14274         /* Create a list of games left */
14275       snprintf(string, MSG_SIZ, "[");
14276         for (i = 0; i < nCmailGames; i ++) {
14277             if (! (   cmailMoveRegistered[i]
14278                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14279                 if (prependComma) {
14280                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14281                 } else {
14282                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14283                     prependComma = 1;
14284                 }
14285
14286                 strcat(string, number);
14287             }
14288         }
14289         strcat(string, "]");
14290
14291         if (nCmailMovesRegistered + nCmailResults == 0) {
14292             switch (nCmailGames) {
14293               case 1:
14294                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14295                 break;
14296
14297               case 2:
14298                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14299                 break;
14300
14301               default:
14302                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14303                          nCmailGames);
14304                 break;
14305             }
14306         } else {
14307             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14308               case 1:
14309                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14310                          string);
14311                 break;
14312
14313               case 0:
14314                 if (nCmailResults == nCmailGames) {
14315                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14316                 } else {
14317                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14318                 }
14319                 break;
14320
14321               default:
14322                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14323                          string);
14324             }
14325         }
14326     }
14327     return cmailMsg;
14328 #endif /* WIN32 */
14329 }
14330
14331 void
14332 ResetGameEvent ()
14333 {
14334     if (gameMode == Training)
14335       SetTrainingModeOff();
14336
14337     Reset(TRUE, TRUE);
14338     cmailMsgLoaded = FALSE;
14339     if (appData.icsActive) {
14340       SendToICS(ics_prefix);
14341       SendToICS("refresh\n");
14342     }
14343 }
14344
14345 void
14346 ExitEvent (int status)
14347 {
14348     exiting++;
14349     if (exiting > 2) {
14350       /* Give up on clean exit */
14351       exit(status);
14352     }
14353     if (exiting > 1) {
14354       /* Keep trying for clean exit */
14355       return;
14356     }
14357
14358     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14359     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14360
14361     if (telnetISR != NULL) {
14362       RemoveInputSource(telnetISR);
14363     }
14364     if (icsPR != NoProc) {
14365       DestroyChildProcess(icsPR, TRUE);
14366     }
14367
14368     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14369     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14370
14371     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14372     /* make sure this other one finishes before killing it!                  */
14373     if(endingGame) { int count = 0;
14374         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14375         while(endingGame && count++ < 10) DoSleep(1);
14376         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14377     }
14378
14379     /* Kill off chess programs */
14380     if (first.pr != NoProc) {
14381         ExitAnalyzeMode();
14382
14383         DoSleep( appData.delayBeforeQuit );
14384         SendToProgram("quit\n", &first);
14385         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14386     }
14387     if (second.pr != NoProc) {
14388         DoSleep( appData.delayBeforeQuit );
14389         SendToProgram("quit\n", &second);
14390         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14391     }
14392     if (first.isr != NULL) {
14393         RemoveInputSource(first.isr);
14394     }
14395     if (second.isr != NULL) {
14396         RemoveInputSource(second.isr);
14397     }
14398
14399     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14400     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14401
14402     ShutDownFrontEnd();
14403     exit(status);
14404 }
14405
14406 void
14407 PauseEngine (ChessProgramState *cps)
14408 {
14409     SendToProgram("pause\n", cps);
14410     cps->pause = 2;
14411 }
14412
14413 void
14414 UnPauseEngine (ChessProgramState *cps)
14415 {
14416     SendToProgram("resume\n", cps);
14417     cps->pause = 1;
14418 }
14419
14420 void
14421 PauseEvent ()
14422 {
14423     if (appData.debugMode)
14424         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14425     if (pausing) {
14426         pausing = FALSE;
14427         ModeHighlight();
14428         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14429             StartClocks();
14430             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14431                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14432                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14433             }
14434             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14435             HandleMachineMove(stashedInputMove, stalledEngine);
14436             stalledEngine = NULL;
14437             return;
14438         }
14439         if (gameMode == MachinePlaysWhite ||
14440             gameMode == TwoMachinesPlay   ||
14441             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14442             if(first.pause)  UnPauseEngine(&first);
14443             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14444             if(second.pause) UnPauseEngine(&second);
14445             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14446             StartClocks();
14447         } else {
14448             DisplayBothClocks();
14449         }
14450         if (gameMode == PlayFromGameFile) {
14451             if (appData.timeDelay >= 0)
14452                 AutoPlayGameLoop();
14453         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14454             Reset(FALSE, TRUE);
14455             SendToICS(ics_prefix);
14456             SendToICS("refresh\n");
14457         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14458             ForwardInner(forwardMostMove);
14459         }
14460         pauseExamInvalid = FALSE;
14461     } else {
14462         switch (gameMode) {
14463           default:
14464             return;
14465           case IcsExamining:
14466             pauseExamForwardMostMove = forwardMostMove;
14467             pauseExamInvalid = FALSE;
14468             /* fall through */
14469           case IcsObserving:
14470           case IcsPlayingWhite:
14471           case IcsPlayingBlack:
14472             pausing = TRUE;
14473             ModeHighlight();
14474             return;
14475           case PlayFromGameFile:
14476             (void) StopLoadGameTimer();
14477             pausing = TRUE;
14478             ModeHighlight();
14479             break;
14480           case BeginningOfGame:
14481             if (appData.icsActive) return;
14482             /* else fall through */
14483           case MachinePlaysWhite:
14484           case MachinePlaysBlack:
14485           case TwoMachinesPlay:
14486             if (forwardMostMove == 0)
14487               return;           /* don't pause if no one has moved */
14488             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14489                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14490                 if(onMove->pause) {           // thinking engine can be paused
14491                     PauseEngine(onMove);      // do it
14492                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14493                         PauseEngine(onMove->other);
14494                     else
14495                         SendToProgram("easy\n", onMove->other);
14496                     StopClocks();
14497                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14498             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14499                 if(first.pause) {
14500                     PauseEngine(&first);
14501                     StopClocks();
14502                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14503             } else { // human on move, pause pondering by either method
14504                 if(first.pause)
14505                     PauseEngine(&first);
14506                 else if(appData.ponderNextMove)
14507                     SendToProgram("easy\n", &first);
14508                 StopClocks();
14509             }
14510             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14511           case AnalyzeMode:
14512             pausing = TRUE;
14513             ModeHighlight();
14514             break;
14515         }
14516     }
14517 }
14518
14519 void
14520 EditCommentEvent ()
14521 {
14522     char title[MSG_SIZ];
14523
14524     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14525       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14526     } else {
14527       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14528                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14529                parseList[currentMove - 1]);
14530     }
14531
14532     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14533 }
14534
14535
14536 void
14537 EditTagsEvent ()
14538 {
14539     char *tags = PGNTags(&gameInfo);
14540     bookUp = FALSE;
14541     EditTagsPopUp(tags, NULL);
14542     free(tags);
14543 }
14544
14545 void
14546 ToggleSecond ()
14547 {
14548   if(second.analyzing) {
14549     SendToProgram("exit\n", &second);
14550     second.analyzing = FALSE;
14551   } else {
14552     if (second.pr == NoProc) StartChessProgram(&second);
14553     InitChessProgram(&second, FALSE);
14554     FeedMovesToProgram(&second, currentMove);
14555
14556     SendToProgram("analyze\n", &second);
14557     second.analyzing = TRUE;
14558   }
14559 }
14560
14561 /* Toggle ShowThinking */
14562 void
14563 ToggleShowThinking()
14564 {
14565   appData.showThinking = !appData.showThinking;
14566   ShowThinkingEvent();
14567 }
14568
14569 int
14570 AnalyzeModeEvent ()
14571 {
14572     char buf[MSG_SIZ];
14573
14574     if (!first.analysisSupport) {
14575       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14576       DisplayError(buf, 0);
14577       return 0;
14578     }
14579     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14580     if (appData.icsActive) {
14581         if (gameMode != IcsObserving) {
14582           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14583             DisplayError(buf, 0);
14584             /* secure check */
14585             if (appData.icsEngineAnalyze) {
14586                 if (appData.debugMode)
14587                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14588                 ExitAnalyzeMode();
14589                 ModeHighlight();
14590             }
14591             return 0;
14592         }
14593         /* if enable, user wants to disable icsEngineAnalyze */
14594         if (appData.icsEngineAnalyze) {
14595                 ExitAnalyzeMode();
14596                 ModeHighlight();
14597                 return 0;
14598         }
14599         appData.icsEngineAnalyze = TRUE;
14600         if (appData.debugMode)
14601             fprintf(debugFP, "ICS engine analyze starting... \n");
14602     }
14603
14604     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14605     if (appData.noChessProgram || gameMode == AnalyzeMode)
14606       return 0;
14607
14608     if (gameMode != AnalyzeFile) {
14609         if (!appData.icsEngineAnalyze) {
14610                EditGameEvent();
14611                if (gameMode != EditGame) return 0;
14612         }
14613         if (!appData.showThinking) ToggleShowThinking();
14614         ResurrectChessProgram();
14615         SendToProgram("analyze\n", &first);
14616         first.analyzing = TRUE;
14617         /*first.maybeThinking = TRUE;*/
14618         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14619         EngineOutputPopUp();
14620     }
14621     if (!appData.icsEngineAnalyze) {
14622         gameMode = AnalyzeMode;
14623         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14624     }
14625     pausing = FALSE;
14626     ModeHighlight();
14627     SetGameInfo();
14628
14629     StartAnalysisClock();
14630     GetTimeMark(&lastNodeCountTime);
14631     lastNodeCount = 0;
14632     return 1;
14633 }
14634
14635 void
14636 AnalyzeFileEvent ()
14637 {
14638     if (appData.noChessProgram || gameMode == AnalyzeFile)
14639       return;
14640
14641     if (!first.analysisSupport) {
14642       char buf[MSG_SIZ];
14643       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14644       DisplayError(buf, 0);
14645       return;
14646     }
14647
14648     if (gameMode != AnalyzeMode) {
14649         keepInfo = 1; // mere annotating should not alter PGN tags
14650         EditGameEvent();
14651         keepInfo = 0;
14652         if (gameMode != EditGame) return;
14653         if (!appData.showThinking) ToggleShowThinking();
14654         ResurrectChessProgram();
14655         SendToProgram("analyze\n", &first);
14656         first.analyzing = TRUE;
14657         /*first.maybeThinking = TRUE;*/
14658         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14659         EngineOutputPopUp();
14660     }
14661     gameMode = AnalyzeFile;
14662     pausing = FALSE;
14663     ModeHighlight();
14664
14665     StartAnalysisClock();
14666     GetTimeMark(&lastNodeCountTime);
14667     lastNodeCount = 0;
14668     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14669     AnalysisPeriodicEvent(1);
14670 }
14671
14672 void
14673 MachineWhiteEvent ()
14674 {
14675     char buf[MSG_SIZ];
14676     char *bookHit = NULL;
14677
14678     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14679       return;
14680
14681
14682     if (gameMode == PlayFromGameFile ||
14683         gameMode == TwoMachinesPlay  ||
14684         gameMode == Training         ||
14685         gameMode == AnalyzeMode      ||
14686         gameMode == EndOfGame)
14687         EditGameEvent();
14688
14689     if (gameMode == EditPosition)
14690         EditPositionDone(TRUE);
14691
14692     if (!WhiteOnMove(currentMove)) {
14693         DisplayError(_("It is not White's turn"), 0);
14694         return;
14695     }
14696
14697     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14698       ExitAnalyzeMode();
14699
14700     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14701         gameMode == AnalyzeFile)
14702         TruncateGame();
14703
14704     ResurrectChessProgram();    /* in case it isn't running */
14705     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14706         gameMode = MachinePlaysWhite;
14707         ResetClocks();
14708     } else
14709     gameMode = MachinePlaysWhite;
14710     pausing = FALSE;
14711     ModeHighlight();
14712     SetGameInfo();
14713     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14714     DisplayTitle(buf);
14715     if (first.sendName) {
14716       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14717       SendToProgram(buf, &first);
14718     }
14719     if (first.sendTime) {
14720       if (first.useColors) {
14721         SendToProgram("black\n", &first); /*gnu kludge*/
14722       }
14723       SendTimeRemaining(&first, TRUE);
14724     }
14725     if (first.useColors) {
14726       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14727     }
14728     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14729     SetMachineThinkingEnables();
14730     first.maybeThinking = TRUE;
14731     StartClocks();
14732     firstMove = FALSE;
14733
14734     if (appData.autoFlipView && !flipView) {
14735       flipView = !flipView;
14736       DrawPosition(FALSE, NULL);
14737       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14738     }
14739
14740     if(bookHit) { // [HGM] book: simulate book reply
14741         static char bookMove[MSG_SIZ]; // a bit generous?
14742
14743         programStats.nodes = programStats.depth = programStats.time =
14744         programStats.score = programStats.got_only_move = 0;
14745         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14746
14747         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14748         strcat(bookMove, bookHit);
14749         HandleMachineMove(bookMove, &first);
14750     }
14751 }
14752
14753 void
14754 MachineBlackEvent ()
14755 {
14756   char buf[MSG_SIZ];
14757   char *bookHit = NULL;
14758
14759     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14760         return;
14761
14762
14763     if (gameMode == PlayFromGameFile ||
14764         gameMode == TwoMachinesPlay  ||
14765         gameMode == Training         ||
14766         gameMode == AnalyzeMode      ||
14767         gameMode == EndOfGame)
14768         EditGameEvent();
14769
14770     if (gameMode == EditPosition)
14771         EditPositionDone(TRUE);
14772
14773     if (WhiteOnMove(currentMove)) {
14774         DisplayError(_("It is not Black's turn"), 0);
14775         return;
14776     }
14777
14778     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14779       ExitAnalyzeMode();
14780
14781     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14782         gameMode == AnalyzeFile)
14783         TruncateGame();
14784
14785     ResurrectChessProgram();    /* in case it isn't running */
14786     gameMode = MachinePlaysBlack;
14787     pausing = FALSE;
14788     ModeHighlight();
14789     SetGameInfo();
14790     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14791     DisplayTitle(buf);
14792     if (first.sendName) {
14793       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14794       SendToProgram(buf, &first);
14795     }
14796     if (first.sendTime) {
14797       if (first.useColors) {
14798         SendToProgram("white\n", &first); /*gnu kludge*/
14799       }
14800       SendTimeRemaining(&first, FALSE);
14801     }
14802     if (first.useColors) {
14803       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14804     }
14805     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14806     SetMachineThinkingEnables();
14807     first.maybeThinking = TRUE;
14808     StartClocks();
14809
14810     if (appData.autoFlipView && flipView) {
14811       flipView = !flipView;
14812       DrawPosition(FALSE, NULL);
14813       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14814     }
14815     if(bookHit) { // [HGM] book: simulate book reply
14816         static char bookMove[MSG_SIZ]; // a bit generous?
14817
14818         programStats.nodes = programStats.depth = programStats.time =
14819         programStats.score = programStats.got_only_move = 0;
14820         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14821
14822         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14823         strcat(bookMove, bookHit);
14824         HandleMachineMove(bookMove, &first);
14825     }
14826 }
14827
14828
14829 void
14830 DisplayTwoMachinesTitle ()
14831 {
14832     char buf[MSG_SIZ];
14833     if (appData.matchGames > 0) {
14834         if(appData.tourneyFile[0]) {
14835           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14836                    gameInfo.white, _("vs."), gameInfo.black,
14837                    nextGame+1, appData.matchGames+1,
14838                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14839         } else
14840         if (first.twoMachinesColor[0] == 'w') {
14841           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14842                    gameInfo.white, _("vs."),  gameInfo.black,
14843                    first.matchWins, second.matchWins,
14844                    matchGame - 1 - (first.matchWins + second.matchWins));
14845         } else {
14846           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14847                    gameInfo.white, _("vs."), gameInfo.black,
14848                    second.matchWins, first.matchWins,
14849                    matchGame - 1 - (first.matchWins + second.matchWins));
14850         }
14851     } else {
14852       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14853     }
14854     DisplayTitle(buf);
14855 }
14856
14857 void
14858 SettingsMenuIfReady ()
14859 {
14860   if (second.lastPing != second.lastPong) {
14861     DisplayMessage("", _("Waiting for second chess program"));
14862     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14863     return;
14864   }
14865   ThawUI();
14866   DisplayMessage("", "");
14867   SettingsPopUp(&second);
14868 }
14869
14870 int
14871 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14872 {
14873     char buf[MSG_SIZ];
14874     if (cps->pr == NoProc) {
14875         StartChessProgram(cps);
14876         if (cps->protocolVersion == 1) {
14877           retry();
14878           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14879         } else {
14880           /* kludge: allow timeout for initial "feature" command */
14881           if(retry != TwoMachinesEventIfReady) FreezeUI();
14882           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14883           DisplayMessage("", buf);
14884           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14885         }
14886         return 1;
14887     }
14888     return 0;
14889 }
14890
14891 void
14892 TwoMachinesEvent P((void))
14893 {
14894     int i;
14895     char buf[MSG_SIZ];
14896     ChessProgramState *onmove;
14897     char *bookHit = NULL;
14898     static int stalling = 0;
14899     TimeMark now;
14900     long wait;
14901
14902     if (appData.noChessProgram) return;
14903
14904     switch (gameMode) {
14905       case TwoMachinesPlay:
14906         return;
14907       case MachinePlaysWhite:
14908       case MachinePlaysBlack:
14909         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14910             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14911             return;
14912         }
14913         /* fall through */
14914       case BeginningOfGame:
14915       case PlayFromGameFile:
14916       case EndOfGame:
14917         EditGameEvent();
14918         if (gameMode != EditGame) return;
14919         break;
14920       case EditPosition:
14921         EditPositionDone(TRUE);
14922         break;
14923       case AnalyzeMode:
14924       case AnalyzeFile:
14925         ExitAnalyzeMode();
14926         break;
14927       case EditGame:
14928       default:
14929         break;
14930     }
14931
14932 //    forwardMostMove = currentMove;
14933     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14934     startingEngine = TRUE;
14935
14936     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14937
14938     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14939     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14940       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14941       return;
14942     }
14943     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14944
14945     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14946                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14947         startingEngine = matchMode = FALSE;
14948         DisplayError("second engine does not play this", 0);
14949         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14950         EditGameEvent(); // switch back to EditGame mode
14951         return;
14952     }
14953
14954     if(!stalling) {
14955       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14956       SendToProgram("force\n", &second);
14957       stalling = 1;
14958       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14959       return;
14960     }
14961     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14962     if(appData.matchPause>10000 || appData.matchPause<10)
14963                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14964     wait = SubtractTimeMarks(&now, &pauseStart);
14965     if(wait < appData.matchPause) {
14966         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14967         return;
14968     }
14969     // we are now committed to starting the game
14970     stalling = 0;
14971     DisplayMessage("", "");
14972     if (startedFromSetupPosition) {
14973         SendBoard(&second, backwardMostMove);
14974     if (appData.debugMode) {
14975         fprintf(debugFP, "Two Machines\n");
14976     }
14977     }
14978     for (i = backwardMostMove; i < forwardMostMove; i++) {
14979         SendMoveToProgram(i, &second);
14980     }
14981
14982     gameMode = TwoMachinesPlay;
14983     pausing = startingEngine = FALSE;
14984     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14985     SetGameInfo();
14986     DisplayTwoMachinesTitle();
14987     firstMove = TRUE;
14988     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14989         onmove = &first;
14990     } else {
14991         onmove = &second;
14992     }
14993     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14994     SendToProgram(first.computerString, &first);
14995     if (first.sendName) {
14996       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14997       SendToProgram(buf, &first);
14998     }
14999     SendToProgram(second.computerString, &second);
15000     if (second.sendName) {
15001       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15002       SendToProgram(buf, &second);
15003     }
15004
15005     ResetClocks();
15006     if (!first.sendTime || !second.sendTime) {
15007         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15008         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15009     }
15010     if (onmove->sendTime) {
15011       if (onmove->useColors) {
15012         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15013       }
15014       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15015     }
15016     if (onmove->useColors) {
15017       SendToProgram(onmove->twoMachinesColor, onmove);
15018     }
15019     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15020 //    SendToProgram("go\n", onmove);
15021     onmove->maybeThinking = TRUE;
15022     SetMachineThinkingEnables();
15023
15024     StartClocks();
15025
15026     if(bookHit) { // [HGM] book: simulate book reply
15027         static char bookMove[MSG_SIZ]; // a bit generous?
15028
15029         programStats.nodes = programStats.depth = programStats.time =
15030         programStats.score = programStats.got_only_move = 0;
15031         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15032
15033         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15034         strcat(bookMove, bookHit);
15035         savedMessage = bookMove; // args for deferred call
15036         savedState = onmove;
15037         ScheduleDelayedEvent(DeferredBookMove, 1);
15038     }
15039 }
15040
15041 void
15042 TrainingEvent ()
15043 {
15044     if (gameMode == Training) {
15045       SetTrainingModeOff();
15046       gameMode = PlayFromGameFile;
15047       DisplayMessage("", _("Training mode off"));
15048     } else {
15049       gameMode = Training;
15050       animateTraining = appData.animate;
15051
15052       /* make sure we are not already at the end of the game */
15053       if (currentMove < forwardMostMove) {
15054         SetTrainingModeOn();
15055         DisplayMessage("", _("Training mode on"));
15056       } else {
15057         gameMode = PlayFromGameFile;
15058         DisplayError(_("Already at end of game"), 0);
15059       }
15060     }
15061     ModeHighlight();
15062 }
15063
15064 void
15065 IcsClientEvent ()
15066 {
15067     if (!appData.icsActive) return;
15068     switch (gameMode) {
15069       case IcsPlayingWhite:
15070       case IcsPlayingBlack:
15071       case IcsObserving:
15072       case IcsIdle:
15073       case BeginningOfGame:
15074       case IcsExamining:
15075         return;
15076
15077       case EditGame:
15078         break;
15079
15080       case EditPosition:
15081         EditPositionDone(TRUE);
15082         break;
15083
15084       case AnalyzeMode:
15085       case AnalyzeFile:
15086         ExitAnalyzeMode();
15087         break;
15088
15089       default:
15090         EditGameEvent();
15091         break;
15092     }
15093
15094     gameMode = IcsIdle;
15095     ModeHighlight();
15096     return;
15097 }
15098
15099 void
15100 EditGameEvent ()
15101 {
15102     int i;
15103
15104     switch (gameMode) {
15105       case Training:
15106         SetTrainingModeOff();
15107         break;
15108       case MachinePlaysWhite:
15109       case MachinePlaysBlack:
15110       case BeginningOfGame:
15111         SendToProgram("force\n", &first);
15112         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15113             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15114                 char buf[MSG_SIZ];
15115                 abortEngineThink = TRUE;
15116                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15117                 SendToProgram(buf, &first);
15118                 DisplayMessage("Aborting engine think", "");
15119                 FreezeUI();
15120             }
15121         }
15122         SetUserThinkingEnables();
15123         break;
15124       case PlayFromGameFile:
15125         (void) StopLoadGameTimer();
15126         if (gameFileFP != NULL) {
15127             gameFileFP = NULL;
15128         }
15129         break;
15130       case EditPosition:
15131         EditPositionDone(TRUE);
15132         break;
15133       case AnalyzeMode:
15134       case AnalyzeFile:
15135         ExitAnalyzeMode();
15136         SendToProgram("force\n", &first);
15137         break;
15138       case TwoMachinesPlay:
15139         GameEnds(EndOfFile, NULL, GE_PLAYER);
15140         ResurrectChessProgram();
15141         SetUserThinkingEnables();
15142         break;
15143       case EndOfGame:
15144         ResurrectChessProgram();
15145         break;
15146       case IcsPlayingBlack:
15147       case IcsPlayingWhite:
15148         DisplayError(_("Warning: You are still playing a game"), 0);
15149         break;
15150       case IcsObserving:
15151         DisplayError(_("Warning: You are still observing a game"), 0);
15152         break;
15153       case IcsExamining:
15154         DisplayError(_("Warning: You are still examining a game"), 0);
15155         break;
15156       case IcsIdle:
15157         break;
15158       case EditGame:
15159       default:
15160         return;
15161     }
15162
15163     pausing = FALSE;
15164     StopClocks();
15165     first.offeredDraw = second.offeredDraw = 0;
15166
15167     if (gameMode == PlayFromGameFile) {
15168         whiteTimeRemaining = timeRemaining[0][currentMove];
15169         blackTimeRemaining = timeRemaining[1][currentMove];
15170         DisplayTitle("");
15171     }
15172
15173     if (gameMode == MachinePlaysWhite ||
15174         gameMode == MachinePlaysBlack ||
15175         gameMode == TwoMachinesPlay ||
15176         gameMode == EndOfGame) {
15177         i = forwardMostMove;
15178         while (i > currentMove) {
15179             SendToProgram("undo\n", &first);
15180             i--;
15181         }
15182         if(!adjustedClock) {
15183         whiteTimeRemaining = timeRemaining[0][currentMove];
15184         blackTimeRemaining = timeRemaining[1][currentMove];
15185         DisplayBothClocks();
15186         }
15187         if (whiteFlag || blackFlag) {
15188             whiteFlag = blackFlag = 0;
15189         }
15190         DisplayTitle("");
15191     }
15192
15193     gameMode = EditGame;
15194     ModeHighlight();
15195     SetGameInfo();
15196 }
15197
15198
15199 void
15200 EditPositionEvent ()
15201 {
15202     if (gameMode == EditPosition) {
15203         EditGameEvent();
15204         return;
15205     }
15206
15207     EditGameEvent();
15208     if (gameMode != EditGame) return;
15209
15210     gameMode = EditPosition;
15211     ModeHighlight();
15212     SetGameInfo();
15213     if (currentMove > 0)
15214       CopyBoard(boards[0], boards[currentMove]);
15215
15216     blackPlaysFirst = !WhiteOnMove(currentMove);
15217     ResetClocks();
15218     currentMove = forwardMostMove = backwardMostMove = 0;
15219     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15220     DisplayMove(-1);
15221     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15222 }
15223
15224 void
15225 ExitAnalyzeMode ()
15226 {
15227     /* [DM] icsEngineAnalyze - possible call from other functions */
15228     if (appData.icsEngineAnalyze) {
15229         appData.icsEngineAnalyze = FALSE;
15230
15231         DisplayMessage("",_("Close ICS engine analyze..."));
15232     }
15233     if (first.analysisSupport && first.analyzing) {
15234       SendToBoth("exit\n");
15235       first.analyzing = second.analyzing = FALSE;
15236     }
15237     thinkOutput[0] = NULLCHAR;
15238 }
15239
15240 void
15241 EditPositionDone (Boolean fakeRights)
15242 {
15243     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15244
15245     startedFromSetupPosition = TRUE;
15246     InitChessProgram(&first, FALSE);
15247     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15248       boards[0][EP_STATUS] = EP_NONE;
15249       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15250       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15251         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15252         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15253       } else boards[0][CASTLING][2] = NoRights;
15254       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15255         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15256         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15257       } else boards[0][CASTLING][5] = NoRights;
15258       if(gameInfo.variant == VariantSChess) {
15259         int i;
15260         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15261           boards[0][VIRGIN][i] = 0;
15262           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15263           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15264         }
15265       }
15266     }
15267     SendToProgram("force\n", &first);
15268     if (blackPlaysFirst) {
15269         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15270         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15271         currentMove = forwardMostMove = backwardMostMove = 1;
15272         CopyBoard(boards[1], boards[0]);
15273     } else {
15274         currentMove = forwardMostMove = backwardMostMove = 0;
15275     }
15276     SendBoard(&first, forwardMostMove);
15277     if (appData.debugMode) {
15278         fprintf(debugFP, "EditPosDone\n");
15279     }
15280     DisplayTitle("");
15281     DisplayMessage("", "");
15282     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15283     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15284     gameMode = EditGame;
15285     ModeHighlight();
15286     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15287     ClearHighlights(); /* [AS] */
15288 }
15289
15290 /* Pause for `ms' milliseconds */
15291 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15292 void
15293 TimeDelay (long ms)
15294 {
15295     TimeMark m1, m2;
15296
15297     GetTimeMark(&m1);
15298     do {
15299         GetTimeMark(&m2);
15300     } while (SubtractTimeMarks(&m2, &m1) < ms);
15301 }
15302
15303 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15304 void
15305 SendMultiLineToICS (char *buf)
15306 {
15307     char temp[MSG_SIZ+1], *p;
15308     int len;
15309
15310     len = strlen(buf);
15311     if (len > MSG_SIZ)
15312       len = MSG_SIZ;
15313
15314     strncpy(temp, buf, len);
15315     temp[len] = 0;
15316
15317     p = temp;
15318     while (*p) {
15319         if (*p == '\n' || *p == '\r')
15320           *p = ' ';
15321         ++p;
15322     }
15323
15324     strcat(temp, "\n");
15325     SendToICS(temp);
15326     SendToPlayer(temp, strlen(temp));
15327 }
15328
15329 void
15330 SetWhiteToPlayEvent ()
15331 {
15332     if (gameMode == EditPosition) {
15333         blackPlaysFirst = FALSE;
15334         DisplayBothClocks();    /* works because currentMove is 0 */
15335     } else if (gameMode == IcsExamining) {
15336         SendToICS(ics_prefix);
15337         SendToICS("tomove white\n");
15338     }
15339 }
15340
15341 void
15342 SetBlackToPlayEvent ()
15343 {
15344     if (gameMode == EditPosition) {
15345         blackPlaysFirst = TRUE;
15346         currentMove = 1;        /* kludge */
15347         DisplayBothClocks();
15348         currentMove = 0;
15349     } else if (gameMode == IcsExamining) {
15350         SendToICS(ics_prefix);
15351         SendToICS("tomove black\n");
15352     }
15353 }
15354
15355 void
15356 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15357 {
15358     char buf[MSG_SIZ];
15359     ChessSquare piece = boards[0][y][x];
15360     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15361     static int lastVariant;
15362
15363     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15364
15365     switch (selection) {
15366       case ClearBoard:
15367         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15368         MarkTargetSquares(1);
15369         CopyBoard(currentBoard, boards[0]);
15370         CopyBoard(menuBoard, initialPosition);
15371         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15372             SendToICS(ics_prefix);
15373             SendToICS("bsetup clear\n");
15374         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15375             SendToICS(ics_prefix);
15376             SendToICS("clearboard\n");
15377         } else {
15378             int nonEmpty = 0;
15379             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15380                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15381                 for (y = 0; y < BOARD_HEIGHT; y++) {
15382                     if (gameMode == IcsExamining) {
15383                         if (boards[currentMove][y][x] != EmptySquare) {
15384                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15385                                     AAA + x, ONE + y);
15386                             SendToICS(buf);
15387                         }
15388                     } else if(boards[0][y][x] != DarkSquare) {
15389                         if(boards[0][y][x] != p) nonEmpty++;
15390                         boards[0][y][x] = p;
15391                     }
15392                 }
15393             }
15394             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15395                 int r;
15396                 for(r = 0; r < BOARD_HEIGHT; r++) {
15397                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15398                     ChessSquare p = menuBoard[r][x];
15399                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15400                   }
15401                 }
15402                 DisplayMessage("Clicking clock again restores position", "");
15403                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15404                 if(!nonEmpty) { // asked to clear an empty board
15405                     CopyBoard(boards[0], menuBoard);
15406                 } else
15407                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15408                     CopyBoard(boards[0], initialPosition);
15409                 } else
15410                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15411                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15412                     CopyBoard(boards[0], erasedBoard);
15413                 } else
15414                     CopyBoard(erasedBoard, currentBoard);
15415
15416             }
15417         }
15418         if (gameMode == EditPosition) {
15419             DrawPosition(FALSE, boards[0]);
15420         }
15421         break;
15422
15423       case WhitePlay:
15424         SetWhiteToPlayEvent();
15425         break;
15426
15427       case BlackPlay:
15428         SetBlackToPlayEvent();
15429         break;
15430
15431       case EmptySquare:
15432         if (gameMode == IcsExamining) {
15433             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15434             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15435             SendToICS(buf);
15436         } else {
15437             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15438                 if(x == BOARD_LEFT-2) {
15439                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15440                     boards[0][y][1] = 0;
15441                 } else
15442                 if(x == BOARD_RGHT+1) {
15443                     if(y >= gameInfo.holdingsSize) break;
15444                     boards[0][y][BOARD_WIDTH-2] = 0;
15445                 } else break;
15446             }
15447             boards[0][y][x] = EmptySquare;
15448             DrawPosition(FALSE, boards[0]);
15449         }
15450         break;
15451
15452       case PromotePiece:
15453         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15454            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15455             selection = (ChessSquare) (PROMOTED(piece));
15456         } else if(piece == EmptySquare) selection = WhiteSilver;
15457         else selection = (ChessSquare)((int)piece - 1);
15458         goto defaultlabel;
15459
15460       case DemotePiece:
15461         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15462            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15463             selection = (ChessSquare) (DEMOTED(piece));
15464         } else if(piece == EmptySquare) selection = BlackSilver;
15465         else selection = (ChessSquare)((int)piece + 1);
15466         goto defaultlabel;
15467
15468       case WhiteQueen:
15469       case BlackQueen:
15470         if(gameInfo.variant == VariantShatranj ||
15471            gameInfo.variant == VariantXiangqi  ||
15472            gameInfo.variant == VariantCourier  ||
15473            gameInfo.variant == VariantASEAN    ||
15474            gameInfo.variant == VariantMakruk     )
15475             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15476         goto defaultlabel;
15477
15478       case WhiteKing:
15479       case BlackKing:
15480         if(gameInfo.variant == VariantXiangqi)
15481             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15482         if(gameInfo.variant == VariantKnightmate)
15483             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15484       default:
15485         defaultlabel:
15486         if (gameMode == IcsExamining) {
15487             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15488             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15489                      PieceToChar(selection), AAA + x, ONE + y);
15490             SendToICS(buf);
15491         } else {
15492             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15493                 int n;
15494                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15495                     n = PieceToNumber(selection - BlackPawn);
15496                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15497                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15498                     boards[0][BOARD_HEIGHT-1-n][1]++;
15499                 } else
15500                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15501                     n = PieceToNumber(selection);
15502                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15503                     boards[0][n][BOARD_WIDTH-1] = selection;
15504                     boards[0][n][BOARD_WIDTH-2]++;
15505                 }
15506             } else
15507             boards[0][y][x] = selection;
15508             DrawPosition(TRUE, boards[0]);
15509             ClearHighlights();
15510             fromX = fromY = -1;
15511         }
15512         break;
15513     }
15514 }
15515
15516
15517 void
15518 DropMenuEvent (ChessSquare selection, int x, int y)
15519 {
15520     ChessMove moveType;
15521
15522     switch (gameMode) {
15523       case IcsPlayingWhite:
15524       case MachinePlaysBlack:
15525         if (!WhiteOnMove(currentMove)) {
15526             DisplayMoveError(_("It is Black's turn"));
15527             return;
15528         }
15529         moveType = WhiteDrop;
15530         break;
15531       case IcsPlayingBlack:
15532       case MachinePlaysWhite:
15533         if (WhiteOnMove(currentMove)) {
15534             DisplayMoveError(_("It is White's turn"));
15535             return;
15536         }
15537         moveType = BlackDrop;
15538         break;
15539       case EditGame:
15540         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15541         break;
15542       default:
15543         return;
15544     }
15545
15546     if (moveType == BlackDrop && selection < BlackPawn) {
15547       selection = (ChessSquare) ((int) selection
15548                                  + (int) BlackPawn - (int) WhitePawn);
15549     }
15550     if (boards[currentMove][y][x] != EmptySquare) {
15551         DisplayMoveError(_("That square is occupied"));
15552         return;
15553     }
15554
15555     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15556 }
15557
15558 void
15559 AcceptEvent ()
15560 {
15561     /* Accept a pending offer of any kind from opponent */
15562
15563     if (appData.icsActive) {
15564         SendToICS(ics_prefix);
15565         SendToICS("accept\n");
15566     } else if (cmailMsgLoaded) {
15567         if (currentMove == cmailOldMove &&
15568             commentList[cmailOldMove] != NULL &&
15569             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15570                    "Black offers a draw" : "White offers a draw")) {
15571             TruncateGame();
15572             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15573             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15574         } else {
15575             DisplayError(_("There is no pending offer on this move"), 0);
15576             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15577         }
15578     } else {
15579         /* Not used for offers from chess program */
15580     }
15581 }
15582
15583 void
15584 DeclineEvent ()
15585 {
15586     /* Decline a pending offer of any kind from opponent */
15587
15588     if (appData.icsActive) {
15589         SendToICS(ics_prefix);
15590         SendToICS("decline\n");
15591     } else if (cmailMsgLoaded) {
15592         if (currentMove == cmailOldMove &&
15593             commentList[cmailOldMove] != NULL &&
15594             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15595                    "Black offers a draw" : "White offers a draw")) {
15596 #ifdef NOTDEF
15597             AppendComment(cmailOldMove, "Draw declined", TRUE);
15598             DisplayComment(cmailOldMove - 1, "Draw declined");
15599 #endif /*NOTDEF*/
15600         } else {
15601             DisplayError(_("There is no pending offer on this move"), 0);
15602         }
15603     } else {
15604         /* Not used for offers from chess program */
15605     }
15606 }
15607
15608 void
15609 RematchEvent ()
15610 {
15611     /* Issue ICS rematch command */
15612     if (appData.icsActive) {
15613         SendToICS(ics_prefix);
15614         SendToICS("rematch\n");
15615     }
15616 }
15617
15618 void
15619 CallFlagEvent ()
15620 {
15621     /* Call your opponent's flag (claim a win on time) */
15622     if (appData.icsActive) {
15623         SendToICS(ics_prefix);
15624         SendToICS("flag\n");
15625     } else {
15626         switch (gameMode) {
15627           default:
15628             return;
15629           case MachinePlaysWhite:
15630             if (whiteFlag) {
15631                 if (blackFlag)
15632                   GameEnds(GameIsDrawn, "Both players ran out of time",
15633                            GE_PLAYER);
15634                 else
15635                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15636             } else {
15637                 DisplayError(_("Your opponent is not out of time"), 0);
15638             }
15639             break;
15640           case MachinePlaysBlack:
15641             if (blackFlag) {
15642                 if (whiteFlag)
15643                   GameEnds(GameIsDrawn, "Both players ran out of time",
15644                            GE_PLAYER);
15645                 else
15646                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15647             } else {
15648                 DisplayError(_("Your opponent is not out of time"), 0);
15649             }
15650             break;
15651         }
15652     }
15653 }
15654
15655 void
15656 ClockClick (int which)
15657 {       // [HGM] code moved to back-end from winboard.c
15658         if(which) { // black clock
15659           if (gameMode == EditPosition || gameMode == IcsExamining) {
15660             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15661             SetBlackToPlayEvent();
15662           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15663                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15664           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15665           } else if (shiftKey) {
15666             AdjustClock(which, -1);
15667           } else if (gameMode == IcsPlayingWhite ||
15668                      gameMode == MachinePlaysBlack) {
15669             CallFlagEvent();
15670           }
15671         } else { // white clock
15672           if (gameMode == EditPosition || gameMode == IcsExamining) {
15673             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15674             SetWhiteToPlayEvent();
15675           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15676                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15677           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15678           } else if (shiftKey) {
15679             AdjustClock(which, -1);
15680           } else if (gameMode == IcsPlayingBlack ||
15681                    gameMode == MachinePlaysWhite) {
15682             CallFlagEvent();
15683           }
15684         }
15685 }
15686
15687 void
15688 DrawEvent ()
15689 {
15690     /* Offer draw or accept pending draw offer from opponent */
15691
15692     if (appData.icsActive) {
15693         /* Note: tournament rules require draw offers to be
15694            made after you make your move but before you punch
15695            your clock.  Currently ICS doesn't let you do that;
15696            instead, you immediately punch your clock after making
15697            a move, but you can offer a draw at any time. */
15698
15699         SendToICS(ics_prefix);
15700         SendToICS("draw\n");
15701         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15702     } else if (cmailMsgLoaded) {
15703         if (currentMove == cmailOldMove &&
15704             commentList[cmailOldMove] != NULL &&
15705             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15706                    "Black offers a draw" : "White offers a draw")) {
15707             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15708             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15709         } else if (currentMove == cmailOldMove + 1) {
15710             char *offer = WhiteOnMove(cmailOldMove) ?
15711               "White offers a draw" : "Black offers a draw";
15712             AppendComment(currentMove, offer, TRUE);
15713             DisplayComment(currentMove - 1, offer);
15714             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15715         } else {
15716             DisplayError(_("You must make your move before offering a draw"), 0);
15717             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15718         }
15719     } else if (first.offeredDraw) {
15720         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15721     } else {
15722         if (first.sendDrawOffers) {
15723             SendToProgram("draw\n", &first);
15724             userOfferedDraw = TRUE;
15725         }
15726     }
15727 }
15728
15729 void
15730 AdjournEvent ()
15731 {
15732     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15733
15734     if (appData.icsActive) {
15735         SendToICS(ics_prefix);
15736         SendToICS("adjourn\n");
15737     } else {
15738         /* Currently GNU Chess doesn't offer or accept Adjourns */
15739     }
15740 }
15741
15742
15743 void
15744 AbortEvent ()
15745 {
15746     /* Offer Abort or accept pending Abort offer from opponent */
15747
15748     if (appData.icsActive) {
15749         SendToICS(ics_prefix);
15750         SendToICS("abort\n");
15751     } else {
15752         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15753     }
15754 }
15755
15756 void
15757 ResignEvent ()
15758 {
15759     /* Resign.  You can do this even if it's not your turn. */
15760
15761     if (appData.icsActive) {
15762         SendToICS(ics_prefix);
15763         SendToICS("resign\n");
15764     } else {
15765         switch (gameMode) {
15766           case MachinePlaysWhite:
15767             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15768             break;
15769           case MachinePlaysBlack:
15770             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15771             break;
15772           case EditGame:
15773             if (cmailMsgLoaded) {
15774                 TruncateGame();
15775                 if (WhiteOnMove(cmailOldMove)) {
15776                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15777                 } else {
15778                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15779                 }
15780                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15781             }
15782             break;
15783           default:
15784             break;
15785         }
15786     }
15787 }
15788
15789
15790 void
15791 StopObservingEvent ()
15792 {
15793     /* Stop observing current games */
15794     SendToICS(ics_prefix);
15795     SendToICS("unobserve\n");
15796 }
15797
15798 void
15799 StopExaminingEvent ()
15800 {
15801     /* Stop observing current game */
15802     SendToICS(ics_prefix);
15803     SendToICS("unexamine\n");
15804 }
15805
15806 void
15807 ForwardInner (int target)
15808 {
15809     int limit; int oldSeekGraphUp = seekGraphUp;
15810
15811     if (appData.debugMode)
15812         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15813                 target, currentMove, forwardMostMove);
15814
15815     if (gameMode == EditPosition)
15816       return;
15817
15818     seekGraphUp = FALSE;
15819     MarkTargetSquares(1);
15820     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15821
15822     if (gameMode == PlayFromGameFile && !pausing)
15823       PauseEvent();
15824
15825     if (gameMode == IcsExamining && pausing)
15826       limit = pauseExamForwardMostMove;
15827     else
15828       limit = forwardMostMove;
15829
15830     if (target > limit) target = limit;
15831
15832     if (target > 0 && moveList[target - 1][0]) {
15833         int fromX, fromY, toX, toY;
15834         toX = moveList[target - 1][2] - AAA;
15835         toY = moveList[target - 1][3] - ONE;
15836         if (moveList[target - 1][1] == '@') {
15837             if (appData.highlightLastMove) {
15838                 SetHighlights(-1, -1, toX, toY);
15839             }
15840         } else {
15841             int viaX = moveList[target - 1][5] - AAA;
15842             int viaY = moveList[target - 1][6] - ONE;
15843             fromX = moveList[target - 1][0] - AAA;
15844             fromY = moveList[target - 1][1] - ONE;
15845             if (target == currentMove + 1) {
15846                 if(moveList[target - 1][4] == ';') { // multi-leg
15847                     ChessSquare piece = boards[currentMove][viaY][viaX];
15848                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15849                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15850                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15851                     boards[currentMove][viaY][viaX] = piece;
15852                 } else
15853                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15854             }
15855             if (appData.highlightLastMove) {
15856                 SetHighlights(fromX, fromY, toX, toY);
15857             }
15858         }
15859     }
15860     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15861         gameMode == Training || gameMode == PlayFromGameFile ||
15862         gameMode == AnalyzeFile) {
15863         while (currentMove < target) {
15864             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15865             SendMoveToProgram(currentMove++, &first);
15866         }
15867     } else {
15868         currentMove = target;
15869     }
15870
15871     if (gameMode == EditGame || gameMode == EndOfGame) {
15872         whiteTimeRemaining = timeRemaining[0][currentMove];
15873         blackTimeRemaining = timeRemaining[1][currentMove];
15874     }
15875     DisplayBothClocks();
15876     DisplayMove(currentMove - 1);
15877     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15878     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15879     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15880         DisplayComment(currentMove - 1, commentList[currentMove]);
15881     }
15882     ClearMap(); // [HGM] exclude: invalidate map
15883 }
15884
15885
15886 void
15887 ForwardEvent ()
15888 {
15889     if (gameMode == IcsExamining && !pausing) {
15890         SendToICS(ics_prefix);
15891         SendToICS("forward\n");
15892     } else {
15893         ForwardInner(currentMove + 1);
15894     }
15895 }
15896
15897 void
15898 ToEndEvent ()
15899 {
15900     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15901         /* to optimze, we temporarily turn off analysis mode while we feed
15902          * the remaining moves to the engine. Otherwise we get analysis output
15903          * after each move.
15904          */
15905         if (first.analysisSupport) {
15906           SendToProgram("exit\nforce\n", &first);
15907           first.analyzing = FALSE;
15908         }
15909     }
15910
15911     if (gameMode == IcsExamining && !pausing) {
15912         SendToICS(ics_prefix);
15913         SendToICS("forward 999999\n");
15914     } else {
15915         ForwardInner(forwardMostMove);
15916     }
15917
15918     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15919         /* we have fed all the moves, so reactivate analysis mode */
15920         SendToProgram("analyze\n", &first);
15921         first.analyzing = TRUE;
15922         /*first.maybeThinking = TRUE;*/
15923         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15924     }
15925 }
15926
15927 void
15928 BackwardInner (int target)
15929 {
15930     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15931
15932     if (appData.debugMode)
15933         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15934                 target, currentMove, forwardMostMove);
15935
15936     if (gameMode == EditPosition) return;
15937     seekGraphUp = FALSE;
15938     MarkTargetSquares(1);
15939     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15940     if (currentMove <= backwardMostMove) {
15941         ClearHighlights();
15942         DrawPosition(full_redraw, boards[currentMove]);
15943         return;
15944     }
15945     if (gameMode == PlayFromGameFile && !pausing)
15946       PauseEvent();
15947
15948     if (moveList[target][0]) {
15949         int fromX, fromY, toX, toY;
15950         toX = moveList[target][2] - AAA;
15951         toY = moveList[target][3] - ONE;
15952         if (moveList[target][1] == '@') {
15953             if (appData.highlightLastMove) {
15954                 SetHighlights(-1, -1, toX, toY);
15955             }
15956         } else {
15957             fromX = moveList[target][0] - AAA;
15958             fromY = moveList[target][1] - ONE;
15959             if (target == currentMove - 1) {
15960                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15961             }
15962             if (appData.highlightLastMove) {
15963                 SetHighlights(fromX, fromY, toX, toY);
15964             }
15965         }
15966     }
15967     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15968         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15969         while (currentMove > target) {
15970             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15971                 // null move cannot be undone. Reload program with move history before it.
15972                 int i;
15973                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15974                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15975                 }
15976                 SendBoard(&first, i);
15977               if(second.analyzing) SendBoard(&second, i);
15978                 for(currentMove=i; currentMove<target; currentMove++) {
15979                     SendMoveToProgram(currentMove, &first);
15980                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15981                 }
15982                 break;
15983             }
15984             SendToBoth("undo\n");
15985             currentMove--;
15986         }
15987     } else {
15988         currentMove = target;
15989     }
15990
15991     if (gameMode == EditGame || gameMode == EndOfGame) {
15992         whiteTimeRemaining = timeRemaining[0][currentMove];
15993         blackTimeRemaining = timeRemaining[1][currentMove];
15994     }
15995     DisplayBothClocks();
15996     DisplayMove(currentMove - 1);
15997     DrawPosition(full_redraw, boards[currentMove]);
15998     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15999     // [HGM] PV info: routine tests if comment empty
16000     DisplayComment(currentMove - 1, commentList[currentMove]);
16001     ClearMap(); // [HGM] exclude: invalidate map
16002 }
16003
16004 void
16005 BackwardEvent ()
16006 {
16007     if (gameMode == IcsExamining && !pausing) {
16008         SendToICS(ics_prefix);
16009         SendToICS("backward\n");
16010     } else {
16011         BackwardInner(currentMove - 1);
16012     }
16013 }
16014
16015 void
16016 ToStartEvent ()
16017 {
16018     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16019         /* to optimize, we temporarily turn off analysis mode while we undo
16020          * all the moves. Otherwise we get analysis output after each undo.
16021          */
16022         if (first.analysisSupport) {
16023           SendToProgram("exit\nforce\n", &first);
16024           first.analyzing = FALSE;
16025         }
16026     }
16027
16028     if (gameMode == IcsExamining && !pausing) {
16029         SendToICS(ics_prefix);
16030         SendToICS("backward 999999\n");
16031     } else {
16032         BackwardInner(backwardMostMove);
16033     }
16034
16035     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16036         /* we have fed all the moves, so reactivate analysis mode */
16037         SendToProgram("analyze\n", &first);
16038         first.analyzing = TRUE;
16039         /*first.maybeThinking = TRUE;*/
16040         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16041     }
16042 }
16043
16044 void
16045 ToNrEvent (int to)
16046 {
16047   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16048   if (to >= forwardMostMove) to = forwardMostMove;
16049   if (to <= backwardMostMove) to = backwardMostMove;
16050   if (to < currentMove) {
16051     BackwardInner(to);
16052   } else {
16053     ForwardInner(to);
16054   }
16055 }
16056
16057 void
16058 RevertEvent (Boolean annotate)
16059 {
16060     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16061         return;
16062     }
16063     if (gameMode != IcsExamining) {
16064         DisplayError(_("You are not examining a game"), 0);
16065         return;
16066     }
16067     if (pausing) {
16068         DisplayError(_("You can't revert while pausing"), 0);
16069         return;
16070     }
16071     SendToICS(ics_prefix);
16072     SendToICS("revert\n");
16073 }
16074
16075 void
16076 RetractMoveEvent ()
16077 {
16078     switch (gameMode) {
16079       case MachinePlaysWhite:
16080       case MachinePlaysBlack:
16081         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16082             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16083             return;
16084         }
16085         if (forwardMostMove < 2) return;
16086         currentMove = forwardMostMove = forwardMostMove - 2;
16087         whiteTimeRemaining = timeRemaining[0][currentMove];
16088         blackTimeRemaining = timeRemaining[1][currentMove];
16089         DisplayBothClocks();
16090         DisplayMove(currentMove - 1);
16091         ClearHighlights();/*!! could figure this out*/
16092         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16093         SendToProgram("remove\n", &first);
16094         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16095         break;
16096
16097       case BeginningOfGame:
16098       default:
16099         break;
16100
16101       case IcsPlayingWhite:
16102       case IcsPlayingBlack:
16103         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16104             SendToICS(ics_prefix);
16105             SendToICS("takeback 2\n");
16106         } else {
16107             SendToICS(ics_prefix);
16108             SendToICS("takeback 1\n");
16109         }
16110         break;
16111     }
16112 }
16113
16114 void
16115 MoveNowEvent ()
16116 {
16117     ChessProgramState *cps;
16118
16119     switch (gameMode) {
16120       case MachinePlaysWhite:
16121         if (!WhiteOnMove(forwardMostMove)) {
16122             DisplayError(_("It is your turn"), 0);
16123             return;
16124         }
16125         cps = &first;
16126         break;
16127       case MachinePlaysBlack:
16128         if (WhiteOnMove(forwardMostMove)) {
16129             DisplayError(_("It is your turn"), 0);
16130             return;
16131         }
16132         cps = &first;
16133         break;
16134       case TwoMachinesPlay:
16135         if (WhiteOnMove(forwardMostMove) ==
16136             (first.twoMachinesColor[0] == 'w')) {
16137             cps = &first;
16138         } else {
16139             cps = &second;
16140         }
16141         break;
16142       case BeginningOfGame:
16143       default:
16144         return;
16145     }
16146     SendToProgram("?\n", cps);
16147 }
16148
16149 void
16150 TruncateGameEvent ()
16151 {
16152     EditGameEvent();
16153     if (gameMode != EditGame) return;
16154     TruncateGame();
16155 }
16156
16157 void
16158 TruncateGame ()
16159 {
16160     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16161     if (forwardMostMove > currentMove) {
16162         if (gameInfo.resultDetails != NULL) {
16163             free(gameInfo.resultDetails);
16164             gameInfo.resultDetails = NULL;
16165             gameInfo.result = GameUnfinished;
16166         }
16167         forwardMostMove = currentMove;
16168         HistorySet(parseList, backwardMostMove, forwardMostMove,
16169                    currentMove-1);
16170     }
16171 }
16172
16173 void
16174 HintEvent ()
16175 {
16176     if (appData.noChessProgram) return;
16177     switch (gameMode) {
16178       case MachinePlaysWhite:
16179         if (WhiteOnMove(forwardMostMove)) {
16180             DisplayError(_("Wait until your turn."), 0);
16181             return;
16182         }
16183         break;
16184       case BeginningOfGame:
16185       case MachinePlaysBlack:
16186         if (!WhiteOnMove(forwardMostMove)) {
16187             DisplayError(_("Wait until your turn."), 0);
16188             return;
16189         }
16190         break;
16191       default:
16192         DisplayError(_("No hint available"), 0);
16193         return;
16194     }
16195     SendToProgram("hint\n", &first);
16196     hintRequested = TRUE;
16197 }
16198
16199 int
16200 SaveSelected (FILE *g, int dummy, char *dummy2)
16201 {
16202     ListGame * lg = (ListGame *) gameList.head;
16203     int nItem, cnt=0;
16204     FILE *f;
16205
16206     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16207         DisplayError(_("Game list not loaded or empty"), 0);
16208         return 0;
16209     }
16210
16211     creatingBook = TRUE; // suppresses stuff during load game
16212
16213     /* Get list size */
16214     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16215         if(lg->position >= 0) { // selected?
16216             LoadGame(f, nItem, "", TRUE);
16217             SaveGamePGN2(g); // leaves g open
16218             cnt++; DoEvents();
16219         }
16220         lg = (ListGame *) lg->node.succ;
16221     }
16222
16223     fclose(g);
16224     creatingBook = FALSE;
16225
16226     return cnt;
16227 }
16228
16229 void
16230 CreateBookEvent ()
16231 {
16232     ListGame * lg = (ListGame *) gameList.head;
16233     FILE *f, *g;
16234     int nItem;
16235     static int secondTime = FALSE;
16236
16237     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16238         DisplayError(_("Game list not loaded or empty"), 0);
16239         return;
16240     }
16241
16242     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16243         fclose(g);
16244         secondTime++;
16245         DisplayNote(_("Book file exists! Try again for overwrite."));
16246         return;
16247     }
16248
16249     creatingBook = TRUE;
16250     secondTime = FALSE;
16251
16252     /* Get list size */
16253     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16254         if(lg->position >= 0) {
16255             LoadGame(f, nItem, "", TRUE);
16256             AddGameToBook(TRUE);
16257             DoEvents();
16258         }
16259         lg = (ListGame *) lg->node.succ;
16260     }
16261
16262     creatingBook = FALSE;
16263     FlushBook();
16264 }
16265
16266 void
16267 BookEvent ()
16268 {
16269     if (appData.noChessProgram) return;
16270     switch (gameMode) {
16271       case MachinePlaysWhite:
16272         if (WhiteOnMove(forwardMostMove)) {
16273             DisplayError(_("Wait until your turn."), 0);
16274             return;
16275         }
16276         break;
16277       case BeginningOfGame:
16278       case MachinePlaysBlack:
16279         if (!WhiteOnMove(forwardMostMove)) {
16280             DisplayError(_("Wait until your turn."), 0);
16281             return;
16282         }
16283         break;
16284       case EditPosition:
16285         EditPositionDone(TRUE);
16286         break;
16287       case TwoMachinesPlay:
16288         return;
16289       default:
16290         break;
16291     }
16292     SendToProgram("bk\n", &first);
16293     bookOutput[0] = NULLCHAR;
16294     bookRequested = TRUE;
16295 }
16296
16297 void
16298 AboutGameEvent ()
16299 {
16300     char *tags = PGNTags(&gameInfo);
16301     TagsPopUp(tags, CmailMsg());
16302     free(tags);
16303 }
16304
16305 /* end button procedures */
16306
16307 void
16308 PrintPosition (FILE *fp, int move)
16309 {
16310     int i, j;
16311
16312     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16313         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16314             char c = PieceToChar(boards[move][i][j]);
16315             fputc(c == '?' ? '.' : c, fp);
16316             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16317         }
16318     }
16319     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16320       fprintf(fp, "white to play\n");
16321     else
16322       fprintf(fp, "black to play\n");
16323 }
16324
16325 void
16326 PrintOpponents (FILE *fp)
16327 {
16328     if (gameInfo.white != NULL) {
16329         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16330     } else {
16331         fprintf(fp, "\n");
16332     }
16333 }
16334
16335 /* Find last component of program's own name, using some heuristics */
16336 void
16337 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16338 {
16339     char *p, *q, c;
16340     int local = (strcmp(host, "localhost") == 0);
16341     while (!local && (p = strchr(prog, ';')) != NULL) {
16342         p++;
16343         while (*p == ' ') p++;
16344         prog = p;
16345     }
16346     if (*prog == '"' || *prog == '\'') {
16347         q = strchr(prog + 1, *prog);
16348     } else {
16349         q = strchr(prog, ' ');
16350     }
16351     if (q == NULL) q = prog + strlen(prog);
16352     p = q;
16353     while (p >= prog && *p != '/' && *p != '\\') p--;
16354     p++;
16355     if(p == prog && *p == '"') p++;
16356     c = *q; *q = 0;
16357     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16358     memcpy(buf, p, q - p);
16359     buf[q - p] = NULLCHAR;
16360     if (!local) {
16361         strcat(buf, "@");
16362         strcat(buf, host);
16363     }
16364 }
16365
16366 char *
16367 TimeControlTagValue ()
16368 {
16369     char buf[MSG_SIZ];
16370     if (!appData.clockMode) {
16371       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16372     } else if (movesPerSession > 0) {
16373       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16374     } else if (timeIncrement == 0) {
16375       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16376     } else {
16377       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16378     }
16379     return StrSave(buf);
16380 }
16381
16382 void
16383 SetGameInfo ()
16384 {
16385     /* This routine is used only for certain modes */
16386     VariantClass v = gameInfo.variant;
16387     ChessMove r = GameUnfinished;
16388     char *p = NULL;
16389
16390     if(keepInfo) return;
16391
16392     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16393         r = gameInfo.result;
16394         p = gameInfo.resultDetails;
16395         gameInfo.resultDetails = NULL;
16396     }
16397     ClearGameInfo(&gameInfo);
16398     gameInfo.variant = v;
16399
16400     switch (gameMode) {
16401       case MachinePlaysWhite:
16402         gameInfo.event = StrSave( appData.pgnEventHeader );
16403         gameInfo.site = StrSave(HostName());
16404         gameInfo.date = PGNDate();
16405         gameInfo.round = StrSave("-");
16406         gameInfo.white = StrSave(first.tidy);
16407         gameInfo.black = StrSave(UserName());
16408         gameInfo.timeControl = TimeControlTagValue();
16409         break;
16410
16411       case MachinePlaysBlack:
16412         gameInfo.event = StrSave( appData.pgnEventHeader );
16413         gameInfo.site = StrSave(HostName());
16414         gameInfo.date = PGNDate();
16415         gameInfo.round = StrSave("-");
16416         gameInfo.white = StrSave(UserName());
16417         gameInfo.black = StrSave(first.tidy);
16418         gameInfo.timeControl = TimeControlTagValue();
16419         break;
16420
16421       case TwoMachinesPlay:
16422         gameInfo.event = StrSave( appData.pgnEventHeader );
16423         gameInfo.site = StrSave(HostName());
16424         gameInfo.date = PGNDate();
16425         if (roundNr > 0) {
16426             char buf[MSG_SIZ];
16427             snprintf(buf, MSG_SIZ, "%d", roundNr);
16428             gameInfo.round = StrSave(buf);
16429         } else {
16430             gameInfo.round = StrSave("-");
16431         }
16432         if (first.twoMachinesColor[0] == 'w') {
16433             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16434             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16435         } else {
16436             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16437             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16438         }
16439         gameInfo.timeControl = TimeControlTagValue();
16440         break;
16441
16442       case EditGame:
16443         gameInfo.event = StrSave("Edited game");
16444         gameInfo.site = StrSave(HostName());
16445         gameInfo.date = PGNDate();
16446         gameInfo.round = StrSave("-");
16447         gameInfo.white = StrSave("-");
16448         gameInfo.black = StrSave("-");
16449         gameInfo.result = r;
16450         gameInfo.resultDetails = p;
16451         break;
16452
16453       case EditPosition:
16454         gameInfo.event = StrSave("Edited position");
16455         gameInfo.site = StrSave(HostName());
16456         gameInfo.date = PGNDate();
16457         gameInfo.round = StrSave("-");
16458         gameInfo.white = StrSave("-");
16459         gameInfo.black = StrSave("-");
16460         break;
16461
16462       case IcsPlayingWhite:
16463       case IcsPlayingBlack:
16464       case IcsObserving:
16465       case IcsExamining:
16466         break;
16467
16468       case PlayFromGameFile:
16469         gameInfo.event = StrSave("Game from non-PGN file");
16470         gameInfo.site = StrSave(HostName());
16471         gameInfo.date = PGNDate();
16472         gameInfo.round = StrSave("-");
16473         gameInfo.white = StrSave("?");
16474         gameInfo.black = StrSave("?");
16475         break;
16476
16477       default:
16478         break;
16479     }
16480 }
16481
16482 void
16483 ReplaceComment (int index, char *text)
16484 {
16485     int len;
16486     char *p;
16487     float score;
16488
16489     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16490        pvInfoList[index-1].depth == len &&
16491        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16492        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16493     while (*text == '\n') text++;
16494     len = strlen(text);
16495     while (len > 0 && text[len - 1] == '\n') len--;
16496
16497     if (commentList[index] != NULL)
16498       free(commentList[index]);
16499
16500     if (len == 0) {
16501         commentList[index] = NULL;
16502         return;
16503     }
16504   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16505       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16506       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16507     commentList[index] = (char *) malloc(len + 2);
16508     strncpy(commentList[index], text, len);
16509     commentList[index][len] = '\n';
16510     commentList[index][len + 1] = NULLCHAR;
16511   } else {
16512     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16513     char *p;
16514     commentList[index] = (char *) malloc(len + 7);
16515     safeStrCpy(commentList[index], "{\n", 3);
16516     safeStrCpy(commentList[index]+2, text, len+1);
16517     commentList[index][len+2] = NULLCHAR;
16518     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16519     strcat(commentList[index], "\n}\n");
16520   }
16521 }
16522
16523 void
16524 CrushCRs (char *text)
16525 {
16526   char *p = text;
16527   char *q = text;
16528   char ch;
16529
16530   do {
16531     ch = *p++;
16532     if (ch == '\r') continue;
16533     *q++ = ch;
16534   } while (ch != '\0');
16535 }
16536
16537 void
16538 AppendComment (int index, char *text, Boolean addBraces)
16539 /* addBraces  tells if we should add {} */
16540 {
16541     int oldlen, len;
16542     char *old;
16543
16544 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16545     if(addBraces == 3) addBraces = 0; else // force appending literally
16546     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16547
16548     CrushCRs(text);
16549     while (*text == '\n') text++;
16550     len = strlen(text);
16551     while (len > 0 && text[len - 1] == '\n') len--;
16552     text[len] = NULLCHAR;
16553
16554     if (len == 0) return;
16555
16556     if (commentList[index] != NULL) {
16557       Boolean addClosingBrace = addBraces;
16558         old = commentList[index];
16559         oldlen = strlen(old);
16560         while(commentList[index][oldlen-1] ==  '\n')
16561           commentList[index][--oldlen] = NULLCHAR;
16562         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16563         safeStrCpy(commentList[index], old, oldlen + len + 6);
16564         free(old);
16565         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16566         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16567           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16568           while (*text == '\n') { text++; len--; }
16569           commentList[index][--oldlen] = NULLCHAR;
16570       }
16571         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16572         else          strcat(commentList[index], "\n");
16573         strcat(commentList[index], text);
16574         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16575         else          strcat(commentList[index], "\n");
16576     } else {
16577         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16578         if(addBraces)
16579           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16580         else commentList[index][0] = NULLCHAR;
16581         strcat(commentList[index], text);
16582         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16583         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16584     }
16585 }
16586
16587 static char *
16588 FindStr (char * text, char * sub_text)
16589 {
16590     char * result = strstr( text, sub_text );
16591
16592     if( result != NULL ) {
16593         result += strlen( sub_text );
16594     }
16595
16596     return result;
16597 }
16598
16599 /* [AS] Try to extract PV info from PGN comment */
16600 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16601 char *
16602 GetInfoFromComment (int index, char * text)
16603 {
16604     char * sep = text, *p;
16605
16606     if( text != NULL && index > 0 ) {
16607         int score = 0;
16608         int depth = 0;
16609         int time = -1, sec = 0, deci;
16610         char * s_eval = FindStr( text, "[%eval " );
16611         char * s_emt = FindStr( text, "[%emt " );
16612 #if 0
16613         if( s_eval != NULL || s_emt != NULL ) {
16614 #else
16615         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16616 #endif
16617             /* New style */
16618             char delim;
16619
16620             if( s_eval != NULL ) {
16621                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16622                     return text;
16623                 }
16624
16625                 if( delim != ']' ) {
16626                     return text;
16627                 }
16628             }
16629
16630             if( s_emt != NULL ) {
16631             }
16632                 return text;
16633         }
16634         else {
16635             /* We expect something like: [+|-]nnn.nn/dd */
16636             int score_lo = 0;
16637
16638             if(*text != '{') return text; // [HGM] braces: must be normal comment
16639
16640             sep = strchr( text, '/' );
16641             if( sep == NULL || sep < (text+4) ) {
16642                 return text;
16643             }
16644
16645             p = text;
16646             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16647             if(p[1] == '(') { // comment starts with PV
16648                p = strchr(p, ')'); // locate end of PV
16649                if(p == NULL || sep < p+5) return text;
16650                // at this point we have something like "{(.*) +0.23/6 ..."
16651                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16652                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16653                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16654             }
16655             time = -1; sec = -1; deci = -1;
16656             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16657                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16658                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16659                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16660                 return text;
16661             }
16662
16663             if( score_lo < 0 || score_lo >= 100 ) {
16664                 return text;
16665             }
16666
16667             if(sec >= 0) time = 600*time + 10*sec; else
16668             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16669
16670             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16671
16672             /* [HGM] PV time: now locate end of PV info */
16673             while( *++sep >= '0' && *sep <= '9'); // strip depth
16674             if(time >= 0)
16675             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16676             if(sec >= 0)
16677             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16678             if(deci >= 0)
16679             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16680             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16681         }
16682
16683         if( depth <= 0 ) {
16684             return text;
16685         }
16686
16687         if( time < 0 ) {
16688             time = -1;
16689         }
16690
16691         pvInfoList[index-1].depth = depth;
16692         pvInfoList[index-1].score = score;
16693         pvInfoList[index-1].time  = 10*time; // centi-sec
16694         if(*sep == '}') *sep = 0; else *--sep = '{';
16695         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16696     }
16697     return sep;
16698 }
16699
16700 void
16701 SendToProgram (char *message, ChessProgramState *cps)
16702 {
16703     int count, outCount, error;
16704     char buf[MSG_SIZ];
16705
16706     if (cps->pr == NoProc) return;
16707     Attention(cps);
16708
16709     if (appData.debugMode) {
16710         TimeMark now;
16711         GetTimeMark(&now);
16712         fprintf(debugFP, "%ld >%-6s: %s",
16713                 SubtractTimeMarks(&now, &programStartTime),
16714                 cps->which, message);
16715         if(serverFP)
16716             fprintf(serverFP, "%ld >%-6s: %s",
16717                 SubtractTimeMarks(&now, &programStartTime),
16718                 cps->which, message), fflush(serverFP);
16719     }
16720
16721     count = strlen(message);
16722     outCount = OutputToProcess(cps->pr, message, count, &error);
16723     if (outCount < count && !exiting
16724                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16725       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16726       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16727         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16728             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16729                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16730                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16731                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16732             } else {
16733                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16734                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16735                 gameInfo.result = res;
16736             }
16737             gameInfo.resultDetails = StrSave(buf);
16738         }
16739         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16740         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16741     }
16742 }
16743
16744 void
16745 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16746 {
16747     char *end_str;
16748     char buf[MSG_SIZ];
16749     ChessProgramState *cps = (ChessProgramState *)closure;
16750
16751     if (isr != cps->isr) return; /* Killed intentionally */
16752     if (count <= 0) {
16753         if (count == 0) {
16754             RemoveInputSource(cps->isr);
16755             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16756                     _(cps->which), cps->program);
16757             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16758             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16759                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16760                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16761                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16762                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16763                 } else {
16764                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16765                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16766                     gameInfo.result = res;
16767                 }
16768                 gameInfo.resultDetails = StrSave(buf);
16769             }
16770             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16771             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16772         } else {
16773             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16774                     _(cps->which), cps->program);
16775             RemoveInputSource(cps->isr);
16776
16777             /* [AS] Program is misbehaving badly... kill it */
16778             if( count == -2 ) {
16779                 DestroyChildProcess( cps->pr, 9 );
16780                 cps->pr = NoProc;
16781             }
16782
16783             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16784         }
16785         return;
16786     }
16787
16788     if ((end_str = strchr(message, '\r')) != NULL)
16789       *end_str = NULLCHAR;
16790     if ((end_str = strchr(message, '\n')) != NULL)
16791       *end_str = NULLCHAR;
16792
16793     if (appData.debugMode) {
16794         TimeMark now; int print = 1;
16795         char *quote = ""; char c; int i;
16796
16797         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16798                 char start = message[0];
16799                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16800                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16801                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16802                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16803                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16804                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16805                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16806                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16807                    sscanf(message, "hint: %c", &c)!=1 &&
16808                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16809                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16810                     print = (appData.engineComments >= 2);
16811                 }
16812                 message[0] = start; // restore original message
16813         }
16814         if(print) {
16815                 GetTimeMark(&now);
16816                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16817                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16818                         quote,
16819                         message);
16820                 if(serverFP)
16821                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16822                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16823                         quote,
16824                         message), fflush(serverFP);
16825         }
16826     }
16827
16828     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16829     if (appData.icsEngineAnalyze) {
16830         if (strstr(message, "whisper") != NULL ||
16831              strstr(message, "kibitz") != NULL ||
16832             strstr(message, "tellics") != NULL) return;
16833     }
16834
16835     HandleMachineMove(message, cps);
16836 }
16837
16838
16839 void
16840 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16841 {
16842     char buf[MSG_SIZ];
16843     int seconds;
16844
16845     if( timeControl_2 > 0 ) {
16846         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16847             tc = timeControl_2;
16848         }
16849     }
16850     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16851     inc /= cps->timeOdds;
16852     st  /= cps->timeOdds;
16853
16854     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16855
16856     if (st > 0) {
16857       /* Set exact time per move, normally using st command */
16858       if (cps->stKludge) {
16859         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16860         seconds = st % 60;
16861         if (seconds == 0) {
16862           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16863         } else {
16864           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16865         }
16866       } else {
16867         snprintf(buf, MSG_SIZ, "st %d\n", st);
16868       }
16869     } else {
16870       /* Set conventional or incremental time control, using level command */
16871       if (seconds == 0) {
16872         /* Note old gnuchess bug -- minutes:seconds used to not work.
16873            Fixed in later versions, but still avoid :seconds
16874            when seconds is 0. */
16875         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16876       } else {
16877         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16878                  seconds, inc/1000.);
16879       }
16880     }
16881     SendToProgram(buf, cps);
16882
16883     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16884     /* Orthogonally, limit search to given depth */
16885     if (sd > 0) {
16886       if (cps->sdKludge) {
16887         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16888       } else {
16889         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16890       }
16891       SendToProgram(buf, cps);
16892     }
16893
16894     if(cps->nps >= 0) { /* [HGM] nps */
16895         if(cps->supportsNPS == FALSE)
16896           cps->nps = -1; // don't use if engine explicitly says not supported!
16897         else {
16898           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16899           SendToProgram(buf, cps);
16900         }
16901     }
16902 }
16903
16904 ChessProgramState *
16905 WhitePlayer ()
16906 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16907 {
16908     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16909        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16910         return &second;
16911     return &first;
16912 }
16913
16914 void
16915 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16916 {
16917     char message[MSG_SIZ];
16918     long time, otime;
16919
16920     /* Note: this routine must be called when the clocks are stopped
16921        or when they have *just* been set or switched; otherwise
16922        it will be off by the time since the current tick started.
16923     */
16924     if (machineWhite) {
16925         time = whiteTimeRemaining / 10;
16926         otime = blackTimeRemaining / 10;
16927     } else {
16928         time = blackTimeRemaining / 10;
16929         otime = whiteTimeRemaining / 10;
16930     }
16931     /* [HGM] translate opponent's time by time-odds factor */
16932     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16933
16934     if (time <= 0) time = 1;
16935     if (otime <= 0) otime = 1;
16936
16937     snprintf(message, MSG_SIZ, "time %ld\n", time);
16938     SendToProgram(message, cps);
16939
16940     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16941     SendToProgram(message, cps);
16942 }
16943
16944 char *
16945 EngineDefinedVariant (ChessProgramState *cps, int n)
16946 {   // return name of n-th unknown variant that engine supports
16947     static char buf[MSG_SIZ];
16948     char *p, *s = cps->variants;
16949     if(!s) return NULL;
16950     do { // parse string from variants feature
16951       VariantClass v;
16952         p = strchr(s, ',');
16953         if(p) *p = NULLCHAR;
16954       v = StringToVariant(s);
16955       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16956         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16957             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16958                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16959                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16960                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16961             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16962         }
16963         if(p) *p++ = ',';
16964         if(n < 0) return buf;
16965     } while(s = p);
16966     return NULL;
16967 }
16968
16969 int
16970 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16971 {
16972   char buf[MSG_SIZ];
16973   int len = strlen(name);
16974   int val;
16975
16976   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16977     (*p) += len + 1;
16978     sscanf(*p, "%d", &val);
16979     *loc = (val != 0);
16980     while (**p && **p != ' ')
16981       (*p)++;
16982     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16983     SendToProgram(buf, cps);
16984     return TRUE;
16985   }
16986   return FALSE;
16987 }
16988
16989 int
16990 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16991 {
16992   char buf[MSG_SIZ];
16993   int len = strlen(name);
16994   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16995     (*p) += len + 1;
16996     sscanf(*p, "%d", loc);
16997     while (**p && **p != ' ') (*p)++;
16998     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16999     SendToProgram(buf, cps);
17000     return TRUE;
17001   }
17002   return FALSE;
17003 }
17004
17005 int
17006 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17007 {
17008   char buf[MSG_SIZ];
17009   int len = strlen(name);
17010   if (strncmp((*p), name, len) == 0
17011       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17012     (*p) += len + 2;
17013     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17014     sscanf(*p, "%[^\"]", *loc);
17015     while (**p && **p != '\"') (*p)++;
17016     if (**p == '\"') (*p)++;
17017     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17018     SendToProgram(buf, cps);
17019     return TRUE;
17020   }
17021   return FALSE;
17022 }
17023
17024 int
17025 ParseOption (Option *opt, ChessProgramState *cps)
17026 // [HGM] options: process the string that defines an engine option, and determine
17027 // name, type, default value, and allowed value range
17028 {
17029         char *p, *q, buf[MSG_SIZ];
17030         int n, min = (-1)<<31, max = 1<<31, def;
17031
17032         opt->target = &opt->value;   // OK for spin/slider and checkbox
17033         if(p = strstr(opt->name, " -spin ")) {
17034             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17035             if(max < min) max = min; // enforce consistency
17036             if(def < min) def = min;
17037             if(def > max) def = max;
17038             opt->value = def;
17039             opt->min = min;
17040             opt->max = max;
17041             opt->type = Spin;
17042         } else if((p = strstr(opt->name, " -slider "))) {
17043             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17044             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17045             if(max < min) max = min; // enforce consistency
17046             if(def < min) def = min;
17047             if(def > max) def = max;
17048             opt->value = def;
17049             opt->min = min;
17050             opt->max = max;
17051             opt->type = Spin; // Slider;
17052         } else if((p = strstr(opt->name, " -string "))) {
17053             opt->textValue = p+9;
17054             opt->type = TextBox;
17055             opt->target = &opt->textValue;
17056         } else if((p = strstr(opt->name, " -file "))) {
17057             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17058             opt->target = opt->textValue = p+7;
17059             opt->type = FileName; // FileName;
17060             opt->target = &opt->textValue;
17061         } else if((p = strstr(opt->name, " -path "))) {
17062             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17063             opt->target = opt->textValue = p+7;
17064             opt->type = PathName; // PathName;
17065             opt->target = &opt->textValue;
17066         } else if(p = strstr(opt->name, " -check ")) {
17067             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17068             opt->value = (def != 0);
17069             opt->type = CheckBox;
17070         } else if(p = strstr(opt->name, " -combo ")) {
17071             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17072             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17073             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17074             opt->value = n = 0;
17075             while(q = StrStr(q, " /// ")) {
17076                 n++; *q = 0;    // count choices, and null-terminate each of them
17077                 q += 5;
17078                 if(*q == '*') { // remember default, which is marked with * prefix
17079                     q++;
17080                     opt->value = n;
17081                 }
17082                 cps->comboList[cps->comboCnt++] = q;
17083             }
17084             cps->comboList[cps->comboCnt++] = NULL;
17085             opt->max = n + 1;
17086             opt->type = ComboBox;
17087         } else if(p = strstr(opt->name, " -button")) {
17088             opt->type = Button;
17089         } else if(p = strstr(opt->name, " -save")) {
17090             opt->type = SaveButton;
17091         } else return FALSE;
17092         *p = 0; // terminate option name
17093         // now look if the command-line options define a setting for this engine option.
17094         if(cps->optionSettings && cps->optionSettings[0])
17095             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17096         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17097           snprintf(buf, MSG_SIZ, "option %s", p);
17098                 if(p = strstr(buf, ",")) *p = 0;
17099                 if(q = strchr(buf, '=')) switch(opt->type) {
17100                     case ComboBox:
17101                         for(n=0; n<opt->max; n++)
17102                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17103                         break;
17104                     case TextBox:
17105                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17106                         break;
17107                     case Spin:
17108                     case CheckBox:
17109                         opt->value = atoi(q+1);
17110                     default:
17111                         break;
17112                 }
17113                 strcat(buf, "\n");
17114                 SendToProgram(buf, cps);
17115         }
17116         return TRUE;
17117 }
17118
17119 void
17120 FeatureDone (ChessProgramState *cps, int val)
17121 {
17122   DelayedEventCallback cb = GetDelayedEvent();
17123   if ((cb == InitBackEnd3 && cps == &first) ||
17124       (cb == SettingsMenuIfReady && cps == &second) ||
17125       (cb == LoadEngine) ||
17126       (cb == TwoMachinesEventIfReady)) {
17127     CancelDelayedEvent();
17128     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17129   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17130   cps->initDone = val;
17131   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17132 }
17133
17134 /* Parse feature command from engine */
17135 void
17136 ParseFeatures (char *args, ChessProgramState *cps)
17137 {
17138   char *p = args;
17139   char *q = NULL;
17140   int val;
17141   char buf[MSG_SIZ];
17142
17143   for (;;) {
17144     while (*p == ' ') p++;
17145     if (*p == NULLCHAR) return;
17146
17147     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17148     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17149     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17150     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17151     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17152     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17153     if (BoolFeature(&p, "reuse", &val, cps)) {
17154       /* Engine can disable reuse, but can't enable it if user said no */
17155       if (!val) cps->reuse = FALSE;
17156       continue;
17157     }
17158     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17159     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17160       if (gameMode == TwoMachinesPlay) {
17161         DisplayTwoMachinesTitle();
17162       } else {
17163         DisplayTitle("");
17164       }
17165       continue;
17166     }
17167     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17168     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17169     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17170     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17171     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17172     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17173     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17174     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17175     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17176     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17177     if (IntFeature(&p, "done", &val, cps)) {
17178       FeatureDone(cps, val);
17179       continue;
17180     }
17181     /* Added by Tord: */
17182     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17183     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17184     /* End of additions by Tord */
17185
17186     /* [HGM] added features: */
17187     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17188     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17189     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17190     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17191     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17192     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17193     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17194     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17195         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17196         FREE(cps->option[cps->nrOptions].name);
17197         cps->option[cps->nrOptions].name = q; q = NULL;
17198         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17199           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17200             SendToProgram(buf, cps);
17201             continue;
17202         }
17203         if(cps->nrOptions >= MAX_OPTIONS) {
17204             cps->nrOptions--;
17205             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17206             DisplayError(buf, 0);
17207         }
17208         continue;
17209     }
17210     /* End of additions by HGM */
17211
17212     /* unknown feature: complain and skip */
17213     q = p;
17214     while (*q && *q != '=') q++;
17215     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17216     SendToProgram(buf, cps);
17217     p = q;
17218     if (*p == '=') {
17219       p++;
17220       if (*p == '\"') {
17221         p++;
17222         while (*p && *p != '\"') p++;
17223         if (*p == '\"') p++;
17224       } else {
17225         while (*p && *p != ' ') p++;
17226       }
17227     }
17228   }
17229
17230 }
17231
17232 void
17233 PeriodicUpdatesEvent (int newState)
17234 {
17235     if (newState == appData.periodicUpdates)
17236       return;
17237
17238     appData.periodicUpdates=newState;
17239
17240     /* Display type changes, so update it now */
17241 //    DisplayAnalysis();
17242
17243     /* Get the ball rolling again... */
17244     if (newState) {
17245         AnalysisPeriodicEvent(1);
17246         StartAnalysisClock();
17247     }
17248 }
17249
17250 void
17251 PonderNextMoveEvent (int newState)
17252 {
17253     if (newState == appData.ponderNextMove) return;
17254     if (gameMode == EditPosition) EditPositionDone(TRUE);
17255     if (newState) {
17256         SendToProgram("hard\n", &first);
17257         if (gameMode == TwoMachinesPlay) {
17258             SendToProgram("hard\n", &second);
17259         }
17260     } else {
17261         SendToProgram("easy\n", &first);
17262         thinkOutput[0] = NULLCHAR;
17263         if (gameMode == TwoMachinesPlay) {
17264             SendToProgram("easy\n", &second);
17265         }
17266     }
17267     appData.ponderNextMove = newState;
17268 }
17269
17270 void
17271 NewSettingEvent (int option, int *feature, char *command, int value)
17272 {
17273     char buf[MSG_SIZ];
17274
17275     if (gameMode == EditPosition) EditPositionDone(TRUE);
17276     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17277     if(feature == NULL || *feature) SendToProgram(buf, &first);
17278     if (gameMode == TwoMachinesPlay) {
17279         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17280     }
17281 }
17282
17283 void
17284 ShowThinkingEvent ()
17285 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17286 {
17287     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17288     int newState = appData.showThinking
17289         // [HGM] thinking: other features now need thinking output as well
17290         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17291
17292     if (oldState == newState) return;
17293     oldState = newState;
17294     if (gameMode == EditPosition) EditPositionDone(TRUE);
17295     if (oldState) {
17296         SendToProgram("post\n", &first);
17297         if (gameMode == TwoMachinesPlay) {
17298             SendToProgram("post\n", &second);
17299         }
17300     } else {
17301         SendToProgram("nopost\n", &first);
17302         thinkOutput[0] = NULLCHAR;
17303         if (gameMode == TwoMachinesPlay) {
17304             SendToProgram("nopost\n", &second);
17305         }
17306     }
17307 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17308 }
17309
17310 void
17311 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17312 {
17313   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17314   if (pr == NoProc) return;
17315   AskQuestion(title, question, replyPrefix, pr);
17316 }
17317
17318 void
17319 TypeInEvent (char firstChar)
17320 {
17321     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17322         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17323         gameMode == AnalyzeMode || gameMode == EditGame ||
17324         gameMode == EditPosition || gameMode == IcsExamining ||
17325         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17326         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17327                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17328                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17329         gameMode == Training) PopUpMoveDialog(firstChar);
17330 }
17331
17332 void
17333 TypeInDoneEvent (char *move)
17334 {
17335         Board board;
17336         int n, fromX, fromY, toX, toY;
17337         char promoChar;
17338         ChessMove moveType;
17339
17340         // [HGM] FENedit
17341         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17342                 EditPositionPasteFEN(move);
17343                 return;
17344         }
17345         // [HGM] movenum: allow move number to be typed in any mode
17346         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17347           ToNrEvent(2*n-1);
17348           return;
17349         }
17350         // undocumented kludge: allow command-line option to be typed in!
17351         // (potentially fatal, and does not implement the effect of the option.)
17352         // should only be used for options that are values on which future decisions will be made,
17353         // and definitely not on options that would be used during initialization.
17354         if(strstr(move, "!!! -") == move) {
17355             ParseArgsFromString(move+4);
17356             return;
17357         }
17358
17359       if (gameMode != EditGame && currentMove != forwardMostMove &&
17360         gameMode != Training) {
17361         DisplayMoveError(_("Displayed move is not current"));
17362       } else {
17363         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17364           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17365         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17366         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17367           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17368           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17369         } else {
17370           DisplayMoveError(_("Could not parse move"));
17371         }
17372       }
17373 }
17374
17375 void
17376 DisplayMove (int moveNumber)
17377 {
17378     char message[MSG_SIZ];
17379     char res[MSG_SIZ];
17380     char cpThinkOutput[MSG_SIZ];
17381
17382     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17383
17384     if (moveNumber == forwardMostMove - 1 ||
17385         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17386
17387         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17388
17389         if (strchr(cpThinkOutput, '\n')) {
17390             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17391         }
17392     } else {
17393         *cpThinkOutput = NULLCHAR;
17394     }
17395
17396     /* [AS] Hide thinking from human user */
17397     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17398         *cpThinkOutput = NULLCHAR;
17399         if( thinkOutput[0] != NULLCHAR ) {
17400             int i;
17401
17402             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17403                 cpThinkOutput[i] = '.';
17404             }
17405             cpThinkOutput[i] = NULLCHAR;
17406             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17407         }
17408     }
17409
17410     if (moveNumber == forwardMostMove - 1 &&
17411         gameInfo.resultDetails != NULL) {
17412         if (gameInfo.resultDetails[0] == NULLCHAR) {
17413           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17414         } else {
17415           snprintf(res, MSG_SIZ, " {%s} %s",
17416                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17417         }
17418     } else {
17419         res[0] = NULLCHAR;
17420     }
17421
17422     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17423         DisplayMessage(res, cpThinkOutput);
17424     } else {
17425       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17426                 WhiteOnMove(moveNumber) ? " " : ".. ",
17427                 parseList[moveNumber], res);
17428         DisplayMessage(message, cpThinkOutput);
17429     }
17430 }
17431
17432 void
17433 DisplayComment (int moveNumber, char *text)
17434 {
17435     char title[MSG_SIZ];
17436
17437     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17438       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17439     } else {
17440       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17441               WhiteOnMove(moveNumber) ? " " : ".. ",
17442               parseList[moveNumber]);
17443     }
17444     if (text != NULL && (appData.autoDisplayComment || commentUp))
17445         CommentPopUp(title, text);
17446 }
17447
17448 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17449  * might be busy thinking or pondering.  It can be omitted if your
17450  * gnuchess is configured to stop thinking immediately on any user
17451  * input.  However, that gnuchess feature depends on the FIONREAD
17452  * ioctl, which does not work properly on some flavors of Unix.
17453  */
17454 void
17455 Attention (ChessProgramState *cps)
17456 {
17457 #if ATTENTION
17458     if (!cps->useSigint) return;
17459     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17460     switch (gameMode) {
17461       case MachinePlaysWhite:
17462       case MachinePlaysBlack:
17463       case TwoMachinesPlay:
17464       case IcsPlayingWhite:
17465       case IcsPlayingBlack:
17466       case AnalyzeMode:
17467       case AnalyzeFile:
17468         /* Skip if we know it isn't thinking */
17469         if (!cps->maybeThinking) return;
17470         if (appData.debugMode)
17471           fprintf(debugFP, "Interrupting %s\n", cps->which);
17472         InterruptChildProcess(cps->pr);
17473         cps->maybeThinking = FALSE;
17474         break;
17475       default:
17476         break;
17477     }
17478 #endif /*ATTENTION*/
17479 }
17480
17481 int
17482 CheckFlags ()
17483 {
17484     if (whiteTimeRemaining <= 0) {
17485         if (!whiteFlag) {
17486             whiteFlag = TRUE;
17487             if (appData.icsActive) {
17488                 if (appData.autoCallFlag &&
17489                     gameMode == IcsPlayingBlack && !blackFlag) {
17490                   SendToICS(ics_prefix);
17491                   SendToICS("flag\n");
17492                 }
17493             } else {
17494                 if (blackFlag) {
17495                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17496                 } else {
17497                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17498                     if (appData.autoCallFlag) {
17499                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17500                         return TRUE;
17501                     }
17502                 }
17503             }
17504         }
17505     }
17506     if (blackTimeRemaining <= 0) {
17507         if (!blackFlag) {
17508             blackFlag = TRUE;
17509             if (appData.icsActive) {
17510                 if (appData.autoCallFlag &&
17511                     gameMode == IcsPlayingWhite && !whiteFlag) {
17512                   SendToICS(ics_prefix);
17513                   SendToICS("flag\n");
17514                 }
17515             } else {
17516                 if (whiteFlag) {
17517                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17518                 } else {
17519                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17520                     if (appData.autoCallFlag) {
17521                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17522                         return TRUE;
17523                     }
17524                 }
17525             }
17526         }
17527     }
17528     return FALSE;
17529 }
17530
17531 void
17532 CheckTimeControl ()
17533 {
17534     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17535         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17536
17537     /*
17538      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17539      */
17540     if ( !WhiteOnMove(forwardMostMove) ) {
17541         /* White made time control */
17542         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17543         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17544         /* [HGM] time odds: correct new time quota for time odds! */
17545                                             / WhitePlayer()->timeOdds;
17546         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17547     } else {
17548         lastBlack -= blackTimeRemaining;
17549         /* Black made time control */
17550         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17551                                             / WhitePlayer()->other->timeOdds;
17552         lastWhite = whiteTimeRemaining;
17553     }
17554 }
17555
17556 void
17557 DisplayBothClocks ()
17558 {
17559     int wom = gameMode == EditPosition ?
17560       !blackPlaysFirst : WhiteOnMove(currentMove);
17561     DisplayWhiteClock(whiteTimeRemaining, wom);
17562     DisplayBlackClock(blackTimeRemaining, !wom);
17563 }
17564
17565
17566 /* Timekeeping seems to be a portability nightmare.  I think everyone
17567    has ftime(), but I'm really not sure, so I'm including some ifdefs
17568    to use other calls if you don't.  Clocks will be less accurate if
17569    you have neither ftime nor gettimeofday.
17570 */
17571
17572 /* VS 2008 requires the #include outside of the function */
17573 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17574 #include <sys/timeb.h>
17575 #endif
17576
17577 /* Get the current time as a TimeMark */
17578 void
17579 GetTimeMark (TimeMark *tm)
17580 {
17581 #if HAVE_GETTIMEOFDAY
17582
17583     struct timeval timeVal;
17584     struct timezone timeZone;
17585
17586     gettimeofday(&timeVal, &timeZone);
17587     tm->sec = (long) timeVal.tv_sec;
17588     tm->ms = (int) (timeVal.tv_usec / 1000L);
17589
17590 #else /*!HAVE_GETTIMEOFDAY*/
17591 #if HAVE_FTIME
17592
17593 // include <sys/timeb.h> / moved to just above start of function
17594     struct timeb timeB;
17595
17596     ftime(&timeB);
17597     tm->sec = (long) timeB.time;
17598     tm->ms = (int) timeB.millitm;
17599
17600 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17601     tm->sec = (long) time(NULL);
17602     tm->ms = 0;
17603 #endif
17604 #endif
17605 }
17606
17607 /* Return the difference in milliseconds between two
17608    time marks.  We assume the difference will fit in a long!
17609 */
17610 long
17611 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17612 {
17613     return 1000L*(tm2->sec - tm1->sec) +
17614            (long) (tm2->ms - tm1->ms);
17615 }
17616
17617
17618 /*
17619  * Code to manage the game clocks.
17620  *
17621  * In tournament play, black starts the clock and then white makes a move.
17622  * We give the human user a slight advantage if he is playing white---the
17623  * clocks don't run until he makes his first move, so it takes zero time.
17624  * Also, we don't account for network lag, so we could get out of sync
17625  * with GNU Chess's clock -- but then, referees are always right.
17626  */
17627
17628 static TimeMark tickStartTM;
17629 static long intendedTickLength;
17630
17631 long
17632 NextTickLength (long timeRemaining)
17633 {
17634     long nominalTickLength, nextTickLength;
17635
17636     if (timeRemaining > 0L && timeRemaining <= 10000L)
17637       nominalTickLength = 100L;
17638     else
17639       nominalTickLength = 1000L;
17640     nextTickLength = timeRemaining % nominalTickLength;
17641     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17642
17643     return nextTickLength;
17644 }
17645
17646 /* Adjust clock one minute up or down */
17647 void
17648 AdjustClock (Boolean which, int dir)
17649 {
17650     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17651     if(which) blackTimeRemaining += 60000*dir;
17652     else      whiteTimeRemaining += 60000*dir;
17653     DisplayBothClocks();
17654     adjustedClock = TRUE;
17655 }
17656
17657 /* Stop clocks and reset to a fresh time control */
17658 void
17659 ResetClocks ()
17660 {
17661     (void) StopClockTimer();
17662     if (appData.icsActive) {
17663         whiteTimeRemaining = blackTimeRemaining = 0;
17664     } else if (searchTime) {
17665         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17666         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17667     } else { /* [HGM] correct new time quote for time odds */
17668         whiteTC = blackTC = fullTimeControlString;
17669         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17670         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17671     }
17672     if (whiteFlag || blackFlag) {
17673         DisplayTitle("");
17674         whiteFlag = blackFlag = FALSE;
17675     }
17676     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17677     DisplayBothClocks();
17678     adjustedClock = FALSE;
17679 }
17680
17681 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17682
17683 /* Decrement running clock by amount of time that has passed */
17684 void
17685 DecrementClocks ()
17686 {
17687     long timeRemaining;
17688     long lastTickLength, fudge;
17689     TimeMark now;
17690
17691     if (!appData.clockMode) return;
17692     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17693
17694     GetTimeMark(&now);
17695
17696     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17697
17698     /* Fudge if we woke up a little too soon */
17699     fudge = intendedTickLength - lastTickLength;
17700     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17701
17702     if (WhiteOnMove(forwardMostMove)) {
17703         if(whiteNPS >= 0) lastTickLength = 0;
17704         timeRemaining = whiteTimeRemaining -= lastTickLength;
17705         if(timeRemaining < 0 && !appData.icsActive) {
17706             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17707             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17708                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17709                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17710             }
17711         }
17712         DisplayWhiteClock(whiteTimeRemaining - fudge,
17713                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17714     } else {
17715         if(blackNPS >= 0) lastTickLength = 0;
17716         timeRemaining = blackTimeRemaining -= lastTickLength;
17717         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17718             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17719             if(suddenDeath) {
17720                 blackStartMove = forwardMostMove;
17721                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17722             }
17723         }
17724         DisplayBlackClock(blackTimeRemaining - fudge,
17725                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17726     }
17727     if (CheckFlags()) return;
17728
17729     if(twoBoards) { // count down secondary board's clocks as well
17730         activePartnerTime -= lastTickLength;
17731         partnerUp = 1;
17732         if(activePartner == 'W')
17733             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17734         else
17735             DisplayBlackClock(activePartnerTime, TRUE);
17736         partnerUp = 0;
17737     }
17738
17739     tickStartTM = now;
17740     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17741     StartClockTimer(intendedTickLength);
17742
17743     /* if the time remaining has fallen below the alarm threshold, sound the
17744      * alarm. if the alarm has sounded and (due to a takeback or time control
17745      * with increment) the time remaining has increased to a level above the
17746      * threshold, reset the alarm so it can sound again.
17747      */
17748
17749     if (appData.icsActive && appData.icsAlarm) {
17750
17751         /* make sure we are dealing with the user's clock */
17752         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17753                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17754            )) return;
17755
17756         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17757             alarmSounded = FALSE;
17758         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17759             PlayAlarmSound();
17760             alarmSounded = TRUE;
17761         }
17762     }
17763 }
17764
17765
17766 /* A player has just moved, so stop the previously running
17767    clock and (if in clock mode) start the other one.
17768    We redisplay both clocks in case we're in ICS mode, because
17769    ICS gives us an update to both clocks after every move.
17770    Note that this routine is called *after* forwardMostMove
17771    is updated, so the last fractional tick must be subtracted
17772    from the color that is *not* on move now.
17773 */
17774 void
17775 SwitchClocks (int newMoveNr)
17776 {
17777     long lastTickLength;
17778     TimeMark now;
17779     int flagged = FALSE;
17780
17781     GetTimeMark(&now);
17782
17783     if (StopClockTimer() && appData.clockMode) {
17784         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17785         if (!WhiteOnMove(forwardMostMove)) {
17786             if(blackNPS >= 0) lastTickLength = 0;
17787             blackTimeRemaining -= lastTickLength;
17788            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17789 //         if(pvInfoList[forwardMostMove].time == -1)
17790                  pvInfoList[forwardMostMove].time =               // use GUI time
17791                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17792         } else {
17793            if(whiteNPS >= 0) lastTickLength = 0;
17794            whiteTimeRemaining -= lastTickLength;
17795            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17796 //         if(pvInfoList[forwardMostMove].time == -1)
17797                  pvInfoList[forwardMostMove].time =
17798                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17799         }
17800         flagged = CheckFlags();
17801     }
17802     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17803     CheckTimeControl();
17804
17805     if (flagged || !appData.clockMode) return;
17806
17807     switch (gameMode) {
17808       case MachinePlaysBlack:
17809       case MachinePlaysWhite:
17810       case BeginningOfGame:
17811         if (pausing) return;
17812         break;
17813
17814       case EditGame:
17815       case PlayFromGameFile:
17816       case IcsExamining:
17817         return;
17818
17819       default:
17820         break;
17821     }
17822
17823     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17824         if(WhiteOnMove(forwardMostMove))
17825              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17826         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17827     }
17828
17829     tickStartTM = now;
17830     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17831       whiteTimeRemaining : blackTimeRemaining);
17832     StartClockTimer(intendedTickLength);
17833 }
17834
17835
17836 /* Stop both clocks */
17837 void
17838 StopClocks ()
17839 {
17840     long lastTickLength;
17841     TimeMark now;
17842
17843     if (!StopClockTimer()) return;
17844     if (!appData.clockMode) return;
17845
17846     GetTimeMark(&now);
17847
17848     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17849     if (WhiteOnMove(forwardMostMove)) {
17850         if(whiteNPS >= 0) lastTickLength = 0;
17851         whiteTimeRemaining -= lastTickLength;
17852         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17853     } else {
17854         if(blackNPS >= 0) lastTickLength = 0;
17855         blackTimeRemaining -= lastTickLength;
17856         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17857     }
17858     CheckFlags();
17859 }
17860
17861 /* Start clock of player on move.  Time may have been reset, so
17862    if clock is already running, stop and restart it. */
17863 void
17864 StartClocks ()
17865 {
17866     (void) StopClockTimer(); /* in case it was running already */
17867     DisplayBothClocks();
17868     if (CheckFlags()) return;
17869
17870     if (!appData.clockMode) return;
17871     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17872
17873     GetTimeMark(&tickStartTM);
17874     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17875       whiteTimeRemaining : blackTimeRemaining);
17876
17877    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17878     whiteNPS = blackNPS = -1;
17879     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17880        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17881         whiteNPS = first.nps;
17882     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17883        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17884         blackNPS = first.nps;
17885     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17886         whiteNPS = second.nps;
17887     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17888         blackNPS = second.nps;
17889     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17890
17891     StartClockTimer(intendedTickLength);
17892 }
17893
17894 char *
17895 TimeString (long ms)
17896 {
17897     long second, minute, hour, day;
17898     char *sign = "";
17899     static char buf[32];
17900
17901     if (ms > 0 && ms <= 9900) {
17902       /* convert milliseconds to tenths, rounding up */
17903       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17904
17905       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17906       return buf;
17907     }
17908
17909     /* convert milliseconds to seconds, rounding up */
17910     /* use floating point to avoid strangeness of integer division
17911        with negative dividends on many machines */
17912     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17913
17914     if (second < 0) {
17915         sign = "-";
17916         second = -second;
17917     }
17918
17919     day = second / (60 * 60 * 24);
17920     second = second % (60 * 60 * 24);
17921     hour = second / (60 * 60);
17922     second = second % (60 * 60);
17923     minute = second / 60;
17924     second = second % 60;
17925
17926     if (day > 0)
17927       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17928               sign, day, hour, minute, second);
17929     else if (hour > 0)
17930       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17931     else
17932       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17933
17934     return buf;
17935 }
17936
17937
17938 /*
17939  * This is necessary because some C libraries aren't ANSI C compliant yet.
17940  */
17941 char *
17942 StrStr (char *string, char *match)
17943 {
17944     int i, length;
17945
17946     length = strlen(match);
17947
17948     for (i = strlen(string) - length; i >= 0; i--, string++)
17949       if (!strncmp(match, string, length))
17950         return string;
17951
17952     return NULL;
17953 }
17954
17955 char *
17956 StrCaseStr (char *string, char *match)
17957 {
17958     int i, j, length;
17959
17960     length = strlen(match);
17961
17962     for (i = strlen(string) - length; i >= 0; i--, string++) {
17963         for (j = 0; j < length; j++) {
17964             if (ToLower(match[j]) != ToLower(string[j]))
17965               break;
17966         }
17967         if (j == length) return string;
17968     }
17969
17970     return NULL;
17971 }
17972
17973 #ifndef _amigados
17974 int
17975 StrCaseCmp (char *s1, char *s2)
17976 {
17977     char c1, c2;
17978
17979     for (;;) {
17980         c1 = ToLower(*s1++);
17981         c2 = ToLower(*s2++);
17982         if (c1 > c2) return 1;
17983         if (c1 < c2) return -1;
17984         if (c1 == NULLCHAR) return 0;
17985     }
17986 }
17987
17988
17989 int
17990 ToLower (int c)
17991 {
17992     return isupper(c) ? tolower(c) : c;
17993 }
17994
17995
17996 int
17997 ToUpper (int c)
17998 {
17999     return islower(c) ? toupper(c) : c;
18000 }
18001 #endif /* !_amigados    */
18002
18003 char *
18004 StrSave (char *s)
18005 {
18006   char *ret;
18007
18008   if ((ret = (char *) malloc(strlen(s) + 1)))
18009     {
18010       safeStrCpy(ret, s, strlen(s)+1);
18011     }
18012   return ret;
18013 }
18014
18015 char *
18016 StrSavePtr (char *s, char **savePtr)
18017 {
18018     if (*savePtr) {
18019         free(*savePtr);
18020     }
18021     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18022       safeStrCpy(*savePtr, s, strlen(s)+1);
18023     }
18024     return(*savePtr);
18025 }
18026
18027 char *
18028 PGNDate ()
18029 {
18030     time_t clock;
18031     struct tm *tm;
18032     char buf[MSG_SIZ];
18033
18034     clock = time((time_t *)NULL);
18035     tm = localtime(&clock);
18036     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18037             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18038     return StrSave(buf);
18039 }
18040
18041
18042 char *
18043 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18044 {
18045     int i, j, fromX, fromY, toX, toY;
18046     int whiteToPlay, haveRights = nrCastlingRights;
18047     char buf[MSG_SIZ];
18048     char *p, *q;
18049     int emptycount;
18050     ChessSquare piece;
18051
18052     whiteToPlay = (gameMode == EditPosition) ?
18053       !blackPlaysFirst : (move % 2 == 0);
18054     p = buf;
18055
18056     /* Piece placement data */
18057     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18058         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18059         emptycount = 0;
18060         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18061             if (boards[move][i][j] == EmptySquare) {
18062                 emptycount++;
18063             } else { ChessSquare piece = boards[move][i][j];
18064                 if (emptycount > 0) {
18065                     if(emptycount<10) /* [HGM] can be >= 10 */
18066                         *p++ = '0' + emptycount;
18067                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18068                     emptycount = 0;
18069                 }
18070                 if(PieceToChar(piece) == '+') {
18071                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18072                     *p++ = '+';
18073                     piece = (ChessSquare)(CHUDEMOTED(piece));
18074                 }
18075                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18076                 if(*p = PieceSuffix(piece)) p++;
18077                 if(p[-1] == '~') {
18078                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18079                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18080                     *p++ = '~';
18081                 }
18082             }
18083         }
18084         if (emptycount > 0) {
18085             if(emptycount<10) /* [HGM] can be >= 10 */
18086                 *p++ = '0' + emptycount;
18087             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18088             emptycount = 0;
18089         }
18090         *p++ = '/';
18091     }
18092     *(p - 1) = ' ';
18093
18094     /* [HGM] print Crazyhouse or Shogi holdings */
18095     if( gameInfo.holdingsWidth ) {
18096         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18097         q = p;
18098         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18099             piece = boards[move][i][BOARD_WIDTH-1];
18100             if( piece != EmptySquare )
18101               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18102                   *p++ = PieceToChar(piece);
18103         }
18104         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18105             piece = boards[move][BOARD_HEIGHT-i-1][0];
18106             if( piece != EmptySquare )
18107               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18108                   *p++ = PieceToChar(piece);
18109         }
18110
18111         if( q == p ) *p++ = '-';
18112         *p++ = ']';
18113         *p++ = ' ';
18114     }
18115
18116     /* Active color */
18117     *p++ = whiteToPlay ? 'w' : 'b';
18118     *p++ = ' ';
18119
18120   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18121     haveRights = 0; q = p;
18122     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18123       piece = boards[move][0][i];
18124       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18125         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18126       }
18127     }
18128     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18129       piece = boards[move][BOARD_HEIGHT-1][i];
18130       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18131         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18132       }
18133     }
18134     if(p == q) *p++ = '-';
18135     *p++ = ' ';
18136   }
18137
18138   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18139     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18140   } else {
18141   if(haveRights) {
18142      int handW=0, handB=0;
18143      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18144         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18145         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18146      }
18147      q = p;
18148      if(appData.fischerCastling) {
18149         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18150            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18151                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18152         } else {
18153        /* [HGM] write directly from rights */
18154            if(boards[move][CASTLING][2] != NoRights &&
18155               boards[move][CASTLING][0] != NoRights   )
18156                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18157            if(boards[move][CASTLING][2] != NoRights &&
18158               boards[move][CASTLING][1] != NoRights   )
18159                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18160         }
18161         if(handB) {
18162            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18163                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18164         } else {
18165            if(boards[move][CASTLING][5] != NoRights &&
18166               boards[move][CASTLING][3] != NoRights   )
18167                 *p++ = boards[move][CASTLING][3] + AAA;
18168            if(boards[move][CASTLING][5] != NoRights &&
18169               boards[move][CASTLING][4] != NoRights   )
18170                 *p++ = boards[move][CASTLING][4] + AAA;
18171         }
18172      } else {
18173
18174         /* [HGM] write true castling rights */
18175         if( nrCastlingRights == 6 ) {
18176             int q, k=0;
18177             if(boards[move][CASTLING][0] != NoRights &&
18178                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18179             q = (boards[move][CASTLING][1] != NoRights &&
18180                  boards[move][CASTLING][2] != NoRights  );
18181             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18182                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18183                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18184                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18185             }
18186             if(q) *p++ = 'Q';
18187             k = 0;
18188             if(boards[move][CASTLING][3] != NoRights &&
18189                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18190             q = (boards[move][CASTLING][4] != NoRights &&
18191                  boards[move][CASTLING][5] != NoRights  );
18192             if(handB) {
18193                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18194                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18195                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18196             }
18197             if(q) *p++ = 'q';
18198         }
18199      }
18200      if (q == p) *p++ = '-'; /* No castling rights */
18201      *p++ = ' ';
18202   }
18203
18204   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18205      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18206      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18207     /* En passant target square */
18208     if (move > backwardMostMove) {
18209         fromX = moveList[move - 1][0] - AAA;
18210         fromY = moveList[move - 1][1] - ONE;
18211         toX = moveList[move - 1][2] - AAA;
18212         toY = moveList[move - 1][3] - ONE;
18213         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18214             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18215             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18216             fromX == toX) {
18217             /* 2-square pawn move just happened */
18218             *p++ = toX + AAA;
18219             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18220         } else {
18221             *p++ = '-';
18222         }
18223     } else if(move == backwardMostMove) {
18224         // [HGM] perhaps we should always do it like this, and forget the above?
18225         if((signed char)boards[move][EP_STATUS] >= 0) {
18226             *p++ = boards[move][EP_STATUS] + AAA;
18227             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18228         } else {
18229             *p++ = '-';
18230         }
18231     } else {
18232         *p++ = '-';
18233     }
18234     *p++ = ' ';
18235   }
18236   }
18237
18238     if(moveCounts)
18239     {   int i = 0, j=move;
18240
18241         /* [HGM] find reversible plies */
18242         if (appData.debugMode) { int k;
18243             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18244             for(k=backwardMostMove; k<=forwardMostMove; k++)
18245                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18246
18247         }
18248
18249         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18250         if( j == backwardMostMove ) i += initialRulePlies;
18251         sprintf(p, "%d ", i);
18252         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18253
18254         /* Fullmove number */
18255         sprintf(p, "%d", (move / 2) + 1);
18256     } else *--p = NULLCHAR;
18257
18258     return StrSave(buf);
18259 }
18260
18261 Boolean
18262 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18263 {
18264     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18265     char *p, c;
18266     int emptycount, virgin[BOARD_FILES];
18267     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18268
18269     p = fen;
18270
18271     /* Piece placement data */
18272     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18273         j = 0;
18274         for (;;) {
18275             if (*p == '/' || *p == ' ' || *p == '[' ) {
18276                 if(j > w) w = j;
18277                 emptycount = gameInfo.boardWidth - j;
18278                 while (emptycount--)
18279                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18280                 if (*p == '/') p++;
18281                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18282                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18283                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18284                     }
18285                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18286                 }
18287                 break;
18288 #if(BOARD_FILES >= 10)*0
18289             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18290                 p++; emptycount=10;
18291                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18292                 while (emptycount--)
18293                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18294 #endif
18295             } else if (*p == '*') {
18296                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18297             } else if (isdigit(*p)) {
18298                 emptycount = *p++ - '0';
18299                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18300                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18301                 while (emptycount--)
18302                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18303             } else if (*p == '<') {
18304                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18305                 else if (i != 0 || !shuffle) return FALSE;
18306                 p++;
18307             } else if (shuffle && *p == '>') {
18308                 p++; // for now ignore closing shuffle range, and assume rank-end
18309             } else if (*p == '?') {
18310                 if (j >= gameInfo.boardWidth) return FALSE;
18311                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18312                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18313             } else if (*p == '+' || isalpha(*p)) {
18314                 char *q, *s = SUFFIXES;
18315                 if (j >= gameInfo.boardWidth) return FALSE;
18316                 if(*p=='+') {
18317                     char c = *++p;
18318                     if(q = strchr(s, p[1])) p++;
18319                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18320                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18321                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18322                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18323                 } else {
18324                     char c = *p++;
18325                     if(q = strchr(s, *p)) p++;
18326                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18327                 }
18328
18329                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18330                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18331                     piece = (ChessSquare) (PROMOTED(piece));
18332                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18333                     p++;
18334                 }
18335                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18336                 if(piece == king) wKingRank = i;
18337                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18338             } else {
18339                 return FALSE;
18340             }
18341         }
18342     }
18343     while (*p == '/' || *p == ' ') p++;
18344
18345     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18346
18347     /* [HGM] by default clear Crazyhouse holdings, if present */
18348     if(gameInfo.holdingsWidth) {
18349        for(i=0; i<BOARD_HEIGHT; i++) {
18350            board[i][0]             = EmptySquare; /* black holdings */
18351            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18352            board[i][1]             = (ChessSquare) 0; /* black counts */
18353            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18354        }
18355     }
18356
18357     /* [HGM] look for Crazyhouse holdings here */
18358     while(*p==' ') p++;
18359     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18360         int swap=0, wcnt=0, bcnt=0;
18361         if(*p == '[') p++;
18362         if(*p == '<') swap++, p++;
18363         if(*p == '-' ) p++; /* empty holdings */ else {
18364             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18365             /* if we would allow FEN reading to set board size, we would   */
18366             /* have to add holdings and shift the board read so far here   */
18367             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18368                 p++;
18369                 if((int) piece >= (int) BlackPawn ) {
18370                     i = (int)piece - (int)BlackPawn;
18371                     i = PieceToNumber((ChessSquare)i);
18372                     if( i >= gameInfo.holdingsSize ) return FALSE;
18373                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18374                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18375                     bcnt++;
18376                 } else {
18377                     i = (int)piece - (int)WhitePawn;
18378                     i = PieceToNumber((ChessSquare)i);
18379                     if( i >= gameInfo.holdingsSize ) return FALSE;
18380                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18381                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18382                     wcnt++;
18383                 }
18384             }
18385             if(subst) { // substitute back-rank question marks by holdings pieces
18386                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18387                     int k, m, n = bcnt + 1;
18388                     if(board[0][j] == ClearBoard) {
18389                         if(!wcnt) return FALSE;
18390                         n = rand() % wcnt;
18391                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18392                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18393                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18394                             break;
18395                         }
18396                     }
18397                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18398                         if(!bcnt) return FALSE;
18399                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18400                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18401                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18402                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18403                             break;
18404                         }
18405                     }
18406                 }
18407                 subst = 0;
18408             }
18409         }
18410         if(*p == ']') p++;
18411     }
18412
18413     if(subst) return FALSE; // substitution requested, but no holdings
18414
18415     while(*p == ' ') p++;
18416
18417     /* Active color */
18418     c = *p++;
18419     if(appData.colorNickNames) {
18420       if( c == appData.colorNickNames[0] ) c = 'w'; else
18421       if( c == appData.colorNickNames[1] ) c = 'b';
18422     }
18423     switch (c) {
18424       case 'w':
18425         *blackPlaysFirst = FALSE;
18426         break;
18427       case 'b':
18428         *blackPlaysFirst = TRUE;
18429         break;
18430       default:
18431         return FALSE;
18432     }
18433
18434     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18435     /* return the extra info in global variiables             */
18436
18437     while(*p==' ') p++;
18438
18439     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18440         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18441         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18442     }
18443
18444     /* set defaults in case FEN is incomplete */
18445     board[EP_STATUS] = EP_UNKNOWN;
18446     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18447     for(i=0; i<nrCastlingRights; i++ ) {
18448         board[CASTLING][i] =
18449             appData.fischerCastling ? NoRights : initialRights[i];
18450     }   /* assume possible unless obviously impossible */
18451     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18452     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18453     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18454                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18455     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18456     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18457     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18458                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18459     FENrulePlies = 0;
18460
18461     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18462       char *q = p;
18463       int w=0, b=0;
18464       while(isalpha(*p)) {
18465         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18466         if(islower(*p)) b |= 1 << (*p++ - 'a');
18467       }
18468       if(*p == '-') p++;
18469       if(p != q) {
18470         board[TOUCHED_W] = ~w;
18471         board[TOUCHED_B] = ~b;
18472         while(*p == ' ') p++;
18473       }
18474     } else
18475
18476     if(nrCastlingRights) {
18477       int fischer = 0;
18478       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18479       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18480           /* castling indicator present, so default becomes no castlings */
18481           for(i=0; i<nrCastlingRights; i++ ) {
18482                  board[CASTLING][i] = NoRights;
18483           }
18484       }
18485       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18486              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18487              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18488              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18489         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18490
18491         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18492             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18493             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18494         }
18495         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18496             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18497         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18498                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18499         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18500                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18501         switch(c) {
18502           case'K':
18503               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18504               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18505               board[CASTLING][2] = whiteKingFile;
18506               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18507               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18508               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18509               break;
18510           case'Q':
18511               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18512               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18513               board[CASTLING][2] = whiteKingFile;
18514               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18515               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18516               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18517               break;
18518           case'k':
18519               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18520               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18521               board[CASTLING][5] = blackKingFile;
18522               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18523               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18524               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18525               break;
18526           case'q':
18527               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18528               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18529               board[CASTLING][5] = blackKingFile;
18530               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18531               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18532               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18533           case '-':
18534               break;
18535           default: /* FRC castlings */
18536               if(c >= 'a') { /* black rights */
18537                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18538                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18539                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18540                   if(i == BOARD_RGHT) break;
18541                   board[CASTLING][5] = i;
18542                   c -= AAA;
18543                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18544                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18545                   if(c > i)
18546                       board[CASTLING][3] = c;
18547                   else
18548                       board[CASTLING][4] = c;
18549               } else { /* white rights */
18550                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18551                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18552                     if(board[0][i] == WhiteKing) break;
18553                   if(i == BOARD_RGHT) break;
18554                   board[CASTLING][2] = i;
18555                   c -= AAA - 'a' + 'A';
18556                   if(board[0][c] >= WhiteKing) break;
18557                   if(c > i)
18558                       board[CASTLING][0] = c;
18559                   else
18560                       board[CASTLING][1] = c;
18561               }
18562         }
18563       }
18564       for(i=0; i<nrCastlingRights; i++)
18565         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18566       if(gameInfo.variant == VariantSChess)
18567         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18568       if(fischer && shuffle) appData.fischerCastling = TRUE;
18569     if (appData.debugMode) {
18570         fprintf(debugFP, "FEN castling rights:");
18571         for(i=0; i<nrCastlingRights; i++)
18572         fprintf(debugFP, " %d", board[CASTLING][i]);
18573         fprintf(debugFP, "\n");
18574     }
18575
18576       while(*p==' ') p++;
18577     }
18578
18579     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18580
18581     /* read e.p. field in games that know e.p. capture */
18582     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18583        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18584        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18585       if(*p=='-') {
18586         p++; board[EP_STATUS] = EP_NONE;
18587       } else {
18588          char c = *p++ - AAA;
18589
18590          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18591          if(*p >= '0' && *p <='9') p++;
18592          board[EP_STATUS] = c;
18593       }
18594     }
18595
18596
18597     if(sscanf(p, "%d", &i) == 1) {
18598         FENrulePlies = i; /* 50-move ply counter */
18599         /* (The move number is still ignored)    */
18600     }
18601
18602     return TRUE;
18603 }
18604
18605 void
18606 EditPositionPasteFEN (char *fen)
18607 {
18608   if (fen != NULL) {
18609     Board initial_position;
18610
18611     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18612       DisplayError(_("Bad FEN position in clipboard"), 0);
18613       return ;
18614     } else {
18615       int savedBlackPlaysFirst = blackPlaysFirst;
18616       EditPositionEvent();
18617       blackPlaysFirst = savedBlackPlaysFirst;
18618       CopyBoard(boards[0], initial_position);
18619       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18620       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18621       DisplayBothClocks();
18622       DrawPosition(FALSE, boards[currentMove]);
18623     }
18624   }
18625 }
18626
18627 static char cseq[12] = "\\   ";
18628
18629 Boolean
18630 set_cont_sequence (char *new_seq)
18631 {
18632     int len;
18633     Boolean ret;
18634
18635     // handle bad attempts to set the sequence
18636         if (!new_seq)
18637                 return 0; // acceptable error - no debug
18638
18639     len = strlen(new_seq);
18640     ret = (len > 0) && (len < sizeof(cseq));
18641     if (ret)
18642       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18643     else if (appData.debugMode)
18644       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18645     return ret;
18646 }
18647
18648 /*
18649     reformat a source message so words don't cross the width boundary.  internal
18650     newlines are not removed.  returns the wrapped size (no null character unless
18651     included in source message).  If dest is NULL, only calculate the size required
18652     for the dest buffer.  lp argument indicats line position upon entry, and it's
18653     passed back upon exit.
18654 */
18655 int
18656 wrap (char *dest, char *src, int count, int width, int *lp)
18657 {
18658     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18659
18660     cseq_len = strlen(cseq);
18661     old_line = line = *lp;
18662     ansi = len = clen = 0;
18663
18664     for (i=0; i < count; i++)
18665     {
18666         if (src[i] == '\033')
18667             ansi = 1;
18668
18669         // if we hit the width, back up
18670         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18671         {
18672             // store i & len in case the word is too long
18673             old_i = i, old_len = len;
18674
18675             // find the end of the last word
18676             while (i && src[i] != ' ' && src[i] != '\n')
18677             {
18678                 i--;
18679                 len--;
18680             }
18681
18682             // word too long?  restore i & len before splitting it
18683             if ((old_i-i+clen) >= width)
18684             {
18685                 i = old_i;
18686                 len = old_len;
18687             }
18688
18689             // extra space?
18690             if (i && src[i-1] == ' ')
18691                 len--;
18692
18693             if (src[i] != ' ' && src[i] != '\n')
18694             {
18695                 i--;
18696                 if (len)
18697                     len--;
18698             }
18699
18700             // now append the newline and continuation sequence
18701             if (dest)
18702                 dest[len] = '\n';
18703             len++;
18704             if (dest)
18705                 strncpy(dest+len, cseq, cseq_len);
18706             len += cseq_len;
18707             line = cseq_len;
18708             clen = cseq_len;
18709             continue;
18710         }
18711
18712         if (dest)
18713             dest[len] = src[i];
18714         len++;
18715         if (!ansi)
18716             line++;
18717         if (src[i] == '\n')
18718             line = 0;
18719         if (src[i] == 'm')
18720             ansi = 0;
18721     }
18722     if (dest && appData.debugMode)
18723     {
18724         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18725             count, width, line, len, *lp);
18726         show_bytes(debugFP, src, count);
18727         fprintf(debugFP, "\ndest: ");
18728         show_bytes(debugFP, dest, len);
18729         fprintf(debugFP, "\n");
18730     }
18731     *lp = dest ? line : old_line;
18732
18733     return len;
18734 }
18735
18736 // [HGM] vari: routines for shelving variations
18737 Boolean modeRestore = FALSE;
18738
18739 void
18740 PushInner (int firstMove, int lastMove)
18741 {
18742         int i, j, nrMoves = lastMove - firstMove;
18743
18744         // push current tail of game on stack
18745         savedResult[storedGames] = gameInfo.result;
18746         savedDetails[storedGames] = gameInfo.resultDetails;
18747         gameInfo.resultDetails = NULL;
18748         savedFirst[storedGames] = firstMove;
18749         savedLast [storedGames] = lastMove;
18750         savedFramePtr[storedGames] = framePtr;
18751         framePtr -= nrMoves; // reserve space for the boards
18752         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18753             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18754             for(j=0; j<MOVE_LEN; j++)
18755                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18756             for(j=0; j<2*MOVE_LEN; j++)
18757                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18758             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18759             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18760             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18761             pvInfoList[firstMove+i-1].depth = 0;
18762             commentList[framePtr+i] = commentList[firstMove+i];
18763             commentList[firstMove+i] = NULL;
18764         }
18765
18766         storedGames++;
18767         forwardMostMove = firstMove; // truncate game so we can start variation
18768 }
18769
18770 void
18771 PushTail (int firstMove, int lastMove)
18772 {
18773         if(appData.icsActive) { // only in local mode
18774                 forwardMostMove = currentMove; // mimic old ICS behavior
18775                 return;
18776         }
18777         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18778
18779         PushInner(firstMove, lastMove);
18780         if(storedGames == 1) GreyRevert(FALSE);
18781         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18782 }
18783
18784 void
18785 PopInner (Boolean annotate)
18786 {
18787         int i, j, nrMoves;
18788         char buf[8000], moveBuf[20];
18789
18790         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18791         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18792         nrMoves = savedLast[storedGames] - currentMove;
18793         if(annotate) {
18794                 int cnt = 10;
18795                 if(!WhiteOnMove(currentMove))
18796                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18797                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18798                 for(i=currentMove; i<forwardMostMove; i++) {
18799                         if(WhiteOnMove(i))
18800                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18801                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18802                         strcat(buf, moveBuf);
18803                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18804                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18805                 }
18806                 strcat(buf, ")");
18807         }
18808         for(i=1; i<=nrMoves; i++) { // copy last variation back
18809             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18810             for(j=0; j<MOVE_LEN; j++)
18811                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18812             for(j=0; j<2*MOVE_LEN; j++)
18813                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18814             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18815             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18816             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18817             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18818             commentList[currentMove+i] = commentList[framePtr+i];
18819             commentList[framePtr+i] = NULL;
18820         }
18821         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18822         framePtr = savedFramePtr[storedGames];
18823         gameInfo.result = savedResult[storedGames];
18824         if(gameInfo.resultDetails != NULL) {
18825             free(gameInfo.resultDetails);
18826       }
18827         gameInfo.resultDetails = savedDetails[storedGames];
18828         forwardMostMove = currentMove + nrMoves;
18829 }
18830
18831 Boolean
18832 PopTail (Boolean annotate)
18833 {
18834         if(appData.icsActive) return FALSE; // only in local mode
18835         if(!storedGames) return FALSE; // sanity
18836         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18837
18838         PopInner(annotate);
18839         if(currentMove < forwardMostMove) ForwardEvent(); else
18840         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18841
18842         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18843         return TRUE;
18844 }
18845
18846 void
18847 CleanupTail ()
18848 {       // remove all shelved variations
18849         int i;
18850         for(i=0; i<storedGames; i++) {
18851             if(savedDetails[i])
18852                 free(savedDetails[i]);
18853             savedDetails[i] = NULL;
18854         }
18855         for(i=framePtr; i<MAX_MOVES; i++) {
18856                 if(commentList[i]) free(commentList[i]);
18857                 commentList[i] = NULL;
18858         }
18859         framePtr = MAX_MOVES-1;
18860         storedGames = 0;
18861 }
18862
18863 void
18864 LoadVariation (int index, char *text)
18865 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18866         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18867         int level = 0, move;
18868
18869         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18870         // first find outermost bracketing variation
18871         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18872             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18873                 if(*p == '{') wait = '}'; else
18874                 if(*p == '[') wait = ']'; else
18875                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18876                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18877             }
18878             if(*p == wait) wait = NULLCHAR; // closing ]} found
18879             p++;
18880         }
18881         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18882         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18883         end[1] = NULLCHAR; // clip off comment beyond variation
18884         ToNrEvent(currentMove-1);
18885         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18886         // kludge: use ParsePV() to append variation to game
18887         move = currentMove;
18888         ParsePV(start, TRUE, TRUE);
18889         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18890         ClearPremoveHighlights();
18891         CommentPopDown();
18892         ToNrEvent(currentMove+1);
18893 }
18894
18895 void
18896 LoadTheme ()
18897 {
18898     char *p, *q, buf[MSG_SIZ];
18899     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18900         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18901         ParseArgsFromString(buf);
18902         ActivateTheme(TRUE); // also redo colors
18903         return;
18904     }
18905     p = nickName;
18906     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18907     {
18908         int len;
18909         q = appData.themeNames;
18910         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18911       if(appData.useBitmaps) {
18912         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18913                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18914                 appData.liteBackTextureMode,
18915                 appData.darkBackTextureMode );
18916       } else {
18917         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18918                 Col2Text(2),   // lightSquareColor
18919                 Col2Text(3) ); // darkSquareColor
18920       }
18921       if(appData.useBorder) {
18922         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18923                 appData.border);
18924       } else {
18925         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18926       }
18927       if(appData.useFont) {
18928         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18929                 appData.renderPiecesWithFont,
18930                 appData.fontToPieceTable,
18931                 Col2Text(9),    // appData.fontBackColorWhite
18932                 Col2Text(10) ); // appData.fontForeColorBlack
18933       } else {
18934         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18935                 appData.pieceDirectory);
18936         if(!appData.pieceDirectory[0])
18937           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18938                 Col2Text(0),   // whitePieceColor
18939                 Col2Text(1) ); // blackPieceColor
18940       }
18941       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18942                 Col2Text(4),   // highlightSquareColor
18943                 Col2Text(5) ); // premoveHighlightColor
18944         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18945         if(insert != q) insert[-1] = NULLCHAR;
18946         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18947         if(q)   free(q);
18948     }
18949     ActivateTheme(FALSE);
18950 }