Fix demoting in Edit Position mode
[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                         gotPremove = 0;
4166                         ClearPremoveHighlights();
4167                         if (appData.debugMode)
4168                           fprintf(debugFP, "Sending premove:\n");
4169                           UserMoveEvent(premoveFromX, premoveFromY,
4170                                         premoveToX, premoveToY,
4171                                         premovePromoChar);
4172                       }
4173                     }
4174
4175                     /* Usually suppress following prompt */
4176                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4177                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4178                         if (looking_at(buf, &i, "*% ")) {
4179                             savingComment = FALSE;
4180                             suppressKibitz = 0;
4181                         }
4182                     }
4183                     next_out = i;
4184                 } else if (started == STARTED_HOLDINGS) {
4185                     int gamenum;
4186                     char new_piece[MSG_SIZ];
4187                     started = STARTED_NONE;
4188                     parse[parse_pos] = NULLCHAR;
4189                     if (appData.debugMode)
4190                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4191                                                         parse, currentMove);
4192                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4193                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4194                         if (gameInfo.variant == VariantNormal) {
4195                           /* [HGM] We seem to switch variant during a game!
4196                            * Presumably no holdings were displayed, so we have
4197                            * to move the position two files to the right to
4198                            * create room for them!
4199                            */
4200                           VariantClass newVariant;
4201                           switch(gameInfo.boardWidth) { // base guess on board width
4202                                 case 9:  newVariant = VariantShogi; break;
4203                                 case 10: newVariant = VariantGreat; break;
4204                                 default: newVariant = VariantCrazyhouse; break;
4205                           }
4206                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4207                           /* Get a move list just to see the header, which
4208                              will tell us whether this is really bug or zh */
4209                           if (ics_getting_history == H_FALSE) {
4210                             ics_getting_history = H_REQUESTED;
4211                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4212                             SendToICS(str);
4213                           }
4214                         }
4215                         new_piece[0] = NULLCHAR;
4216                         sscanf(parse, "game %d white [%s black [%s <- %s",
4217                                &gamenum, white_holding, black_holding,
4218                                new_piece);
4219                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4220                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4221                         /* [HGM] copy holdings to board holdings area */
4222                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4223                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4224                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4225 #if ZIPPY
4226                         if (appData.zippyPlay && first.initDone) {
4227                             ZippyHoldings(white_holding, black_holding,
4228                                           new_piece);
4229                         }
4230 #endif /*ZIPPY*/
4231                         if (tinyLayout || smallLayout) {
4232                             char wh[16], bh[16];
4233                             PackHolding(wh, white_holding);
4234                             PackHolding(bh, black_holding);
4235                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4236                                     gameInfo.white, gameInfo.black);
4237                         } else {
4238                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4239                                     gameInfo.white, white_holding, _("vs."),
4240                                     gameInfo.black, black_holding);
4241                         }
4242                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4243                         DrawPosition(FALSE, boards[currentMove]);
4244                         DisplayTitle(str);
4245                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4246                         sscanf(parse, "game %d white [%s black [%s <- %s",
4247                                &gamenum, white_holding, black_holding,
4248                                new_piece);
4249                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4250                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4251                         /* [HGM] copy holdings to partner-board holdings area */
4252                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4253                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4254                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4255                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4256                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4257                       }
4258                     }
4259                     /* Suppress following prompt */
4260                     if (looking_at(buf, &i, "*% ")) {
4261                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4262                         savingComment = FALSE;
4263                         suppressKibitz = 0;
4264                     }
4265                     next_out = i;
4266                 }
4267                 continue;
4268             }
4269
4270             i++;                /* skip unparsed character and loop back */
4271         }
4272
4273         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4274 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4275 //          SendToPlayer(&buf[next_out], i - next_out);
4276             started != STARTED_HOLDINGS && leftover_start > next_out) {
4277             SendToPlayer(&buf[next_out], leftover_start - next_out);
4278             next_out = i;
4279         }
4280
4281         leftover_len = buf_len - leftover_start;
4282         /* if buffer ends with something we couldn't parse,
4283            reparse it after appending the next read */
4284
4285     } else if (count == 0) {
4286         RemoveInputSource(isr);
4287         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4288     } else {
4289         DisplayFatalError(_("Error reading from ICS"), error, 1);
4290     }
4291 }
4292
4293
4294 /* Board style 12 looks like this:
4295
4296    <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
4297
4298  * The "<12> " is stripped before it gets to this routine.  The two
4299  * trailing 0's (flip state and clock ticking) are later addition, and
4300  * some chess servers may not have them, or may have only the first.
4301  * Additional trailing fields may be added in the future.
4302  */
4303
4304 #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"
4305
4306 #define RELATION_OBSERVING_PLAYED    0
4307 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4308 #define RELATION_PLAYING_MYMOVE      1
4309 #define RELATION_PLAYING_NOTMYMOVE  -1
4310 #define RELATION_EXAMINING           2
4311 #define RELATION_ISOLATED_BOARD     -3
4312 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4313
4314 void
4315 ParseBoard12 (char *string)
4316 {
4317 #if ZIPPY
4318     int i, takeback;
4319     char *bookHit = NULL; // [HGM] book
4320 #endif
4321     GameMode newGameMode;
4322     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4323     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4324     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4325     char to_play, board_chars[200];
4326     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4327     char black[32], white[32];
4328     Board board;
4329     int prevMove = currentMove;
4330     int ticking = 2;
4331     ChessMove moveType;
4332     int fromX, fromY, toX, toY;
4333     char promoChar;
4334     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4335     Boolean weird = FALSE, reqFlag = FALSE;
4336
4337     fromX = fromY = toX = toY = -1;
4338
4339     newGame = FALSE;
4340
4341     if (appData.debugMode)
4342       fprintf(debugFP, "Parsing board: %s\n", string);
4343
4344     move_str[0] = NULLCHAR;
4345     elapsed_time[0] = NULLCHAR;
4346     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4347         int  i = 0, j;
4348         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4349             if(string[i] == ' ') { ranks++; files = 0; }
4350             else files++;
4351             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4352             i++;
4353         }
4354         for(j = 0; j <i; j++) board_chars[j] = string[j];
4355         board_chars[i] = '\0';
4356         string += i + 1;
4357     }
4358     n = sscanf(string, PATTERN, &to_play, &double_push,
4359                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4360                &gamenum, white, black, &relation, &basetime, &increment,
4361                &white_stren, &black_stren, &white_time, &black_time,
4362                &moveNum, str, elapsed_time, move_str, &ics_flip,
4363                &ticking);
4364
4365     if (n < 21) {
4366         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4367         DisplayError(str, 0);
4368         return;
4369     }
4370
4371     /* Convert the move number to internal form */
4372     moveNum = (moveNum - 1) * 2;
4373     if (to_play == 'B') moveNum++;
4374     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4375       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4376                         0, 1);
4377       return;
4378     }
4379
4380     switch (relation) {
4381       case RELATION_OBSERVING_PLAYED:
4382       case RELATION_OBSERVING_STATIC:
4383         if (gamenum == -1) {
4384             /* Old ICC buglet */
4385             relation = RELATION_OBSERVING_STATIC;
4386         }
4387         newGameMode = IcsObserving;
4388         break;
4389       case RELATION_PLAYING_MYMOVE:
4390       case RELATION_PLAYING_NOTMYMOVE:
4391         newGameMode =
4392           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4393             IcsPlayingWhite : IcsPlayingBlack;
4394         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4395         break;
4396       case RELATION_EXAMINING:
4397         newGameMode = IcsExamining;
4398         break;
4399       case RELATION_ISOLATED_BOARD:
4400       default:
4401         /* Just display this board.  If user was doing something else,
4402            we will forget about it until the next board comes. */
4403         newGameMode = IcsIdle;
4404         break;
4405       case RELATION_STARTING_POSITION:
4406         newGameMode = gameMode;
4407         break;
4408     }
4409
4410     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4411         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4412          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4413       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4414       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4415       static int lastBgGame = -1;
4416       char *toSqr;
4417       for (k = 0; k < ranks; k++) {
4418         for (j = 0; j < files; j++)
4419           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4420         if(gameInfo.holdingsWidth > 1) {
4421              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4422              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4423         }
4424       }
4425       CopyBoard(partnerBoard, board);
4426       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4427         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4428         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4429       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4430       if(toSqr = strchr(str, '-')) {
4431         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4432         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4433       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4434       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4435       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4436       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4437       if(twoBoards) {
4438           DisplayWhiteClock(white_time*fac, to_play == 'W');
4439           DisplayBlackClock(black_time*fac, to_play != 'W');
4440           activePartner = to_play;
4441           if(gamenum != lastBgGame) {
4442               char buf[MSG_SIZ];
4443               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4444               DisplayTitle(buf);
4445           }
4446           lastBgGame = gamenum;
4447           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4448                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4449       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4450                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4451       if(!twoBoards) DisplayMessage(partnerStatus, "");
4452         partnerBoardValid = TRUE;
4453       return;
4454     }
4455
4456     if(appData.dualBoard && appData.bgObserve) {
4457         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4458             SendToICS(ics_prefix), SendToICS("pobserve\n");
4459         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4460             char buf[MSG_SIZ];
4461             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4462             SendToICS(buf);
4463         }
4464     }
4465
4466     /* Modify behavior for initial board display on move listing
4467        of wild games.
4468        */
4469     switch (ics_getting_history) {
4470       case H_FALSE:
4471       case H_REQUESTED:
4472         break;
4473       case H_GOT_REQ_HEADER:
4474       case H_GOT_UNREQ_HEADER:
4475         /* This is the initial position of the current game */
4476         gamenum = ics_gamenum;
4477         moveNum = 0;            /* old ICS bug workaround */
4478         if (to_play == 'B') {
4479           startedFromSetupPosition = TRUE;
4480           blackPlaysFirst = TRUE;
4481           moveNum = 1;
4482           if (forwardMostMove == 0) forwardMostMove = 1;
4483           if (backwardMostMove == 0) backwardMostMove = 1;
4484           if (currentMove == 0) currentMove = 1;
4485         }
4486         newGameMode = gameMode;
4487         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4488         break;
4489       case H_GOT_UNWANTED_HEADER:
4490         /* This is an initial board that we don't want */
4491         return;
4492       case H_GETTING_MOVES:
4493         /* Should not happen */
4494         DisplayError(_("Error gathering move list: extra board"), 0);
4495         ics_getting_history = H_FALSE;
4496         return;
4497     }
4498
4499    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4500                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4501                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4502      /* [HGM] We seem to have switched variant unexpectedly
4503       * Try to guess new variant from board size
4504       */
4505           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4506           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4507           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4508           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4509           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4510           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4511           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4512           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4513           /* Get a move list just to see the header, which
4514              will tell us whether this is really bug or zh */
4515           if (ics_getting_history == H_FALSE) {
4516             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4517             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4518             SendToICS(str);
4519           }
4520     }
4521
4522     /* Take action if this is the first board of a new game, or of a
4523        different game than is currently being displayed.  */
4524     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4525         relation == RELATION_ISOLATED_BOARD) {
4526
4527         /* Forget the old game and get the history (if any) of the new one */
4528         if (gameMode != BeginningOfGame) {
4529           Reset(TRUE, TRUE);
4530         }
4531         newGame = TRUE;
4532         if (appData.autoRaiseBoard) BoardToTop();
4533         prevMove = -3;
4534         if (gamenum == -1) {
4535             newGameMode = IcsIdle;
4536         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4537                    appData.getMoveList && !reqFlag) {
4538             /* Need to get game history */
4539             ics_getting_history = H_REQUESTED;
4540             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4541             SendToICS(str);
4542         }
4543
4544         /* Initially flip the board to have black on the bottom if playing
4545            black or if the ICS flip flag is set, but let the user change
4546            it with the Flip View button. */
4547         flipView = appData.autoFlipView ?
4548           (newGameMode == IcsPlayingBlack) || ics_flip :
4549           appData.flipView;
4550
4551         /* Done with values from previous mode; copy in new ones */
4552         gameMode = newGameMode;
4553         ModeHighlight();
4554         ics_gamenum = gamenum;
4555         if (gamenum == gs_gamenum) {
4556             int klen = strlen(gs_kind);
4557             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4558             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4559             gameInfo.event = StrSave(str);
4560         } else {
4561             gameInfo.event = StrSave("ICS game");
4562         }
4563         gameInfo.site = StrSave(appData.icsHost);
4564         gameInfo.date = PGNDate();
4565         gameInfo.round = StrSave("-");
4566         gameInfo.white = StrSave(white);
4567         gameInfo.black = StrSave(black);
4568         timeControl = basetime * 60 * 1000;
4569         timeControl_2 = 0;
4570         timeIncrement = increment * 1000;
4571         movesPerSession = 0;
4572         gameInfo.timeControl = TimeControlTagValue();
4573         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4574   if (appData.debugMode) {
4575     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4576     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4577     setbuf(debugFP, NULL);
4578   }
4579
4580         gameInfo.outOfBook = NULL;
4581
4582         /* Do we have the ratings? */
4583         if (strcmp(player1Name, white) == 0 &&
4584             strcmp(player2Name, black) == 0) {
4585             if (appData.debugMode)
4586               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4587                       player1Rating, player2Rating);
4588             gameInfo.whiteRating = player1Rating;
4589             gameInfo.blackRating = player2Rating;
4590         } else if (strcmp(player2Name, white) == 0 &&
4591                    strcmp(player1Name, black) == 0) {
4592             if (appData.debugMode)
4593               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4594                       player2Rating, player1Rating);
4595             gameInfo.whiteRating = player2Rating;
4596             gameInfo.blackRating = player1Rating;
4597         }
4598         player1Name[0] = player2Name[0] = NULLCHAR;
4599
4600         /* Silence shouts if requested */
4601         if (appData.quietPlay &&
4602             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4603             SendToICS(ics_prefix);
4604             SendToICS("set shout 0\n");
4605         }
4606     }
4607
4608     /* Deal with midgame name changes */
4609     if (!newGame) {
4610         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4611             if (gameInfo.white) free(gameInfo.white);
4612             gameInfo.white = StrSave(white);
4613         }
4614         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4615             if (gameInfo.black) free(gameInfo.black);
4616             gameInfo.black = StrSave(black);
4617         }
4618     }
4619
4620     /* Throw away game result if anything actually changes in examine mode */
4621     if (gameMode == IcsExamining && !newGame) {
4622         gameInfo.result = GameUnfinished;
4623         if (gameInfo.resultDetails != NULL) {
4624             free(gameInfo.resultDetails);
4625             gameInfo.resultDetails = NULL;
4626         }
4627     }
4628
4629     /* In pausing && IcsExamining mode, we ignore boards coming
4630        in if they are in a different variation than we are. */
4631     if (pauseExamInvalid) return;
4632     if (pausing && gameMode == IcsExamining) {
4633         if (moveNum <= pauseExamForwardMostMove) {
4634             pauseExamInvalid = TRUE;
4635             forwardMostMove = pauseExamForwardMostMove;
4636             return;
4637         }
4638     }
4639
4640   if (appData.debugMode) {
4641     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4642   }
4643     /* Parse the board */
4644     for (k = 0; k < ranks; k++) {
4645       for (j = 0; j < files; j++)
4646         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4647       if(gameInfo.holdingsWidth > 1) {
4648            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4649            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4650       }
4651     }
4652     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4653       board[5][BOARD_RGHT+1] = WhiteAngel;
4654       board[6][BOARD_RGHT+1] = WhiteMarshall;
4655       board[1][0] = BlackMarshall;
4656       board[2][0] = BlackAngel;
4657       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4658     }
4659     CopyBoard(boards[moveNum], board);
4660     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4661     if (moveNum == 0) {
4662         startedFromSetupPosition =
4663           !CompareBoards(board, initialPosition);
4664         if(startedFromSetupPosition)
4665             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4666     }
4667
4668     /* [HGM] Set castling rights. Take the outermost Rooks,
4669        to make it also work for FRC opening positions. Note that board12
4670        is really defective for later FRC positions, as it has no way to
4671        indicate which Rook can castle if they are on the same side of King.
4672        For the initial position we grant rights to the outermost Rooks,
4673        and remember thos rights, and we then copy them on positions
4674        later in an FRC game. This means WB might not recognize castlings with
4675        Rooks that have moved back to their original position as illegal,
4676        but in ICS mode that is not its job anyway.
4677     */
4678     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4679     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4680
4681         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4682             if(board[0][i] == WhiteRook) j = i;
4683         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4684         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4685             if(board[0][i] == WhiteRook) j = i;
4686         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4687         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4689         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4690         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4691             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4692         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4693
4694         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4695         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4696         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4697             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4698         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4699             if(board[BOARD_HEIGHT-1][k] == bKing)
4700                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4701         if(gameInfo.variant == VariantTwoKings) {
4702             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4703             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4704             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4705         }
4706     } else { int r;
4707         r = boards[moveNum][CASTLING][0] = initialRights[0];
4708         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4709         r = boards[moveNum][CASTLING][1] = initialRights[1];
4710         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4711         r = boards[moveNum][CASTLING][3] = initialRights[3];
4712         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4713         r = boards[moveNum][CASTLING][4] = initialRights[4];
4714         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4715         /* wildcastle kludge: always assume King has rights */
4716         r = boards[moveNum][CASTLING][2] = initialRights[2];
4717         r = boards[moveNum][CASTLING][5] = initialRights[5];
4718     }
4719     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4720     boards[moveNum][EP_STATUS] = EP_NONE;
4721     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4722     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4723     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4724
4725
4726     if (ics_getting_history == H_GOT_REQ_HEADER ||
4727         ics_getting_history == H_GOT_UNREQ_HEADER) {
4728         /* This was an initial position from a move list, not
4729            the current position */
4730         return;
4731     }
4732
4733     /* Update currentMove and known move number limits */
4734     newMove = newGame || moveNum > forwardMostMove;
4735
4736     if (newGame) {
4737         forwardMostMove = backwardMostMove = currentMove = moveNum;
4738         if (gameMode == IcsExamining && moveNum == 0) {
4739           /* Workaround for ICS limitation: we are not told the wild
4740              type when starting to examine a game.  But if we ask for
4741              the move list, the move list header will tell us */
4742             ics_getting_history = H_REQUESTED;
4743             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4744             SendToICS(str);
4745         }
4746     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4747                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4748 #if ZIPPY
4749         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4750         /* [HGM] applied this also to an engine that is silently watching        */
4751         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4752             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4753             gameInfo.variant == currentlyInitializedVariant) {
4754           takeback = forwardMostMove - moveNum;
4755           for (i = 0; i < takeback; i++) {
4756             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4757             SendToProgram("undo\n", &first);
4758           }
4759         }
4760 #endif
4761
4762         forwardMostMove = moveNum;
4763         if (!pausing || currentMove > forwardMostMove)
4764           currentMove = forwardMostMove;
4765     } else {
4766         /* New part of history that is not contiguous with old part */
4767         if (pausing && gameMode == IcsExamining) {
4768             pauseExamInvalid = TRUE;
4769             forwardMostMove = pauseExamForwardMostMove;
4770             return;
4771         }
4772         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4773 #if ZIPPY
4774             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4775                 // [HGM] when we will receive the move list we now request, it will be
4776                 // fed to the engine from the first move on. So if the engine is not
4777                 // in the initial position now, bring it there.
4778                 InitChessProgram(&first, 0);
4779             }
4780 #endif
4781             ics_getting_history = H_REQUESTED;
4782             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4783             SendToICS(str);
4784         }
4785         forwardMostMove = backwardMostMove = currentMove = moveNum;
4786     }
4787
4788     /* Update the clocks */
4789     if (strchr(elapsed_time, '.')) {
4790       /* Time is in ms */
4791       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4792       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4793     } else {
4794       /* Time is in seconds */
4795       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4796       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4797     }
4798
4799
4800 #if ZIPPY
4801     if (appData.zippyPlay && newGame &&
4802         gameMode != IcsObserving && gameMode != IcsIdle &&
4803         gameMode != IcsExamining)
4804       ZippyFirstBoard(moveNum, basetime, increment);
4805 #endif
4806
4807     /* Put the move on the move list, first converting
4808        to canonical algebraic form. */
4809     if (moveNum > 0) {
4810   if (appData.debugMode) {
4811     int f = forwardMostMove;
4812     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4813             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4814             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4815     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4816     fprintf(debugFP, "moveNum = %d\n", moveNum);
4817     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4818     setbuf(debugFP, NULL);
4819   }
4820         if (moveNum <= backwardMostMove) {
4821             /* We don't know what the board looked like before
4822                this move.  Punt. */
4823           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4824             strcat(parseList[moveNum - 1], " ");
4825             strcat(parseList[moveNum - 1], elapsed_time);
4826             moveList[moveNum - 1][0] = NULLCHAR;
4827         } else if (strcmp(move_str, "none") == 0) {
4828             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4829             /* Again, we don't know what the board looked like;
4830                this is really the start of the game. */
4831             parseList[moveNum - 1][0] = NULLCHAR;
4832             moveList[moveNum - 1][0] = NULLCHAR;
4833             backwardMostMove = moveNum;
4834             startedFromSetupPosition = TRUE;
4835             fromX = fromY = toX = toY = -1;
4836         } else {
4837           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4838           //                 So we parse the long-algebraic move string in stead of the SAN move
4839           int valid; char buf[MSG_SIZ], *prom;
4840
4841           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4842                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4843           // str looks something like "Q/a1-a2"; kill the slash
4844           if(str[1] == '/')
4845             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4846           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4847           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4848                 strcat(buf, prom); // long move lacks promo specification!
4849           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4850                 if(appData.debugMode)
4851                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4852                 safeStrCpy(move_str, buf, MSG_SIZ);
4853           }
4854           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4855                                 &fromX, &fromY, &toX, &toY, &promoChar)
4856                || ParseOneMove(buf, moveNum - 1, &moveType,
4857                                 &fromX, &fromY, &toX, &toY, &promoChar);
4858           // end of long SAN patch
4859           if (valid) {
4860             (void) CoordsToAlgebraic(boards[moveNum - 1],
4861                                      PosFlags(moveNum - 1),
4862                                      fromY, fromX, toY, toX, promoChar,
4863                                      parseList[moveNum-1]);
4864             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4865               case MT_NONE:
4866               case MT_STALEMATE:
4867               default:
4868                 break;
4869               case MT_CHECK:
4870                 if(!IS_SHOGI(gameInfo.variant))
4871                     strcat(parseList[moveNum - 1], "+");
4872                 break;
4873               case MT_CHECKMATE:
4874               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4875                 strcat(parseList[moveNum - 1], "#");
4876                 break;
4877             }
4878             strcat(parseList[moveNum - 1], " ");
4879             strcat(parseList[moveNum - 1], elapsed_time);
4880             /* currentMoveString is set as a side-effect of ParseOneMove */
4881             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4882             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4883             strcat(moveList[moveNum - 1], "\n");
4884
4885             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4886                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4887               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4888                 ChessSquare old, new = boards[moveNum][k][j];
4889                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4890                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4891                   if(old == new) continue;
4892                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4893                   else if(new == WhiteWazir || new == BlackWazir) {
4894                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4895                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4896                       else boards[moveNum][k][j] = old; // preserve type of Gold
4897                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4898                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4899               }
4900           } else {
4901             /* Move from ICS was illegal!?  Punt. */
4902             if (appData.debugMode) {
4903               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4904               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4905             }
4906             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4907             strcat(parseList[moveNum - 1], " ");
4908             strcat(parseList[moveNum - 1], elapsed_time);
4909             moveList[moveNum - 1][0] = NULLCHAR;
4910             fromX = fromY = toX = toY = -1;
4911           }
4912         }
4913   if (appData.debugMode) {
4914     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4915     setbuf(debugFP, NULL);
4916   }
4917
4918 #if ZIPPY
4919         /* Send move to chess program (BEFORE animating it). */
4920         if (appData.zippyPlay && !newGame && newMove &&
4921            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4922
4923             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4924                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4925                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4926                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4927                             move_str);
4928                     DisplayError(str, 0);
4929                 } else {
4930                     if (first.sendTime) {
4931                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4932                     }
4933                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4934                     if (firstMove && !bookHit) {
4935                         firstMove = FALSE;
4936                         if (first.useColors) {
4937                           SendToProgram(gameMode == IcsPlayingWhite ?
4938                                         "white\ngo\n" :
4939                                         "black\ngo\n", &first);
4940                         } else {
4941                           SendToProgram("go\n", &first);
4942                         }
4943                         first.maybeThinking = TRUE;
4944                     }
4945                 }
4946             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4947               if (moveList[moveNum - 1][0] == NULLCHAR) {
4948                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4949                 DisplayError(str, 0);
4950               } else {
4951                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4952                 SendMoveToProgram(moveNum - 1, &first);
4953               }
4954             }
4955         }
4956 #endif
4957     }
4958
4959     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4960         /* If move comes from a remote source, animate it.  If it
4961            isn't remote, it will have already been animated. */
4962         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4963             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4964         }
4965         if (!pausing && appData.highlightLastMove) {
4966             SetHighlights(fromX, fromY, toX, toY);
4967         }
4968     }
4969
4970     /* Start the clocks */
4971     whiteFlag = blackFlag = FALSE;
4972     appData.clockMode = !(basetime == 0 && increment == 0);
4973     if (ticking == 0) {
4974       ics_clock_paused = TRUE;
4975       StopClocks();
4976     } else if (ticking == 1) {
4977       ics_clock_paused = FALSE;
4978     }
4979     if (gameMode == IcsIdle ||
4980         relation == RELATION_OBSERVING_STATIC ||
4981         relation == RELATION_EXAMINING ||
4982         ics_clock_paused)
4983       DisplayBothClocks();
4984     else
4985       StartClocks();
4986
4987     /* Display opponents and material strengths */
4988     if (gameInfo.variant != VariantBughouse &&
4989         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4990         if (tinyLayout || smallLayout) {
4991             if(gameInfo.variant == VariantNormal)
4992               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4993                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4994                     basetime, increment);
4995             else
4996               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4997                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4998                     basetime, increment, (int) gameInfo.variant);
4999         } else {
5000             if(gameInfo.variant == VariantNormal)
5001               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5002                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5003                     basetime, increment);
5004             else
5005               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5006                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5007                     basetime, increment, VariantName(gameInfo.variant));
5008         }
5009         DisplayTitle(str);
5010   if (appData.debugMode) {
5011     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5012   }
5013     }
5014
5015
5016     /* Display the board */
5017     if (!pausing && !appData.noGUI) {
5018
5019       if (appData.premove)
5020           if (!gotPremove ||
5021              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5022              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5023               ClearPremoveHighlights();
5024
5025       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5026         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5027       DrawPosition(j, boards[currentMove]);
5028
5029       DisplayMove(moveNum - 1);
5030       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5031             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5032               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5033         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5034       }
5035     }
5036
5037     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5038 #if ZIPPY
5039     if(bookHit) { // [HGM] book: simulate book reply
5040         static char bookMove[MSG_SIZ]; // a bit generous?
5041
5042         programStats.nodes = programStats.depth = programStats.time =
5043         programStats.score = programStats.got_only_move = 0;
5044         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5045
5046         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5047         strcat(bookMove, bookHit);
5048         HandleMachineMove(bookMove, &first);
5049     }
5050 #endif
5051 }
5052
5053 void
5054 GetMoveListEvent ()
5055 {
5056     char buf[MSG_SIZ];
5057     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5058         ics_getting_history = H_REQUESTED;
5059         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5060         SendToICS(buf);
5061     }
5062 }
5063
5064 void
5065 SendToBoth (char *msg)
5066 {   // to make it easy to keep two engines in step in dual analysis
5067     SendToProgram(msg, &first);
5068     if(second.analyzing) SendToProgram(msg, &second);
5069 }
5070
5071 void
5072 AnalysisPeriodicEvent (int force)
5073 {
5074     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5075          && !force) || !appData.periodicUpdates)
5076       return;
5077
5078     /* Send . command to Crafty to collect stats */
5079     SendToBoth(".\n");
5080
5081     /* Don't send another until we get a response (this makes
5082        us stop sending to old Crafty's which don't understand
5083        the "." command (sending illegal cmds resets node count & time,
5084        which looks bad)) */
5085     programStats.ok_to_send = 0;
5086 }
5087
5088 void
5089 ics_update_width (int new_width)
5090 {
5091         ics_printf("set width %d\n", new_width);
5092 }
5093
5094 void
5095 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5096 {
5097     char buf[MSG_SIZ];
5098
5099     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5100         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5101             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5102             SendToProgram(buf, cps);
5103             return;
5104         }
5105         // null move in variant where engine does not understand it (for analysis purposes)
5106         SendBoard(cps, moveNum + 1); // send position after move in stead.
5107         return;
5108     }
5109     if (cps->useUsermove) {
5110       SendToProgram("usermove ", cps);
5111     }
5112     if (cps->useSAN) {
5113       char *space;
5114       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5115         int len = space - parseList[moveNum];
5116         memcpy(buf, parseList[moveNum], len);
5117         buf[len++] = '\n';
5118         buf[len] = NULLCHAR;
5119       } else {
5120         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5121       }
5122       SendToProgram(buf, cps);
5123     } else {
5124       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5125         AlphaRank(moveList[moveNum], 4);
5126         SendToProgram(moveList[moveNum], cps);
5127         AlphaRank(moveList[moveNum], 4); // and back
5128       } else
5129       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5130        * the engine. It would be nice to have a better way to identify castle
5131        * moves here. */
5132       if(appData.fischerCastling && cps->useOOCastle) {
5133         int fromX = moveList[moveNum][0] - AAA;
5134         int fromY = moveList[moveNum][1] - ONE;
5135         int toX = moveList[moveNum][2] - AAA;
5136         int toY = moveList[moveNum][3] - ONE;
5137         if((boards[moveNum][fromY][fromX] == WhiteKing
5138             && boards[moveNum][toY][toX] == WhiteRook)
5139            || (boards[moveNum][fromY][fromX] == BlackKing
5140                && boards[moveNum][toY][toX] == BlackRook)) {
5141           if(toX > fromX) SendToProgram("O-O\n", cps);
5142           else SendToProgram("O-O-O\n", cps);
5143         }
5144         else SendToProgram(moveList[moveNum], cps);
5145       } else
5146       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5147         char *m = moveList[moveNum];
5148         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
5149           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5150                                                m[2], m[3] - '0',
5151                                                m[5], m[6] - '0',
5152                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5153         else
5154           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5155                                                m[5], m[6] - '0',
5156                                                m[5], m[6] - '0',
5157                                                m[2], m[3] - '0');
5158           SendToProgram(buf, cps);
5159       } else
5160       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5161         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5162           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5163           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5164                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5165         } else
5166           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5167                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5168         SendToProgram(buf, cps);
5169       }
5170       else SendToProgram(moveList[moveNum], cps);
5171       /* End of additions by Tord */
5172     }
5173
5174     /* [HGM] setting up the opening has brought engine in force mode! */
5175     /*       Send 'go' if we are in a mode where machine should play. */
5176     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5177         (gameMode == TwoMachinesPlay   ||
5178 #if ZIPPY
5179          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5180 #endif
5181          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5182         SendToProgram("go\n", cps);
5183   if (appData.debugMode) {
5184     fprintf(debugFP, "(extra)\n");
5185   }
5186     }
5187     setboardSpoiledMachineBlack = 0;
5188 }
5189
5190 void
5191 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5192 {
5193     char user_move[MSG_SIZ];
5194     char suffix[4];
5195
5196     if(gameInfo.variant == VariantSChess && promoChar) {
5197         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5198         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5199     } else suffix[0] = NULLCHAR;
5200
5201     switch (moveType) {
5202       default:
5203         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5204                 (int)moveType, fromX, fromY, toX, toY);
5205         DisplayError(user_move + strlen("say "), 0);
5206         break;
5207       case WhiteKingSideCastle:
5208       case BlackKingSideCastle:
5209       case WhiteQueenSideCastleWild:
5210       case BlackQueenSideCastleWild:
5211       /* PUSH Fabien */
5212       case WhiteHSideCastleFR:
5213       case BlackHSideCastleFR:
5214       /* POP Fabien */
5215         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5216         break;
5217       case WhiteQueenSideCastle:
5218       case BlackQueenSideCastle:
5219       case WhiteKingSideCastleWild:
5220       case BlackKingSideCastleWild:
5221       /* PUSH Fabien */
5222       case WhiteASideCastleFR:
5223       case BlackASideCastleFR:
5224       /* POP Fabien */
5225         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5226         break;
5227       case WhiteNonPromotion:
5228       case BlackNonPromotion:
5229         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5230         break;
5231       case WhitePromotion:
5232       case BlackPromotion:
5233         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5234            gameInfo.variant == VariantMakruk)
5235           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5236                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5237                 PieceToChar(WhiteFerz));
5238         else if(gameInfo.variant == VariantGreat)
5239           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5240                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5241                 PieceToChar(WhiteMan));
5242         else
5243           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5244                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5245                 promoChar);
5246         break;
5247       case WhiteDrop:
5248       case BlackDrop:
5249       drop:
5250         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5251                  ToUpper(PieceToChar((ChessSquare) fromX)),
5252                  AAA + toX, ONE + toY);
5253         break;
5254       case IllegalMove:  /* could be a variant we don't quite understand */
5255         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5256       case NormalMove:
5257       case WhiteCapturesEnPassant:
5258       case BlackCapturesEnPassant:
5259         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5260                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5261         break;
5262     }
5263     SendToICS(user_move);
5264     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5265         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5266 }
5267
5268 void
5269 UploadGameEvent ()
5270 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5271     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5272     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5273     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5274       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5275       return;
5276     }
5277     if(gameMode != IcsExamining) { // is this ever not the case?
5278         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5279
5280         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5281           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5282         } else { // on FICS we must first go to general examine mode
5283           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5284         }
5285         if(gameInfo.variant != VariantNormal) {
5286             // try figure out wild number, as xboard names are not always valid on ICS
5287             for(i=1; i<=36; i++) {
5288               snprintf(buf, MSG_SIZ, "wild/%d", i);
5289                 if(StringToVariant(buf) == gameInfo.variant) break;
5290             }
5291             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5292             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5293             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5294         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5295         SendToICS(ics_prefix);
5296         SendToICS(buf);
5297         if(startedFromSetupPosition || backwardMostMove != 0) {
5298           fen = PositionToFEN(backwardMostMove, NULL, 1);
5299           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5300             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5301             SendToICS(buf);
5302           } else { // FICS: everything has to set by separate bsetup commands
5303             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5304             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5305             SendToICS(buf);
5306             if(!WhiteOnMove(backwardMostMove)) {
5307                 SendToICS("bsetup tomove black\n");
5308             }
5309             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5310             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5311             SendToICS(buf);
5312             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5313             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5314             SendToICS(buf);
5315             i = boards[backwardMostMove][EP_STATUS];
5316             if(i >= 0) { // set e.p.
5317               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5318                 SendToICS(buf);
5319             }
5320             bsetup++;
5321           }
5322         }
5323       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5324             SendToICS("bsetup done\n"); // switch to normal examining.
5325     }
5326     for(i = backwardMostMove; i<last; i++) {
5327         char buf[20];
5328         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5329         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5330             int len = strlen(moveList[i]);
5331             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5332             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5333         }
5334         SendToICS(buf);
5335     }
5336     SendToICS(ics_prefix);
5337     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5338 }
5339
5340 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5341 int legNr = 1;
5342
5343 void
5344 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5345 {
5346     if (rf == DROP_RANK) {
5347       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5348       sprintf(move, "%c@%c%c\n",
5349                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5350     } else {
5351         if (promoChar == 'x' || promoChar == NULLCHAR) {
5352           sprintf(move, "%c%c%c%c\n",
5353                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5354           if(killX >= 0 && killY >= 0) {
5355             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5356             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5357           }
5358         } else {
5359             sprintf(move, "%c%c%c%c%c\n",
5360                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5361         }
5362     }
5363 }
5364
5365 void
5366 ProcessICSInitScript (FILE *f)
5367 {
5368     char buf[MSG_SIZ];
5369
5370     while (fgets(buf, MSG_SIZ, f)) {
5371         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5372     }
5373
5374     fclose(f);
5375 }
5376
5377
5378 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5379 int dragging;
5380 static ClickType lastClickType;
5381
5382 int
5383 PieceInString (char *s, ChessSquare piece)
5384 {
5385   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5386   while((p = strchr(s, ID))) {
5387     if(!suffix || p[1] == suffix) return TRUE;
5388     s = p;
5389   }
5390   return FALSE;
5391 }
5392
5393 int
5394 Partner (ChessSquare *p)
5395 { // change piece into promotion partner if one shogi-promotes to the other
5396   ChessSquare partner = promoPartner[*p];
5397   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5398   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5399   *p = partner;
5400   return 1;
5401 }
5402
5403 void
5404 Sweep (int step)
5405 {
5406     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5407     static int toggleFlag;
5408     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5409     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5410     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5411     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5412     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5413     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5414     do {
5415         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5416         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5417         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5418         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5419         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5420         if(!step) step = -1;
5421     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5422             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5423             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5424             promoSweep == pawn ||
5425             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5426             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5427     if(toX >= 0) {
5428         int victim = boards[currentMove][toY][toX];
5429         boards[currentMove][toY][toX] = promoSweep;
5430         DrawPosition(FALSE, boards[currentMove]);
5431         boards[currentMove][toY][toX] = victim;
5432     } else
5433     ChangeDragPiece(promoSweep);
5434 }
5435
5436 int
5437 PromoScroll (int x, int y)
5438 {
5439   int step = 0;
5440
5441   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5442   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5443   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5444   if(!step) return FALSE;
5445   lastX = x; lastY = y;
5446   if((promoSweep < BlackPawn) == flipView) step = -step;
5447   if(step > 0) selectFlag = 1;
5448   if(!selectFlag) Sweep(step);
5449   return FALSE;
5450 }
5451
5452 void
5453 NextPiece (int step)
5454 {
5455     ChessSquare piece = boards[currentMove][toY][toX];
5456     do {
5457         pieceSweep -= step;
5458         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5459         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5460         if(!step) step = -1;
5461     } while(PieceToChar(pieceSweep) == '.');
5462     boards[currentMove][toY][toX] = pieceSweep;
5463     DrawPosition(FALSE, boards[currentMove]);
5464     boards[currentMove][toY][toX] = piece;
5465 }
5466 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5467 void
5468 AlphaRank (char *move, int n)
5469 {
5470 //    char *p = move, c; int x, y;
5471
5472     if (appData.debugMode) {
5473         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5474     }
5475
5476     if(move[1]=='*' &&
5477        move[2]>='0' && move[2]<='9' &&
5478        move[3]>='a' && move[3]<='x'    ) {
5479         move[1] = '@';
5480         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5481         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5482     } else
5483     if(move[0]>='0' && move[0]<='9' &&
5484        move[1]>='a' && move[1]<='x' &&
5485        move[2]>='0' && move[2]<='9' &&
5486        move[3]>='a' && move[3]<='x'    ) {
5487         /* input move, Shogi -> normal */
5488         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5489         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5490         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5491         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5492     } else
5493     if(move[1]=='@' &&
5494        move[3]>='0' && move[3]<='9' &&
5495        move[2]>='a' && move[2]<='x'    ) {
5496         move[1] = '*';
5497         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5498         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5499     } else
5500     if(
5501        move[0]>='a' && move[0]<='x' &&
5502        move[3]>='0' && move[3]<='9' &&
5503        move[2]>='a' && move[2]<='x'    ) {
5504          /* output move, normal -> Shogi */
5505         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5506         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5507         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5508         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5509         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5510     }
5511     if (appData.debugMode) {
5512         fprintf(debugFP, "   out = '%s'\n", move);
5513     }
5514 }
5515
5516 char yy_textstr[8000];
5517
5518 /* Parser for moves from gnuchess, ICS, or user typein box */
5519 Boolean
5520 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5521 {
5522     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5523
5524     switch (*moveType) {
5525       case WhitePromotion:
5526       case BlackPromotion:
5527       case WhiteNonPromotion:
5528       case BlackNonPromotion:
5529       case NormalMove:
5530       case FirstLeg:
5531       case WhiteCapturesEnPassant:
5532       case BlackCapturesEnPassant:
5533       case WhiteKingSideCastle:
5534       case WhiteQueenSideCastle:
5535       case BlackKingSideCastle:
5536       case BlackQueenSideCastle:
5537       case WhiteKingSideCastleWild:
5538       case WhiteQueenSideCastleWild:
5539       case BlackKingSideCastleWild:
5540       case BlackQueenSideCastleWild:
5541       /* Code added by Tord: */
5542       case WhiteHSideCastleFR:
5543       case WhiteASideCastleFR:
5544       case BlackHSideCastleFR:
5545       case BlackASideCastleFR:
5546       /* End of code added by Tord */
5547       case IllegalMove:         /* bug or odd chess variant */
5548         if(currentMoveString[1] == '@') { // illegal drop
5549           *fromX = WhiteOnMove(moveNum) ?
5550             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5551             (int) CharToPiece(ToLower(currentMoveString[0]));
5552           goto drop;
5553         }
5554         *fromX = currentMoveString[0] - AAA;
5555         *fromY = currentMoveString[1] - ONE;
5556         *toX = currentMoveString[2] - AAA;
5557         *toY = currentMoveString[3] - ONE;
5558         *promoChar = currentMoveString[4];
5559         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5560             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5561     if (appData.debugMode) {
5562         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5563     }
5564             *fromX = *fromY = *toX = *toY = 0;
5565             return FALSE;
5566         }
5567         if (appData.testLegality) {
5568           return (*moveType != IllegalMove);
5569         } else {
5570           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5571                          // [HGM] lion: if this is a double move we are less critical
5572                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5573         }
5574
5575       case WhiteDrop:
5576       case BlackDrop:
5577         *fromX = *moveType == WhiteDrop ?
5578           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5579           (int) CharToPiece(ToLower(currentMoveString[0]));
5580       drop:
5581         *fromY = DROP_RANK;
5582         *toX = currentMoveString[2] - AAA;
5583         *toY = currentMoveString[3] - ONE;
5584         *promoChar = NULLCHAR;
5585         return TRUE;
5586
5587       case AmbiguousMove:
5588       case ImpossibleMove:
5589       case EndOfFile:
5590       case ElapsedTime:
5591       case Comment:
5592       case PGNTag:
5593       case NAG:
5594       case WhiteWins:
5595       case BlackWins:
5596       case GameIsDrawn:
5597       default:
5598     if (appData.debugMode) {
5599         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5600     }
5601         /* bug? */
5602         *fromX = *fromY = *toX = *toY = 0;
5603         *promoChar = NULLCHAR;
5604         return FALSE;
5605     }
5606 }
5607
5608 Boolean pushed = FALSE;
5609 char *lastParseAttempt;
5610
5611 void
5612 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5613 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5614   int fromX, fromY, toX, toY; char promoChar;
5615   ChessMove moveType;
5616   Boolean valid;
5617   int nr = 0;
5618
5619   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5620   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5621     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5622     pushed = TRUE;
5623   }
5624   endPV = forwardMostMove;
5625   do {
5626     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5627     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5628     lastParseAttempt = pv;
5629     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5630     if(!valid && nr == 0 &&
5631        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5632         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5633         // Hande case where played move is different from leading PV move
5634         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5635         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5636         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5637         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5638           endPV += 2; // if position different, keep this
5639           moveList[endPV-1][0] = fromX + AAA;
5640           moveList[endPV-1][1] = fromY + ONE;
5641           moveList[endPV-1][2] = toX + AAA;
5642           moveList[endPV-1][3] = toY + ONE;
5643           parseList[endPV-1][0] = NULLCHAR;
5644           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5645         }
5646       }
5647     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5648     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5649     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5650     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5651         valid++; // allow comments in PV
5652         continue;
5653     }
5654     nr++;
5655     if(endPV+1 > framePtr) break; // no space, truncate
5656     if(!valid) break;
5657     endPV++;
5658     CopyBoard(boards[endPV], boards[endPV-1]);
5659     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5660     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5661     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5662     CoordsToAlgebraic(boards[endPV - 1],
5663                              PosFlags(endPV - 1),
5664                              fromY, fromX, toY, toX, promoChar,
5665                              parseList[endPV - 1]);
5666   } while(valid);
5667   if(atEnd == 2) return; // used hidden, for PV conversion
5668   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5669   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5670   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5671                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5672   DrawPosition(TRUE, boards[currentMove]);
5673 }
5674
5675 int
5676 MultiPV (ChessProgramState *cps, int kind)
5677 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5678         int i;
5679         for(i=0; i<cps->nrOptions; i++) {
5680             char *s = cps->option[i].name;
5681             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5682             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5683                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5684         }
5685         return -1;
5686 }
5687
5688 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5689 static int multi, pv_margin;
5690 static ChessProgramState *activeCps;
5691
5692 Boolean
5693 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5694 {
5695         int startPV, lineStart, origIndex = index;
5696         char *p, buf2[MSG_SIZ];
5697         ChessProgramState *cps = (pane ? &second : &first);
5698
5699         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5700         lastX = x; lastY = y;
5701         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5702         lineStart = startPV = index;
5703         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5704         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5705         index = startPV;
5706         do{ while(buf[index] && buf[index] != '\n') index++;
5707         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5708         buf[index] = 0;
5709         if(lineStart == 0 && gameMode == AnalyzeMode) {
5710             int n = 0;
5711             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5712             if(n == 0) { // click not on "fewer" or "more"
5713                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5714                     pv_margin = cps->option[multi].value;
5715                     activeCps = cps; // non-null signals margin adjustment
5716                 }
5717             } else if((multi = MultiPV(cps, 1)) >= 0) {
5718                 n += cps->option[multi].value; if(n < 1) n = 1;
5719                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5720                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5721                 cps->option[multi].value = n;
5722                 *start = *end = 0;
5723                 return FALSE;
5724             }
5725         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5726                 ExcludeClick(origIndex - lineStart);
5727                 return FALSE;
5728         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5729                 Collapse(origIndex - lineStart);
5730                 return FALSE;
5731         }
5732         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5733         *start = startPV; *end = index-1;
5734         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5735         return TRUE;
5736 }
5737
5738 char *
5739 PvToSAN (char *pv)
5740 {
5741         static char buf[10*MSG_SIZ];
5742         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5743         *buf = NULLCHAR;
5744         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5745         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5746         for(i = forwardMostMove; i<endPV; i++){
5747             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5748             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5749             k += strlen(buf+k);
5750         }
5751         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5752         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5753         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5754         endPV = savedEnd;
5755         return buf;
5756 }
5757
5758 Boolean
5759 LoadPV (int x, int y)
5760 { // called on right mouse click to load PV
5761   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5762   lastX = x; lastY = y;
5763   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5764   extendGame = FALSE;
5765   return TRUE;
5766 }
5767
5768 void
5769 UnLoadPV ()
5770 {
5771   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5772   if(activeCps) {
5773     if(pv_margin != activeCps->option[multi].value) {
5774       char buf[MSG_SIZ];
5775       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5776       SendToProgram(buf, activeCps);
5777       activeCps->option[multi].value = pv_margin;
5778     }
5779     activeCps = NULL;
5780     return;
5781   }
5782   if(endPV < 0) return;
5783   if(appData.autoCopyPV) CopyFENToClipboard();
5784   endPV = -1;
5785   if(extendGame && currentMove > forwardMostMove) {
5786         Boolean saveAnimate = appData.animate;
5787         if(pushed) {
5788             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5789                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5790             } else storedGames--; // abandon shelved tail of original game
5791         }
5792         pushed = FALSE;
5793         forwardMostMove = currentMove;
5794         currentMove = oldFMM;
5795         appData.animate = FALSE;
5796         ToNrEvent(forwardMostMove);
5797         appData.animate = saveAnimate;
5798   }
5799   currentMove = forwardMostMove;
5800   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5801   ClearPremoveHighlights();
5802   DrawPosition(TRUE, boards[currentMove]);
5803 }
5804
5805 void
5806 MovePV (int x, int y, int h)
5807 { // step through PV based on mouse coordinates (called on mouse move)
5808   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5809
5810   if(activeCps) { // adjusting engine's multi-pv margin
5811     if(x > lastX) pv_margin++; else
5812     if(x < lastX) pv_margin -= (pv_margin > 0);
5813     if(x != lastX) {
5814       char buf[MSG_SIZ];
5815       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5816       DisplayMessage(buf, "");
5817     }
5818     lastX = x;
5819     return;
5820   }
5821   // we must somehow check if right button is still down (might be released off board!)
5822   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5823   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5824   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5825   if(!step) return;
5826   lastX = x; lastY = y;
5827
5828   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5829   if(endPV < 0) return;
5830   if(y < margin) step = 1; else
5831   if(y > h - margin) step = -1;
5832   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5833   currentMove += step;
5834   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5835   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5836                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5837   DrawPosition(FALSE, boards[currentMove]);
5838 }
5839
5840
5841 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5842 // All positions will have equal probability, but the current method will not provide a unique
5843 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5844 #define DARK 1
5845 #define LITE 2
5846 #define ANY 3
5847
5848 int squaresLeft[4];
5849 int piecesLeft[(int)BlackPawn];
5850 int seed, nrOfShuffles;
5851
5852 void
5853 GetPositionNumber ()
5854 {       // sets global variable seed
5855         int i;
5856
5857         seed = appData.defaultFrcPosition;
5858         if(seed < 0) { // randomize based on time for negative FRC position numbers
5859                 for(i=0; i<50; i++) seed += random();
5860                 seed = random() ^ random() >> 8 ^ random() << 8;
5861                 if(seed<0) seed = -seed;
5862         }
5863 }
5864
5865 int
5866 put (Board board, int pieceType, int rank, int n, int shade)
5867 // put the piece on the (n-1)-th empty squares of the given shade
5868 {
5869         int i;
5870
5871         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5872                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5873                         board[rank][i] = (ChessSquare) pieceType;
5874                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5875                         squaresLeft[ANY]--;
5876                         piecesLeft[pieceType]--;
5877                         return i;
5878                 }
5879         }
5880         return -1;
5881 }
5882
5883
5884 void
5885 AddOnePiece (Board board, int pieceType, int rank, int shade)
5886 // calculate where the next piece goes, (any empty square), and put it there
5887 {
5888         int i;
5889
5890         i = seed % squaresLeft[shade];
5891         nrOfShuffles *= squaresLeft[shade];
5892         seed /= squaresLeft[shade];
5893         put(board, pieceType, rank, i, shade);
5894 }
5895
5896 void
5897 AddTwoPieces (Board board, int pieceType, int rank)
5898 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5899 {
5900         int i, n=squaresLeft[ANY], j=n-1, k;
5901
5902         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5903         i = seed % k;  // pick one
5904         nrOfShuffles *= k;
5905         seed /= k;
5906         while(i >= j) i -= j--;
5907         j = n - 1 - j; i += j;
5908         put(board, pieceType, rank, j, ANY);
5909         put(board, pieceType, rank, i, ANY);
5910 }
5911
5912 void
5913 SetUpShuffle (Board board, int number)
5914 {
5915         int i, p, first=1;
5916
5917         GetPositionNumber(); nrOfShuffles = 1;
5918
5919         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5920         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5921         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5922
5923         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5924
5925         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5926             p = (int) board[0][i];
5927             if(p < (int) BlackPawn) piecesLeft[p] ++;
5928             board[0][i] = EmptySquare;
5929         }
5930
5931         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5932             // shuffles restricted to allow normal castling put KRR first
5933             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5934                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5935             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5936                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5937             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5938                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5939             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5940                 put(board, WhiteRook, 0, 0, ANY);
5941             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5942         }
5943
5944         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5945             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5946             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5947                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5948                 while(piecesLeft[p] >= 2) {
5949                     AddOnePiece(board, p, 0, LITE);
5950                     AddOnePiece(board, p, 0, DARK);
5951                 }
5952                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5953             }
5954
5955         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5956             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5957             // but we leave King and Rooks for last, to possibly obey FRC restriction
5958             if(p == (int)WhiteRook) continue;
5959             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5960             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5961         }
5962
5963         // now everything is placed, except perhaps King (Unicorn) and Rooks
5964
5965         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5966             // Last King gets castling rights
5967             while(piecesLeft[(int)WhiteUnicorn]) {
5968                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5969                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5970             }
5971
5972             while(piecesLeft[(int)WhiteKing]) {
5973                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5974                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5975             }
5976
5977
5978         } else {
5979             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5980             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5981         }
5982
5983         // Only Rooks can be left; simply place them all
5984         while(piecesLeft[(int)WhiteRook]) {
5985                 i = put(board, WhiteRook, 0, 0, ANY);
5986                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5987                         if(first) {
5988                                 first=0;
5989                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5990                         }
5991                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5992                 }
5993         }
5994         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5995             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5996         }
5997
5998         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5999 }
6000
6001 int
6002 ptclen (const char *s, char *escapes)
6003 {
6004     int n = 0;
6005     if(!*escapes) return strlen(s);
6006     while(*s) n += (*s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)), s++;
6007     return n;
6008 }
6009
6010 static int pieceOrder[] = {
6011   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, // P N B R Q F E A C W M
6012  11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, // O H I J G D V L S U Lion
6013  45, 23, 24, 25, 26, 27, 28, 29, 46, 31, 32, // Sword Zebra Camel Tower Wolf Dragon Duck Axe Leopard Gnu Cub
6014  44, 51, 56, 57, 58, 59, 60, 61, 62, 63, 34, // Whale Pegasus Wizard Copper Iron Viking Flag Amazon Wheel Shield Claw
6015  33, 55, 53, 42, 37, 48, 39, 40, 41, 22, 30, // +P +N =B =R +L +S +E +Ph +Kn Butterfly Hat
6016  38, 43, 35, 36, 49, 47, 52, 50, 54, 64, 65 // +V +M =H =D Princess HSword +GB HCrown Wheer Shierd King
6017 };
6018
6019 int
6020 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6021 /* [HGM] moved here from winboard.c because of its general usefulness */
6022 /*       Basically a safe strcpy that uses the last character as King */
6023 {
6024     int result = FALSE; int NrPieces;
6025     unsigned char partner[EmptySquare];
6026
6027     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6028                     && NrPieces >= 12 && !(NrPieces&1)) {
6029         int i, ii, j = 0; /* [HGM] Accept even length from 12 to 88 */
6030
6031         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6032         for( ii=0; ii<NrPieces/2-1; ii++ ) {
6033             char *p, c=0;
6034             i = pieceOrder[ii];
6035             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6036             table[i] = map[j++];
6037             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
6038             if(c) partner[i] = table[i], table[i] = c;
6039         }
6040         table[(int) WhiteKing]  = map[j++];
6041         for( ii=0; ii<NrPieces/2-1; ii++ ) {
6042             char *p, c=0;
6043             i = WHITE_TO_BLACK pieceOrder[ii];
6044             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6045             table[i] = map[j++];
6046             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
6047             if(c) partner[i] = table[i], table[i] = c;
6048         }
6049         table[(int) BlackKing]  = map[j++];
6050
6051
6052         if(*escapes) { // set up promotion pairing
6053             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6054             // pieceToChar entirely filled, so we can look up specified partners
6055             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6056                 int c = table[i];
6057                 if(c == '^' || c == '-') { // has specified partner
6058                     int p;
6059                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6060                     if(c == '^') table[i] = '+';
6061                     if(p < EmptySquare) promoPartner[p] = i, promoPartner[i] = p; // marry them
6062                 } else if(c == '*') {
6063                     table[i] = partner[i];
6064                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6065                 }
6066             }
6067         }
6068
6069         result = TRUE;
6070     }
6071
6072     return result;
6073 }
6074
6075 int
6076 SetCharTable (unsigned char *table, const char * map)
6077 {
6078     return SetCharTableEsc(table, map, "");
6079 }
6080
6081 void
6082 Prelude (Board board)
6083 {       // [HGM] superchess: random selection of exo-pieces
6084         int i, j, k; ChessSquare p;
6085         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6086
6087         GetPositionNumber(); // use FRC position number
6088
6089         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6090             SetCharTable(pieceToChar, appData.pieceToCharTable);
6091             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6092                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6093         }
6094
6095         j = seed%4;                 seed /= 4;
6096         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6097         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6098         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6099         j = seed%3 + (seed%3 >= j); seed /= 3;
6100         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6101         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6102         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6103         j = seed%3;                 seed /= 3;
6104         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6105         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6106         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6107         j = seed%2 + (seed%2 >= j); seed /= 2;
6108         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6109         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6110         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6111         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6112         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6113         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6114         put(board, exoPieces[0],    0, 0, ANY);
6115         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6116 }
6117
6118 void
6119 InitPosition (int redraw)
6120 {
6121     ChessSquare (* pieces)[BOARD_FILES];
6122     int i, j, pawnRow=1, pieceRows=1, overrule,
6123     oldx = gameInfo.boardWidth,
6124     oldy = gameInfo.boardHeight,
6125     oldh = gameInfo.holdingsWidth;
6126     static int oldv;
6127
6128     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6129
6130     /* [AS] Initialize pv info list [HGM] and game status */
6131     {
6132         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6133             pvInfoList[i].depth = 0;
6134             boards[i][EP_STATUS] = EP_NONE;
6135             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6136         }
6137
6138         initialRulePlies = 0; /* 50-move counter start */
6139
6140         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6141         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6142     }
6143
6144
6145     /* [HGM] logic here is completely changed. In stead of full positions */
6146     /* the initialized data only consist of the two backranks. The switch */
6147     /* selects which one we will use, which is than copied to the Board   */
6148     /* initialPosition, which for the rest is initialized by Pawns and    */
6149     /* empty squares. This initial position is then copied to boards[0],  */
6150     /* possibly after shuffling, so that it remains available.            */
6151
6152     gameInfo.holdingsWidth = 0; /* default board sizes */
6153     gameInfo.boardWidth    = 8;
6154     gameInfo.boardHeight   = 8;
6155     gameInfo.holdingsSize  = 0;
6156     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6157     for(i=0; i<BOARD_FILES-6; i++)
6158       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6159     initialPosition[EP_STATUS] = EP_NONE;
6160     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6161     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6162     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6163          SetCharTable(pieceNickName, appData.pieceNickNames);
6164     else SetCharTable(pieceNickName, "............");
6165     pieces = FIDEArray;
6166
6167     switch (gameInfo.variant) {
6168     case VariantFischeRandom:
6169       shuffleOpenings = TRUE;
6170       appData.fischerCastling = TRUE;
6171     default:
6172       break;
6173     case VariantShatranj:
6174       pieces = ShatranjArray;
6175       nrCastlingRights = 0;
6176       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6177       break;
6178     case VariantMakruk:
6179       pieces = makrukArray;
6180       nrCastlingRights = 0;
6181       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6182       break;
6183     case VariantASEAN:
6184       pieces = aseanArray;
6185       nrCastlingRights = 0;
6186       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6187       break;
6188     case VariantTwoKings:
6189       pieces = twoKingsArray;
6190       break;
6191     case VariantGrand:
6192       pieces = GrandArray;
6193       nrCastlingRights = 0;
6194       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6195       gameInfo.boardWidth = 10;
6196       gameInfo.boardHeight = 10;
6197       gameInfo.holdingsSize = 7;
6198       break;
6199     case VariantCapaRandom:
6200       shuffleOpenings = TRUE;
6201       appData.fischerCastling = TRUE;
6202     case VariantCapablanca:
6203       pieces = CapablancaArray;
6204       gameInfo.boardWidth = 10;
6205       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6206       break;
6207     case VariantGothic:
6208       pieces = GothicArray;
6209       gameInfo.boardWidth = 10;
6210       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6211       break;
6212     case VariantSChess:
6213       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6214       gameInfo.holdingsSize = 7;
6215       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6216       break;
6217     case VariantJanus:
6218       pieces = JanusArray;
6219       gameInfo.boardWidth = 10;
6220       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6221       nrCastlingRights = 6;
6222         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6223         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6224         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6225         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6226         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6227         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6228       break;
6229     case VariantFalcon:
6230       pieces = FalconArray;
6231       gameInfo.boardWidth = 10;
6232       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6233       break;
6234     case VariantXiangqi:
6235       pieces = XiangqiArray;
6236       gameInfo.boardWidth  = 9;
6237       gameInfo.boardHeight = 10;
6238       nrCastlingRights = 0;
6239       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6240       break;
6241     case VariantShogi:
6242       pieces = ShogiArray;
6243       gameInfo.boardWidth  = 9;
6244       gameInfo.boardHeight = 9;
6245       gameInfo.holdingsSize = 7;
6246       nrCastlingRights = 0;
6247       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6248       break;
6249     case VariantChu:
6250       pieces = ChuArray; pieceRows = 3;
6251       gameInfo.boardWidth  = 12;
6252       gameInfo.boardHeight = 12;
6253       nrCastlingRights = 0;
6254       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/^P.^B^R.^S^E^X^O^G^C^A^T^H^D.^V^M^L^I^FK"
6255                                    "p.brqsexogcathd.vmlifn/^p.^b^r.^s^e^x^o^g^c^a^t^h^d.^v^m^l^i^fk", SUFFIXES);
6256       break;
6257     case VariantCourier:
6258       pieces = CourierArray;
6259       gameInfo.boardWidth  = 12;
6260       nrCastlingRights = 0;
6261       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6262       break;
6263     case VariantKnightmate:
6264       pieces = KnightmateArray;
6265       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6266       break;
6267     case VariantSpartan:
6268       pieces = SpartanArray;
6269       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6270       break;
6271     case VariantLion:
6272       pieces = lionArray;
6273       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6274       break;
6275     case VariantChuChess:
6276       pieces = ChuChessArray;
6277       gameInfo.boardWidth = 10;
6278       gameInfo.boardHeight = 10;
6279       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6280       break;
6281     case VariantFairy:
6282       pieces = fairyArray;
6283       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6284       break;
6285     case VariantGreat:
6286       pieces = GreatArray;
6287       gameInfo.boardWidth = 10;
6288       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6289       gameInfo.holdingsSize = 8;
6290       break;
6291     case VariantSuper:
6292       pieces = FIDEArray;
6293       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6294       gameInfo.holdingsSize = 8;
6295       startedFromSetupPosition = TRUE;
6296       break;
6297     case VariantCrazyhouse:
6298     case VariantBughouse:
6299       pieces = FIDEArray;
6300       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6301       gameInfo.holdingsSize = 5;
6302       break;
6303     case VariantWildCastle:
6304       pieces = FIDEArray;
6305       /* !!?shuffle with kings guaranteed to be on d or e file */
6306       shuffleOpenings = 1;
6307       break;
6308     case VariantNoCastle:
6309       pieces = FIDEArray;
6310       nrCastlingRights = 0;
6311       /* !!?unconstrained back-rank shuffle */
6312       shuffleOpenings = 1;
6313       break;
6314     }
6315
6316     overrule = 0;
6317     if(appData.NrFiles >= 0) {
6318         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6319         gameInfo.boardWidth = appData.NrFiles;
6320     }
6321     if(appData.NrRanks >= 0) {
6322         gameInfo.boardHeight = appData.NrRanks;
6323     }
6324     if(appData.holdingsSize >= 0) {
6325         i = appData.holdingsSize;
6326         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6327         gameInfo.holdingsSize = i;
6328     }
6329     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6330     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6331         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6332
6333     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6334     if(pawnRow < 1) pawnRow = 1;
6335     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6336        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6337     if(gameInfo.variant == VariantChu) pawnRow = 3;
6338
6339     /* User pieceToChar list overrules defaults */
6340     if(appData.pieceToCharTable != NULL)
6341         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6342
6343     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6344
6345         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6346             s = (ChessSquare) 0; /* account holding counts in guard band */
6347         for( i=0; i<BOARD_HEIGHT; i++ )
6348             initialPosition[i][j] = s;
6349
6350         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6351         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6352         initialPosition[pawnRow][j] = WhitePawn;
6353         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6354         if(gameInfo.variant == VariantXiangqi) {
6355             if(j&1) {
6356                 initialPosition[pawnRow][j] =
6357                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6358                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6359                    initialPosition[2][j] = WhiteCannon;
6360                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6361                 }
6362             }
6363         }
6364         if(gameInfo.variant == VariantChu) {
6365              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6366                initialPosition[pawnRow+1][j] = WhiteCobra,
6367                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6368              for(i=1; i<pieceRows; i++) {
6369                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6370                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6371              }
6372         }
6373         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6374             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6375                initialPosition[0][j] = WhiteRook;
6376                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6377             }
6378         }
6379         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6380     }
6381     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6382     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6383
6384             j=BOARD_LEFT+1;
6385             initialPosition[1][j] = WhiteBishop;
6386             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6387             j=BOARD_RGHT-2;
6388             initialPosition[1][j] = WhiteRook;
6389             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6390     }
6391
6392     if( nrCastlingRights == -1) {
6393         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6394         /*       This sets default castling rights from none to normal corners   */
6395         /* Variants with other castling rights must set them themselves above    */
6396         nrCastlingRights = 6;
6397
6398         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6399         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6400         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6401         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6402         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6403         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6404      }
6405
6406      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6407      if(gameInfo.variant == VariantGreat) { // promotion commoners
6408         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6409         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6410         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6411         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6412      }
6413      if( gameInfo.variant == VariantSChess ) {
6414       initialPosition[1][0] = BlackMarshall;
6415       initialPosition[2][0] = BlackAngel;
6416       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6417       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6418       initialPosition[1][1] = initialPosition[2][1] =
6419       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6420      }
6421   if (appData.debugMode) {
6422     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6423   }
6424     if(shuffleOpenings) {
6425         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6426         startedFromSetupPosition = TRUE;
6427     }
6428     if(startedFromPositionFile) {
6429       /* [HGM] loadPos: use PositionFile for every new game */
6430       CopyBoard(initialPosition, filePosition);
6431       for(i=0; i<nrCastlingRights; i++)
6432           initialRights[i] = filePosition[CASTLING][i];
6433       startedFromSetupPosition = TRUE;
6434     }
6435
6436     CopyBoard(boards[0], initialPosition);
6437
6438     if(oldx != gameInfo.boardWidth ||
6439        oldy != gameInfo.boardHeight ||
6440        oldv != gameInfo.variant ||
6441        oldh != gameInfo.holdingsWidth
6442                                          )
6443             InitDrawingSizes(-2 ,0);
6444
6445     oldv = gameInfo.variant;
6446     if (redraw)
6447       DrawPosition(TRUE, boards[currentMove]);
6448 }
6449
6450 void
6451 SendBoard (ChessProgramState *cps, int moveNum)
6452 {
6453     char message[MSG_SIZ];
6454
6455     if (cps->useSetboard) {
6456       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6457       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6458       SendToProgram(message, cps);
6459       free(fen);
6460
6461     } else {
6462       ChessSquare *bp;
6463       int i, j, left=0, right=BOARD_WIDTH;
6464       /* Kludge to set black to move, avoiding the troublesome and now
6465        * deprecated "black" command.
6466        */
6467       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6468         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6469
6470       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6471
6472       SendToProgram("edit\n", cps);
6473       SendToProgram("#\n", cps);
6474       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6475         bp = &boards[moveNum][i][left];
6476         for (j = left; j < right; j++, bp++) {
6477           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6478           if ((int) *bp < (int) BlackPawn) {
6479             if(j == BOARD_RGHT+1)
6480                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6481             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6482             if(message[0] == '+' || message[0] == '~') {
6483               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6484                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6485                         AAA + j, ONE + i - '0');
6486             }
6487             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6488                 message[1] = BOARD_RGHT   - 1 - j + '1';
6489                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6490             }
6491             SendToProgram(message, cps);
6492           }
6493         }
6494       }
6495
6496       SendToProgram("c\n", cps);
6497       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6498         bp = &boards[moveNum][i][left];
6499         for (j = left; j < right; j++, bp++) {
6500           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6501           if (((int) *bp != (int) EmptySquare)
6502               && ((int) *bp >= (int) BlackPawn)) {
6503             if(j == BOARD_LEFT-2)
6504                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6505             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6506                     AAA + j, ONE + i - '0');
6507             if(message[0] == '+' || message[0] == '~') {
6508               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6509                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6510                         AAA + j, ONE + i - '0');
6511             }
6512             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6513                 message[1] = BOARD_RGHT   - 1 - j + '1';
6514                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6515             }
6516             SendToProgram(message, cps);
6517           }
6518         }
6519       }
6520
6521       SendToProgram(".\n", cps);
6522     }
6523     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6524 }
6525
6526 char exclusionHeader[MSG_SIZ];
6527 int exCnt, excludePtr;
6528 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6529 static Exclusion excluTab[200];
6530 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6531
6532 static void
6533 WriteMap (int s)
6534 {
6535     int j;
6536     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6537     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6538 }
6539
6540 static void
6541 ClearMap ()
6542 {
6543     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6544     excludePtr = 24; exCnt = 0;
6545     WriteMap(0);
6546 }
6547
6548 static void
6549 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6550 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6551     char buf[2*MOVE_LEN], *p;
6552     Exclusion *e = excluTab;
6553     int i;
6554     for(i=0; i<exCnt; i++)
6555         if(e[i].ff == fromX && e[i].fr == fromY &&
6556            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6557     if(i == exCnt) { // was not in exclude list; add it
6558         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6559         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6560             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6561             return; // abort
6562         }
6563         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6564         excludePtr++; e[i].mark = excludePtr++;
6565         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6566         exCnt++;
6567     }
6568     exclusionHeader[e[i].mark] = state;
6569 }
6570
6571 static int
6572 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6573 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6574     char buf[MSG_SIZ];
6575     int j, k;
6576     ChessMove moveType;
6577     if((signed char)promoChar == -1) { // kludge to indicate best move
6578         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6579             return 1; // if unparsable, abort
6580     }
6581     // update exclusion map (resolving toggle by consulting existing state)
6582     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6583     j = k%8; k >>= 3;
6584     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6585     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6586          excludeMap[k] |=   1<<j;
6587     else excludeMap[k] &= ~(1<<j);
6588     // update header
6589     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6590     // inform engine
6591     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6592     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6593     SendToBoth(buf);
6594     return (state == '+');
6595 }
6596
6597 static void
6598 ExcludeClick (int index)
6599 {
6600     int i, j;
6601     Exclusion *e = excluTab;
6602     if(index < 25) { // none, best or tail clicked
6603         if(index < 13) { // none: include all
6604             WriteMap(0); // clear map
6605             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6606             SendToBoth("include all\n"); // and inform engine
6607         } else if(index > 18) { // tail
6608             if(exclusionHeader[19] == '-') { // tail was excluded
6609                 SendToBoth("include all\n");
6610                 WriteMap(0); // clear map completely
6611                 // now re-exclude selected moves
6612                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6613                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6614             } else { // tail was included or in mixed state
6615                 SendToBoth("exclude all\n");
6616                 WriteMap(0xFF); // fill map completely
6617                 // now re-include selected moves
6618                 j = 0; // count them
6619                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6620                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6621                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6622             }
6623         } else { // best
6624             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6625         }
6626     } else {
6627         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6628             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6629             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6630             break;
6631         }
6632     }
6633 }
6634
6635 ChessSquare
6636 DefaultPromoChoice (int white)
6637 {
6638     ChessSquare result;
6639     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6640        gameInfo.variant == VariantMakruk)
6641         result = WhiteFerz; // no choice
6642     else if(gameInfo.variant == VariantASEAN)
6643         result = WhiteRook; // no choice
6644     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6645         result= WhiteKing; // in Suicide Q is the last thing we want
6646     else if(gameInfo.variant == VariantSpartan)
6647         result = white ? WhiteQueen : WhiteAngel;
6648     else result = WhiteQueen;
6649     if(!white) result = WHITE_TO_BLACK result;
6650     return result;
6651 }
6652
6653 static int autoQueen; // [HGM] oneclick
6654
6655 int
6656 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6657 {
6658     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6659     /* [HGM] add Shogi promotions */
6660     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6661     ChessSquare piece, partner;
6662     ChessMove moveType;
6663     Boolean premove;
6664
6665     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6666     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6667
6668     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6669       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6670         return FALSE;
6671
6672     piece = boards[currentMove][fromY][fromX];
6673     if(gameInfo.variant == VariantChu) {
6674         promotionZoneSize = BOARD_HEIGHT/3;
6675         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6676     } else if(gameInfo.variant == VariantShogi) {
6677         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6678         highestPromotingPiece = (int)WhiteAlfil;
6679     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6680         promotionZoneSize = 3;
6681     }
6682
6683     // Treat Lance as Pawn when it is not representing Amazon or Lance
6684     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6685         if(piece == WhiteLance) piece = WhitePawn; else
6686         if(piece == BlackLance) piece = BlackPawn;
6687     }
6688
6689     // next weed out all moves that do not touch the promotion zone at all
6690     if((int)piece >= BlackPawn) {
6691         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6692              return FALSE;
6693         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6694         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6695     } else {
6696         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6697            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6698         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6699              return FALSE;
6700     }
6701
6702     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6703
6704     // weed out mandatory Shogi promotions
6705     if(gameInfo.variant == VariantShogi) {
6706         if(piece >= BlackPawn) {
6707             if(toY == 0 && piece == BlackPawn ||
6708                toY == 0 && piece == BlackQueen ||
6709                toY <= 1 && piece == BlackKnight) {
6710                 *promoChoice = '+';
6711                 return FALSE;
6712             }
6713         } else {
6714             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6715                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6716                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6717                 *promoChoice = '+';
6718                 return FALSE;
6719             }
6720         }
6721     }
6722
6723     // weed out obviously illegal Pawn moves
6724     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6725         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6726         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6727         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6728         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6729         // note we are not allowed to test for valid (non-)capture, due to premove
6730     }
6731
6732     // we either have a choice what to promote to, or (in Shogi) whether to promote
6733     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6734        gameInfo.variant == VariantMakruk) {
6735         ChessSquare p=BlackFerz;  // no choice
6736         while(p < EmptySquare) {  //but make sure we use piece that exists
6737             *promoChoice = PieceToChar(p++);
6738             if(*promoChoice != '.') break;
6739         }
6740         return FALSE;
6741     }
6742     // no sense asking what we must promote to if it is going to explode...
6743     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6744         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6745         return FALSE;
6746     }
6747     // give caller the default choice even if we will not make it
6748     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6749     partner = piece; // pieces can promote if the pieceToCharTable says so
6750     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6751     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6752     if(        sweepSelect && gameInfo.variant != VariantGreat
6753                            && gameInfo.variant != VariantGrand
6754                            && gameInfo.variant != VariantSuper) return FALSE;
6755     if(autoQueen) return FALSE; // predetermined
6756
6757     // suppress promotion popup on illegal moves that are not premoves
6758     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6759               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6760     if(appData.testLegality && !premove) {
6761         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6762                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6763         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6764         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6765             return FALSE;
6766     }
6767
6768     return TRUE;
6769 }
6770
6771 int
6772 InPalace (int row, int column)
6773 {   /* [HGM] for Xiangqi */
6774     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6775          column < (BOARD_WIDTH + 4)/2 &&
6776          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6777     return FALSE;
6778 }
6779
6780 int
6781 PieceForSquare (int x, int y)
6782 {
6783   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6784      return -1;
6785   else
6786      return boards[currentMove][y][x];
6787 }
6788
6789 int
6790 OKToStartUserMove (int x, int y)
6791 {
6792     ChessSquare from_piece;
6793     int white_piece;
6794
6795     if (matchMode) return FALSE;
6796     if (gameMode == EditPosition) return TRUE;
6797
6798     if (x >= 0 && y >= 0)
6799       from_piece = boards[currentMove][y][x];
6800     else
6801       from_piece = EmptySquare;
6802
6803     if (from_piece == EmptySquare) return FALSE;
6804
6805     white_piece = (int)from_piece >= (int)WhitePawn &&
6806       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6807
6808     switch (gameMode) {
6809       case AnalyzeFile:
6810       case TwoMachinesPlay:
6811       case EndOfGame:
6812         return FALSE;
6813
6814       case IcsObserving:
6815       case IcsIdle:
6816         return FALSE;
6817
6818       case MachinePlaysWhite:
6819       case IcsPlayingBlack:
6820         if (appData.zippyPlay) return FALSE;
6821         if (white_piece) {
6822             DisplayMoveError(_("You are playing Black"));
6823             return FALSE;
6824         }
6825         break;
6826
6827       case MachinePlaysBlack:
6828       case IcsPlayingWhite:
6829         if (appData.zippyPlay) return FALSE;
6830         if (!white_piece) {
6831             DisplayMoveError(_("You are playing White"));
6832             return FALSE;
6833         }
6834         break;
6835
6836       case PlayFromGameFile:
6837             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6838       case EditGame:
6839         if (!white_piece && WhiteOnMove(currentMove)) {
6840             DisplayMoveError(_("It is White's turn"));
6841             return FALSE;
6842         }
6843         if (white_piece && !WhiteOnMove(currentMove)) {
6844             DisplayMoveError(_("It is Black's turn"));
6845             return FALSE;
6846         }
6847         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6848             /* Editing correspondence game history */
6849             /* Could disallow this or prompt for confirmation */
6850             cmailOldMove = -1;
6851         }
6852         break;
6853
6854       case BeginningOfGame:
6855         if (appData.icsActive) return FALSE;
6856         if (!appData.noChessProgram) {
6857             if (!white_piece) {
6858                 DisplayMoveError(_("You are playing White"));
6859                 return FALSE;
6860             }
6861         }
6862         break;
6863
6864       case Training:
6865         if (!white_piece && WhiteOnMove(currentMove)) {
6866             DisplayMoveError(_("It is White's turn"));
6867             return FALSE;
6868         }
6869         if (white_piece && !WhiteOnMove(currentMove)) {
6870             DisplayMoveError(_("It is Black's turn"));
6871             return FALSE;
6872         }
6873         break;
6874
6875       default:
6876       case IcsExamining:
6877         break;
6878     }
6879     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6880         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6881         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6882         && gameMode != AnalyzeFile && gameMode != Training) {
6883         DisplayMoveError(_("Displayed position is not current"));
6884         return FALSE;
6885     }
6886     return TRUE;
6887 }
6888
6889 Boolean
6890 OnlyMove (int *x, int *y, Boolean captures)
6891 {
6892     DisambiguateClosure cl;
6893     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6894     switch(gameMode) {
6895       case MachinePlaysBlack:
6896       case IcsPlayingWhite:
6897       case BeginningOfGame:
6898         if(!WhiteOnMove(currentMove)) return FALSE;
6899         break;
6900       case MachinePlaysWhite:
6901       case IcsPlayingBlack:
6902         if(WhiteOnMove(currentMove)) return FALSE;
6903         break;
6904       case EditGame:
6905         break;
6906       default:
6907         return FALSE;
6908     }
6909     cl.pieceIn = EmptySquare;
6910     cl.rfIn = *y;
6911     cl.ffIn = *x;
6912     cl.rtIn = -1;
6913     cl.ftIn = -1;
6914     cl.promoCharIn = NULLCHAR;
6915     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6916     if( cl.kind == NormalMove ||
6917         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6918         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6919         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6920       fromX = cl.ff;
6921       fromY = cl.rf;
6922       *x = cl.ft;
6923       *y = cl.rt;
6924       return TRUE;
6925     }
6926     if(cl.kind != ImpossibleMove) return FALSE;
6927     cl.pieceIn = EmptySquare;
6928     cl.rfIn = -1;
6929     cl.ffIn = -1;
6930     cl.rtIn = *y;
6931     cl.ftIn = *x;
6932     cl.promoCharIn = NULLCHAR;
6933     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6934     if( cl.kind == NormalMove ||
6935         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6936         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6937         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6938       fromX = cl.ff;
6939       fromY = cl.rf;
6940       *x = cl.ft;
6941       *y = cl.rt;
6942       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6943       return TRUE;
6944     }
6945     return FALSE;
6946 }
6947
6948 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6949 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6950 int lastLoadGameUseList = FALSE;
6951 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6952 ChessMove lastLoadGameStart = EndOfFile;
6953 int doubleClick;
6954 Boolean addToBookFlag;
6955
6956 void
6957 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6958 {
6959     ChessMove moveType;
6960     ChessSquare pup;
6961     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6962
6963     /* Check if the user is playing in turn.  This is complicated because we
6964        let the user "pick up" a piece before it is his turn.  So the piece he
6965        tried to pick up may have been captured by the time he puts it down!
6966        Therefore we use the color the user is supposed to be playing in this
6967        test, not the color of the piece that is currently on the starting
6968        square---except in EditGame mode, where the user is playing both
6969        sides; fortunately there the capture race can't happen.  (It can
6970        now happen in IcsExamining mode, but that's just too bad.  The user
6971        will get a somewhat confusing message in that case.)
6972        */
6973
6974     switch (gameMode) {
6975       case AnalyzeFile:
6976       case TwoMachinesPlay:
6977       case EndOfGame:
6978       case IcsObserving:
6979       case IcsIdle:
6980         /* We switched into a game mode where moves are not accepted,
6981            perhaps while the mouse button was down. */
6982         return;
6983
6984       case MachinePlaysWhite:
6985         /* User is moving for Black */
6986         if (WhiteOnMove(currentMove)) {
6987             DisplayMoveError(_("It is White's turn"));
6988             return;
6989         }
6990         break;
6991
6992       case MachinePlaysBlack:
6993         /* User is moving for White */
6994         if (!WhiteOnMove(currentMove)) {
6995             DisplayMoveError(_("It is Black's turn"));
6996             return;
6997         }
6998         break;
6999
7000       case PlayFromGameFile:
7001             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7002       case EditGame:
7003       case IcsExamining:
7004       case BeginningOfGame:
7005       case AnalyzeMode:
7006       case Training:
7007         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7008         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7009             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7010             /* User is moving for Black */
7011             if (WhiteOnMove(currentMove)) {
7012                 DisplayMoveError(_("It is White's turn"));
7013                 return;
7014             }
7015         } else {
7016             /* User is moving for White */
7017             if (!WhiteOnMove(currentMove)) {
7018                 DisplayMoveError(_("It is Black's turn"));
7019                 return;
7020             }
7021         }
7022         break;
7023
7024       case IcsPlayingBlack:
7025         /* User is moving for Black */
7026         if (WhiteOnMove(currentMove)) {
7027             if (!appData.premove) {
7028                 DisplayMoveError(_("It is White's turn"));
7029             } else if (toX >= 0 && toY >= 0) {
7030                 premoveToX = toX;
7031                 premoveToY = toY;
7032                 premoveFromX = fromX;
7033                 premoveFromY = fromY;
7034                 premovePromoChar = promoChar;
7035                 gotPremove = 1;
7036                 if (appData.debugMode)
7037                     fprintf(debugFP, "Got premove: fromX %d,"
7038                             "fromY %d, toX %d, toY %d\n",
7039                             fromX, fromY, toX, toY);
7040             }
7041             return;
7042         }
7043         break;
7044
7045       case IcsPlayingWhite:
7046         /* User is moving for White */
7047         if (!WhiteOnMove(currentMove)) {
7048             if (!appData.premove) {
7049                 DisplayMoveError(_("It is Black's turn"));
7050             } else if (toX >= 0 && toY >= 0) {
7051                 premoveToX = toX;
7052                 premoveToY = toY;
7053                 premoveFromX = fromX;
7054                 premoveFromY = fromY;
7055                 premovePromoChar = promoChar;
7056                 gotPremove = 1;
7057                 if (appData.debugMode)
7058                     fprintf(debugFP, "Got premove: fromX %d,"
7059                             "fromY %d, toX %d, toY %d\n",
7060                             fromX, fromY, toX, toY);
7061             }
7062             return;
7063         }
7064         break;
7065
7066       default:
7067         break;
7068
7069       case EditPosition:
7070         /* EditPosition, empty square, or different color piece;
7071            click-click move is possible */
7072         if (toX == -2 || toY == -2) {
7073             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7074             DrawPosition(FALSE, boards[currentMove]);
7075             return;
7076         } else if (toX >= 0 && toY >= 0) {
7077             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7078                 ChessSquare p = boards[0][rf][ff];
7079                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7080                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7081             }
7082             boards[0][toY][toX] = boards[0][fromY][fromX];
7083             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7084                 if(boards[0][fromY][0] != EmptySquare) {
7085                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7086                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7087                 }
7088             } else
7089             if(fromX == BOARD_RGHT+1) {
7090                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7091                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7092                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7093                 }
7094             } else
7095             boards[0][fromY][fromX] = gatingPiece;
7096             ClearHighlights();
7097             DrawPosition(FALSE, boards[currentMove]);
7098             return;
7099         }
7100         return;
7101     }
7102
7103     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7104     pup = boards[currentMove][toY][toX];
7105
7106     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7107     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7108          if( pup != EmptySquare ) return;
7109          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7110            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7111                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7112            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7113            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7114            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7115            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7116          fromY = DROP_RANK;
7117     }
7118
7119     /* [HGM] always test for legality, to get promotion info */
7120     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7121                                          fromY, fromX, toY, toX, promoChar);
7122
7123     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7124
7125     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7126
7127     /* [HGM] but possibly ignore an IllegalMove result */
7128     if (appData.testLegality) {
7129         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7130             DisplayMoveError(_("Illegal move"));
7131             return;
7132         }
7133     }
7134
7135     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7136         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7137              ClearPremoveHighlights(); // was included
7138         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7139         return;
7140     }
7141
7142     if(addToBookFlag) { // adding moves to book
7143         char buf[MSG_SIZ], move[MSG_SIZ];
7144         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7145         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
7146         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7147         AddBookMove(buf);
7148         addToBookFlag = FALSE;
7149         ClearHighlights();
7150         return;
7151     }
7152
7153     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7154 }
7155
7156 /* Common tail of UserMoveEvent and DropMenuEvent */
7157 int
7158 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7159 {
7160     char *bookHit = 0;
7161
7162     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7163         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7164         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7165         if(WhiteOnMove(currentMove)) {
7166             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7167         } else {
7168             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7169         }
7170     }
7171
7172     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7173        move type in caller when we know the move is a legal promotion */
7174     if(moveType == NormalMove && promoChar)
7175         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7176
7177     /* [HGM] <popupFix> The following if has been moved here from
7178        UserMoveEvent(). Because it seemed to belong here (why not allow
7179        piece drops in training games?), and because it can only be
7180        performed after it is known to what we promote. */
7181     if (gameMode == Training) {
7182       /* compare the move played on the board to the next move in the
7183        * game. If they match, display the move and the opponent's response.
7184        * If they don't match, display an error message.
7185        */
7186       int saveAnimate;
7187       Board testBoard;
7188       CopyBoard(testBoard, boards[currentMove]);
7189       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7190
7191       if (CompareBoards(testBoard, boards[currentMove+1])) {
7192         ForwardInner(currentMove+1);
7193
7194         /* Autoplay the opponent's response.
7195          * if appData.animate was TRUE when Training mode was entered,
7196          * the response will be animated.
7197          */
7198         saveAnimate = appData.animate;
7199         appData.animate = animateTraining;
7200         ForwardInner(currentMove+1);
7201         appData.animate = saveAnimate;
7202
7203         /* check for the end of the game */
7204         if (currentMove >= forwardMostMove) {
7205           gameMode = PlayFromGameFile;
7206           ModeHighlight();
7207           SetTrainingModeOff();
7208           DisplayInformation(_("End of game"));
7209         }
7210       } else {
7211         DisplayError(_("Incorrect move"), 0);
7212       }
7213       return 1;
7214     }
7215
7216   /* Ok, now we know that the move is good, so we can kill
7217      the previous line in Analysis Mode */
7218   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7219                                 && currentMove < forwardMostMove) {
7220     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7221     else forwardMostMove = currentMove;
7222   }
7223
7224   ClearMap();
7225
7226   /* If we need the chess program but it's dead, restart it */
7227   ResurrectChessProgram();
7228
7229   /* A user move restarts a paused game*/
7230   if (pausing)
7231     PauseEvent();
7232
7233   thinkOutput[0] = NULLCHAR;
7234
7235   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7236
7237   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7238     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7239     return 1;
7240   }
7241
7242   if (gameMode == BeginningOfGame) {
7243     if (appData.noChessProgram) {
7244       gameMode = EditGame;
7245       SetGameInfo();
7246     } else {
7247       char buf[MSG_SIZ];
7248       gameMode = MachinePlaysBlack;
7249       StartClocks();
7250       SetGameInfo();
7251       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7252       DisplayTitle(buf);
7253       if (first.sendName) {
7254         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7255         SendToProgram(buf, &first);
7256       }
7257       StartClocks();
7258     }
7259     ModeHighlight();
7260   }
7261
7262   /* Relay move to ICS or chess engine */
7263   if (appData.icsActive) {
7264     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7265         gameMode == IcsExamining) {
7266       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7267         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7268         SendToICS("draw ");
7269         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7270       }
7271       // also send plain move, in case ICS does not understand atomic claims
7272       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7273       ics_user_moved = 1;
7274     }
7275   } else {
7276     if (first.sendTime && (gameMode == BeginningOfGame ||
7277                            gameMode == MachinePlaysWhite ||
7278                            gameMode == MachinePlaysBlack)) {
7279       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7280     }
7281     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7282          // [HGM] book: if program might be playing, let it use book
7283         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7284         first.maybeThinking = TRUE;
7285     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7286         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7287         SendBoard(&first, currentMove+1);
7288         if(second.analyzing) {
7289             if(!second.useSetboard) SendToProgram("undo\n", &second);
7290             SendBoard(&second, currentMove+1);
7291         }
7292     } else {
7293         SendMoveToProgram(forwardMostMove-1, &first);
7294         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7295     }
7296     if (currentMove == cmailOldMove + 1) {
7297       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7298     }
7299   }
7300
7301   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7302
7303   switch (gameMode) {
7304   case EditGame:
7305     if(appData.testLegality)
7306     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7307     case MT_NONE:
7308     case MT_CHECK:
7309       break;
7310     case MT_CHECKMATE:
7311     case MT_STAINMATE:
7312       if (WhiteOnMove(currentMove)) {
7313         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7314       } else {
7315         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7316       }
7317       break;
7318     case MT_STALEMATE:
7319       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7320       break;
7321     }
7322     break;
7323
7324   case MachinePlaysBlack:
7325   case MachinePlaysWhite:
7326     /* disable certain menu options while machine is thinking */
7327     SetMachineThinkingEnables();
7328     break;
7329
7330   default:
7331     break;
7332   }
7333
7334   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7335   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7336
7337   if(bookHit) { // [HGM] book: simulate book reply
7338         static char bookMove[MSG_SIZ]; // a bit generous?
7339
7340         programStats.nodes = programStats.depth = programStats.time =
7341         programStats.score = programStats.got_only_move = 0;
7342         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7343
7344         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7345         strcat(bookMove, bookHit);
7346         HandleMachineMove(bookMove, &first);
7347   }
7348   return 1;
7349 }
7350
7351 void
7352 MarkByFEN(char *fen)
7353 {
7354         int r, f;
7355         if(!appData.markers || !appData.highlightDragging) return;
7356         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7357         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7358         while(*fen) {
7359             int s = 0;
7360             marker[r][f] = 0;
7361             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7362             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7363             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7364             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7365             if(*fen == 'T') marker[r][f++] = 0; else
7366             if(*fen == 'Y') marker[r][f++] = 1; else
7367             if(*fen == 'G') marker[r][f++] = 3; else
7368             if(*fen == 'B') marker[r][f++] = 4; else
7369             if(*fen == 'C') marker[r][f++] = 5; else
7370             if(*fen == 'M') marker[r][f++] = 6; else
7371             if(*fen == 'W') marker[r][f++] = 7; else
7372             if(*fen == 'D') marker[r][f++] = 8; else
7373             if(*fen == 'R') marker[r][f++] = 2; else {
7374                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7375               f += s; fen -= s>0;
7376             }
7377             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7378             if(r < 0) break;
7379             fen++;
7380         }
7381         DrawPosition(TRUE, NULL);
7382 }
7383
7384 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7385
7386 void
7387 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7388 {
7389     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7390     Markers *m = (Markers *) closure;
7391     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7392         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7393                          || kind == WhiteCapturesEnPassant
7394                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7395     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7396 }
7397
7398 static int hoverSavedValid;
7399
7400 void
7401 MarkTargetSquares (int clear)
7402 {
7403   int x, y, sum=0;
7404   if(clear) { // no reason to ever suppress clearing
7405     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7406     hoverSavedValid = 0;
7407     if(!sum) return; // nothing was cleared,no redraw needed
7408   } else {
7409     int capt = 0;
7410     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7411        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7412     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7413     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7414       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7415       if(capt)
7416       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7417     }
7418   }
7419   DrawPosition(FALSE, NULL);
7420 }
7421
7422 int
7423 Explode (Board board, int fromX, int fromY, int toX, int toY)
7424 {
7425     if(gameInfo.variant == VariantAtomic &&
7426        (board[toY][toX] != EmptySquare ||                     // capture?
7427         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7428                          board[fromY][fromX] == BlackPawn   )
7429       )) {
7430         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7431         return TRUE;
7432     }
7433     return FALSE;
7434 }
7435
7436 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7437
7438 int
7439 CanPromote (ChessSquare piece, int y)
7440 {
7441         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7442         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7443         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7444         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7445            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7446            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7447          gameInfo.variant == VariantMakruk) return FALSE;
7448         return (piece == BlackPawn && y <= zone ||
7449                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7450                 piece == BlackLance && y <= zone ||
7451                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7452 }
7453
7454 void
7455 HoverEvent (int xPix, int yPix, int x, int y)
7456 {
7457         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7458         int r, f;
7459         if(!first.highlight) return;
7460         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7461         if(x == oldX && y == oldY) return; // only do something if we enter new square
7462         oldFromX = fromX; oldFromY = fromY;
7463         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7464           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7465             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7466           hoverSavedValid = 1;
7467         } else if(oldX != x || oldY != y) {
7468           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7469           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7470           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7471             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7472           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7473             char buf[MSG_SIZ];
7474             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7475             SendToProgram(buf, &first);
7476           }
7477           oldX = x; oldY = y;
7478 //        SetHighlights(fromX, fromY, x, y);
7479         }
7480 }
7481
7482 void ReportClick(char *action, int x, int y)
7483 {
7484         char buf[MSG_SIZ]; // Inform engine of what user does
7485         int r, f;
7486         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7487           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7488             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7489         if(!first.highlight || gameMode == EditPosition) return;
7490         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7491         SendToProgram(buf, &first);
7492 }
7493
7494 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7495
7496 void
7497 LeftClick (ClickType clickType, int xPix, int yPix)
7498 {
7499     int x, y;
7500     Boolean saveAnimate;
7501     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7502     char promoChoice = NULLCHAR;
7503     ChessSquare piece;
7504     static TimeMark lastClickTime, prevClickTime;
7505
7506     x = EventToSquare(xPix, BOARD_WIDTH);
7507     y = EventToSquare(yPix, BOARD_HEIGHT);
7508     if (!flipView && y >= 0) {
7509         y = BOARD_HEIGHT - 1 - y;
7510     }
7511     if (flipView && x >= 0) {
7512         x = BOARD_WIDTH - 1 - x;
7513     }
7514
7515     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7516         static int dummy;
7517         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7518         right = TRUE;
7519         return;
7520     }
7521
7522     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7523
7524     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7525
7526     if (clickType == Press) ErrorPopDown();
7527     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7528
7529     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7530         defaultPromoChoice = promoSweep;
7531         promoSweep = EmptySquare;   // terminate sweep
7532         promoDefaultAltered = TRUE;
7533         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7534     }
7535
7536     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7537         if(clickType == Release) return; // ignore upclick of click-click destination
7538         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7539         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7540         if(gameInfo.holdingsWidth &&
7541                 (WhiteOnMove(currentMove)
7542                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7543                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7544             // click in right holdings, for determining promotion piece
7545             ChessSquare p = boards[currentMove][y][x];
7546             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7547             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7548             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7549                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7550                 fromX = fromY = -1;
7551                 return;
7552             }
7553         }
7554         DrawPosition(FALSE, boards[currentMove]);
7555         return;
7556     }
7557
7558     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7559     if(clickType == Press
7560             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7561               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7562               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7563         return;
7564
7565     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7566         // could be static click on premove from-square: abort premove
7567         gotPremove = 0;
7568         ClearPremoveHighlights();
7569     }
7570
7571     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7572         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7573
7574     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7575         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7576                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7577         defaultPromoChoice = DefaultPromoChoice(side);
7578     }
7579
7580     autoQueen = appData.alwaysPromoteToQueen;
7581
7582     if (fromX == -1) {
7583       int originalY = y;
7584       gatingPiece = EmptySquare;
7585       if (clickType != Press) {
7586         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7587             DragPieceEnd(xPix, yPix); dragging = 0;
7588             DrawPosition(FALSE, NULL);
7589         }
7590         return;
7591       }
7592       doubleClick = FALSE;
7593       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7594         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7595       }
7596       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7597       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7598          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7599          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7600             /* First square */
7601             if (OKToStartUserMove(fromX, fromY)) {
7602                 second = 0;
7603                 ReportClick("lift", x, y);
7604                 MarkTargetSquares(0);
7605                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7606                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7607                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7608                     promoSweep = defaultPromoChoice;
7609                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7610                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7611                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7612                 }
7613                 if (appData.highlightDragging) {
7614                     SetHighlights(fromX, fromY, -1, -1);
7615                 } else {
7616                     ClearHighlights();
7617                 }
7618             } else fromX = fromY = -1;
7619             return;
7620         }
7621     }
7622
7623     /* fromX != -1 */
7624     if (clickType == Press && gameMode != EditPosition) {
7625         ChessSquare fromP;
7626         ChessSquare toP;
7627         int frc;
7628
7629         // ignore off-board to clicks
7630         if(y < 0 || x < 0) return;
7631
7632         /* Check if clicking again on the same color piece */
7633         fromP = boards[currentMove][fromY][fromX];
7634         toP = boards[currentMove][y][x];
7635         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7636         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7637             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7638            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7639              WhitePawn <= toP && toP <= WhiteKing &&
7640              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7641              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7642             (BlackPawn <= fromP && fromP <= BlackKing &&
7643              BlackPawn <= toP && toP <= BlackKing &&
7644              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7645              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7646             /* Clicked again on same color piece -- changed his mind */
7647             second = (x == fromX && y == fromY);
7648             killX = killY = -1;
7649             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7650                 second = FALSE; // first double-click rather than scond click
7651                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7652             }
7653             promoDefaultAltered = FALSE;
7654             MarkTargetSquares(1);
7655            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7656             if (appData.highlightDragging) {
7657                 SetHighlights(x, y, -1, -1);
7658             } else {
7659                 ClearHighlights();
7660             }
7661             if (OKToStartUserMove(x, y)) {
7662                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7663                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7664                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7665                  gatingPiece = boards[currentMove][fromY][fromX];
7666                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7667                 fromX = x;
7668                 fromY = y; dragging = 1;
7669                 if(!second) ReportClick("lift", x, y);
7670                 MarkTargetSquares(0);
7671                 DragPieceBegin(xPix, yPix, FALSE);
7672                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7673                     promoSweep = defaultPromoChoice;
7674                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7675                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7676                 }
7677             }
7678            }
7679            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7680            second = FALSE;
7681         }
7682         // ignore clicks on holdings
7683         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7684     }
7685
7686     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7687         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7688         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7689         return;
7690     }
7691
7692     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7693         DragPieceEnd(xPix, yPix); dragging = 0;
7694         if(clearFlag) {
7695             // a deferred attempt to click-click move an empty square on top of a piece
7696             boards[currentMove][y][x] = EmptySquare;
7697             ClearHighlights();
7698             DrawPosition(FALSE, boards[currentMove]);
7699             fromX = fromY = -1; clearFlag = 0;
7700             return;
7701         }
7702         if (appData.animateDragging) {
7703             /* Undo animation damage if any */
7704             DrawPosition(FALSE, NULL);
7705         }
7706         if (second) {
7707             /* Second up/down in same square; just abort move */
7708             second = 0;
7709             fromX = fromY = -1;
7710             gatingPiece = EmptySquare;
7711             MarkTargetSquares(1);
7712             ClearHighlights();
7713             gotPremove = 0;
7714             ClearPremoveHighlights();
7715         } else {
7716             /* First upclick in same square; start click-click mode */
7717             SetHighlights(x, y, -1, -1);
7718         }
7719         return;
7720     }
7721
7722     clearFlag = 0;
7723
7724     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7725        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7726         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7727         DisplayMessage(_("only marked squares are legal"),"");
7728         DrawPosition(TRUE, NULL);
7729         return; // ignore to-click
7730     }
7731
7732     /* we now have a different from- and (possibly off-board) to-square */
7733     /* Completed move */
7734     if(!sweepSelecting) {
7735         toX = x;
7736         toY = y;
7737     }
7738
7739     piece = boards[currentMove][fromY][fromX];
7740
7741     saveAnimate = appData.animate;
7742     if (clickType == Press) {
7743         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7744         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7745             // must be Edit Position mode with empty-square selected
7746             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7747             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7748             return;
7749         }
7750         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7751             return;
7752         }
7753         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7754             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7755         } else
7756         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7757         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7758           if(appData.sweepSelect) {
7759             promoSweep = defaultPromoChoice;
7760             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7761             selectFlag = 0; lastX = xPix; lastY = yPix;
7762             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7763             Sweep(0); // Pawn that is going to promote: preview promotion piece
7764             sweepSelecting = 1;
7765             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7766             MarkTargetSquares(1);
7767           }
7768           return; // promo popup appears on up-click
7769         }
7770         /* Finish clickclick move */
7771         if (appData.animate || appData.highlightLastMove) {
7772             SetHighlights(fromX, fromY, toX, toY);
7773         } else {
7774             ClearHighlights();
7775         }
7776     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7777         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7778         *promoRestrict = 0;
7779         if (appData.animate || appData.highlightLastMove) {
7780             SetHighlights(fromX, fromY, toX, toY);
7781         } else {
7782             ClearHighlights();
7783         }
7784     } else {
7785 #if 0
7786 // [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
7787         /* Finish drag move */
7788         if (appData.highlightLastMove) {
7789             SetHighlights(fromX, fromY, toX, toY);
7790         } else {
7791             ClearHighlights();
7792         }
7793 #endif
7794         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7795           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7796         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7797         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7798           dragging *= 2;            // flag button-less dragging if we are dragging
7799           MarkTargetSquares(1);
7800           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7801           else {
7802             kill2X = killX; kill2Y = killY;
7803             killX = x; killY = y;     //remeber this square as intermediate
7804             ReportClick("put", x, y); // and inform engine
7805             ReportClick("lift", x, y);
7806             MarkTargetSquares(0);
7807             return;
7808           }
7809         }
7810         DragPieceEnd(xPix, yPix); dragging = 0;
7811         /* Don't animate move and drag both */
7812         appData.animate = FALSE;
7813     }
7814
7815     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7816     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7817         ChessSquare piece = boards[currentMove][fromY][fromX];
7818         if(gameMode == EditPosition && piece != EmptySquare &&
7819            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7820             int n;
7821
7822             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7823                 n = PieceToNumber(piece - (int)BlackPawn);
7824                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7825                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7826                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7827             } else
7828             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7829                 n = PieceToNumber(piece);
7830                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7831                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7832                 boards[currentMove][n][BOARD_WIDTH-2]++;
7833             }
7834             boards[currentMove][fromY][fromX] = EmptySquare;
7835         }
7836         ClearHighlights();
7837         fromX = fromY = -1;
7838         MarkTargetSquares(1);
7839         DrawPosition(TRUE, boards[currentMove]);
7840         return;
7841     }
7842
7843     // off-board moves should not be highlighted
7844     if(x < 0 || y < 0) ClearHighlights();
7845     else ReportClick("put", x, y);
7846
7847     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7848
7849     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7850
7851     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7852         SetHighlights(fromX, fromY, toX, toY);
7853         MarkTargetSquares(1);
7854         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7855             // [HGM] super: promotion to captured piece selected from holdings
7856             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7857             promotionChoice = TRUE;
7858             // kludge follows to temporarily execute move on display, without promoting yet
7859             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7860             boards[currentMove][toY][toX] = p;
7861             DrawPosition(FALSE, boards[currentMove]);
7862             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7863             boards[currentMove][toY][toX] = q;
7864             DisplayMessage("Click in holdings to choose piece", "");
7865             return;
7866         }
7867         PromotionPopUp(promoChoice);
7868     } else {
7869         int oldMove = currentMove;
7870         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7871         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7872         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7873         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7874            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7875             DrawPosition(TRUE, boards[currentMove]);
7876         MarkTargetSquares(1);
7877         fromX = fromY = -1;
7878     }
7879     appData.animate = saveAnimate;
7880     if (appData.animate || appData.animateDragging) {
7881         /* Undo animation damage if needed */
7882         DrawPosition(FALSE, NULL);
7883     }
7884 }
7885
7886 int
7887 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7888 {   // front-end-free part taken out of PieceMenuPopup
7889     int whichMenu; int xSqr, ySqr;
7890
7891     if(seekGraphUp) { // [HGM] seekgraph
7892         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7893         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7894         return -2;
7895     }
7896
7897     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7898          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7899         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7900         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7901         if(action == Press)   {
7902             originalFlip = flipView;
7903             flipView = !flipView; // temporarily flip board to see game from partners perspective
7904             DrawPosition(TRUE, partnerBoard);
7905             DisplayMessage(partnerStatus, "");
7906             partnerUp = TRUE;
7907         } else if(action == Release) {
7908             flipView = originalFlip;
7909             DrawPosition(TRUE, boards[currentMove]);
7910             partnerUp = FALSE;
7911         }
7912         return -2;
7913     }
7914
7915     xSqr = EventToSquare(x, BOARD_WIDTH);
7916     ySqr = EventToSquare(y, BOARD_HEIGHT);
7917     if (action == Release) {
7918         if(pieceSweep != EmptySquare) {
7919             EditPositionMenuEvent(pieceSweep, toX, toY);
7920             pieceSweep = EmptySquare;
7921         } else UnLoadPV(); // [HGM] pv
7922     }
7923     if (action != Press) return -2; // return code to be ignored
7924     switch (gameMode) {
7925       case IcsExamining:
7926         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7927       case EditPosition:
7928         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7929         if (xSqr < 0 || ySqr < 0) return -1;
7930         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7931         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7932         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7933         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7934         NextPiece(0);
7935         return 2; // grab
7936       case IcsObserving:
7937         if(!appData.icsEngineAnalyze) return -1;
7938       case IcsPlayingWhite:
7939       case IcsPlayingBlack:
7940         if(!appData.zippyPlay) goto noZip;
7941       case AnalyzeMode:
7942       case AnalyzeFile:
7943       case MachinePlaysWhite:
7944       case MachinePlaysBlack:
7945       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7946         if (!appData.dropMenu) {
7947           LoadPV(x, y);
7948           return 2; // flag front-end to grab mouse events
7949         }
7950         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7951            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7952       case EditGame:
7953       noZip:
7954         if (xSqr < 0 || ySqr < 0) return -1;
7955         if (!appData.dropMenu || appData.testLegality &&
7956             gameInfo.variant != VariantBughouse &&
7957             gameInfo.variant != VariantCrazyhouse) return -1;
7958         whichMenu = 1; // drop menu
7959         break;
7960       default:
7961         return -1;
7962     }
7963
7964     if (((*fromX = xSqr) < 0) ||
7965         ((*fromY = ySqr) < 0)) {
7966         *fromX = *fromY = -1;
7967         return -1;
7968     }
7969     if (flipView)
7970       *fromX = BOARD_WIDTH - 1 - *fromX;
7971     else
7972       *fromY = BOARD_HEIGHT - 1 - *fromY;
7973
7974     return whichMenu;
7975 }
7976
7977 void
7978 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7979 {
7980 //    char * hint = lastHint;
7981     FrontEndProgramStats stats;
7982
7983     stats.which = cps == &first ? 0 : 1;
7984     stats.depth = cpstats->depth;
7985     stats.nodes = cpstats->nodes;
7986     stats.score = cpstats->score;
7987     stats.time = cpstats->time;
7988     stats.pv = cpstats->movelist;
7989     stats.hint = lastHint;
7990     stats.an_move_index = 0;
7991     stats.an_move_count = 0;
7992
7993     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7994         stats.hint = cpstats->move_name;
7995         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7996         stats.an_move_count = cpstats->nr_moves;
7997     }
7998
7999     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
8000
8001     SetProgramStats( &stats );
8002 }
8003
8004 void
8005 ClearEngineOutputPane (int which)
8006 {
8007     static FrontEndProgramStats dummyStats;
8008     dummyStats.which = which;
8009     dummyStats.pv = "#";
8010     SetProgramStats( &dummyStats );
8011 }
8012
8013 #define MAXPLAYERS 500
8014
8015 char *
8016 TourneyStandings (int display)
8017 {
8018     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8019     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8020     char result, *p, *names[MAXPLAYERS];
8021
8022     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8023         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8024     names[0] = p = strdup(appData.participants);
8025     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8026
8027     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8028
8029     while(result = appData.results[nr]) {
8030         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8031         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8032         wScore = bScore = 0;
8033         switch(result) {
8034           case '+': wScore = 2; break;
8035           case '-': bScore = 2; break;
8036           case '=': wScore = bScore = 1; break;
8037           case ' ':
8038           case '*': return strdup("busy"); // tourney not finished
8039         }
8040         score[w] += wScore;
8041         score[b] += bScore;
8042         games[w]++;
8043         games[b]++;
8044         nr++;
8045     }
8046     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8047     for(w=0; w<nPlayers; w++) {
8048         bScore = -1;
8049         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8050         ranking[w] = b; points[w] = bScore; score[b] = -2;
8051     }
8052     p = malloc(nPlayers*34+1);
8053     for(w=0; w<nPlayers && w<display; w++)
8054         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8055     free(names[0]);
8056     return p;
8057 }
8058
8059 void
8060 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8061 {       // count all piece types
8062         int p, f, r;
8063         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8064         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8065         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8066                 p = board[r][f];
8067                 pCnt[p]++;
8068                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8069                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8070                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8071                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8072                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8073                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8074         }
8075 }
8076
8077 int
8078 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8079 {
8080         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8081         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8082
8083         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8084         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8085         if(myPawns == 2 && nMine == 3) // KPP
8086             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8087         if(myPawns == 1 && nMine == 2) // KP
8088             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8089         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8090             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8091         if(myPawns) return FALSE;
8092         if(pCnt[WhiteRook+side])
8093             return pCnt[BlackRook-side] ||
8094                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8095                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8096                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8097         if(pCnt[WhiteCannon+side]) {
8098             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8099             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8100         }
8101         if(pCnt[WhiteKnight+side])
8102             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8103         return FALSE;
8104 }
8105
8106 int
8107 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8108 {
8109         VariantClass v = gameInfo.variant;
8110
8111         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8112         if(v == VariantShatranj) return TRUE; // always winnable through baring
8113         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8114         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8115
8116         if(v == VariantXiangqi) {
8117                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8118
8119                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8120                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8121                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8122                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8123                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8124                 if(stale) // we have at least one last-rank P plus perhaps C
8125                     return majors // KPKX
8126                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8127                 else // KCA*E*
8128                     return pCnt[WhiteFerz+side] // KCAK
8129                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8130                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8131                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8132
8133         } else if(v == VariantKnightmate) {
8134                 if(nMine == 1) return FALSE;
8135                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8136         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8137                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8138
8139                 if(nMine == 1) return FALSE; // bare King
8140                 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
8141                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8142                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8143                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8144                 if(pCnt[WhiteKnight+side])
8145                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8146                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8147                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8148                 if(nBishops)
8149                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8150                 if(pCnt[WhiteAlfil+side])
8151                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8152                 if(pCnt[WhiteWazir+side])
8153                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8154         }
8155
8156         return TRUE;
8157 }
8158
8159 int
8160 CompareWithRights (Board b1, Board b2)
8161 {
8162     int rights = 0;
8163     if(!CompareBoards(b1, b2)) return FALSE;
8164     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8165     /* compare castling rights */
8166     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8167            rights++; /* King lost rights, while rook still had them */
8168     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8169         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8170            rights++; /* but at least one rook lost them */
8171     }
8172     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8173            rights++;
8174     if( b1[CASTLING][5] != NoRights ) {
8175         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8176            rights++;
8177     }
8178     return rights == 0;
8179 }
8180
8181 int
8182 Adjudicate (ChessProgramState *cps)
8183 {       // [HGM] some adjudications useful with buggy engines
8184         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8185         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8186         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8187         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8188         int k, drop, count = 0; static int bare = 1;
8189         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8190         Boolean canAdjudicate = !appData.icsActive;
8191
8192         // most tests only when we understand the game, i.e. legality-checking on
8193             if( appData.testLegality )
8194             {   /* [HGM] Some more adjudications for obstinate engines */
8195                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8196                 static int moveCount = 6;
8197                 ChessMove result;
8198                 char *reason = NULL;
8199
8200                 /* Count what is on board. */
8201                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8202
8203                 /* Some material-based adjudications that have to be made before stalemate test */
8204                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8205                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8206                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8207                      if(canAdjudicate && appData.checkMates) {
8208                          if(engineOpponent)
8209                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8210                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8211                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8212                          return 1;
8213                      }
8214                 }
8215
8216                 /* Bare King in Shatranj (loses) or Losers (wins) */
8217                 if( nrW == 1 || nrB == 1) {
8218                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8219                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8220                      if(canAdjudicate && appData.checkMates) {
8221                          if(engineOpponent)
8222                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8223                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8224                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8225                          return 1;
8226                      }
8227                   } else
8228                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8229                   {    /* bare King */
8230                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8231                         if(canAdjudicate && appData.checkMates) {
8232                             /* but only adjudicate if adjudication enabled */
8233                             if(engineOpponent)
8234                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8235                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8236                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8237                             return 1;
8238                         }
8239                   }
8240                 } else bare = 1;
8241
8242
8243             // don't wait for engine to announce game end if we can judge ourselves
8244             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8245               case MT_CHECK:
8246                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8247                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8248                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8249                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8250                             checkCnt++;
8251                         if(checkCnt >= 2) {
8252                             reason = "Xboard adjudication: 3rd check";
8253                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8254                             break;
8255                         }
8256                     }
8257                 }
8258               case MT_NONE:
8259               default:
8260                 break;
8261               case MT_STEALMATE:
8262               case MT_STALEMATE:
8263               case MT_STAINMATE:
8264                 reason = "Xboard adjudication: Stalemate";
8265                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8266                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8267                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8268                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8269                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8270                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8271                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8272                                                                         EP_CHECKMATE : EP_WINS);
8273                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8274                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8275                 }
8276                 break;
8277               case MT_CHECKMATE:
8278                 reason = "Xboard adjudication: Checkmate";
8279                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8280                 if(gameInfo.variant == VariantShogi) {
8281                     if(forwardMostMove > backwardMostMove
8282                        && moveList[forwardMostMove-1][1] == '@'
8283                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8284                         reason = "XBoard adjudication: pawn-drop mate";
8285                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8286                     }
8287                 }
8288                 break;
8289             }
8290
8291                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8292                     case EP_STALEMATE:
8293                         result = GameIsDrawn; break;
8294                     case EP_CHECKMATE:
8295                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8296                     case EP_WINS:
8297                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8298                     default:
8299                         result = EndOfFile;
8300                 }
8301                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8302                     if(engineOpponent)
8303                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8304                     GameEnds( result, reason, GE_XBOARD );
8305                     return 1;
8306                 }
8307
8308                 /* Next absolutely insufficient mating material. */
8309                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8310                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8311                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8312
8313                      /* always flag draws, for judging claims */
8314                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8315
8316                      if(canAdjudicate && appData.materialDraws) {
8317                          /* but only adjudicate them if adjudication enabled */
8318                          if(engineOpponent) {
8319                            SendToProgram("force\n", engineOpponent); // suppress reply
8320                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8321                          }
8322                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8323                          return 1;
8324                      }
8325                 }
8326
8327                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8328                 if(gameInfo.variant == VariantXiangqi ?
8329                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8330                  : nrW + nrB == 4 &&
8331                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8332                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8333                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8334                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8335                    ) ) {
8336                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8337                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8338                           if(engineOpponent) {
8339                             SendToProgram("force\n", engineOpponent); // suppress reply
8340                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8341                           }
8342                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8343                           return 1;
8344                      }
8345                 } else moveCount = 6;
8346             }
8347
8348         // Repetition draws and 50-move rule can be applied independently of legality testing
8349
8350                 /* Check for rep-draws */
8351                 count = 0;
8352                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8353                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8354                 for(k = forwardMostMove-2;
8355                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8356                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8357                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8358                     k-=2)
8359                 {   int rights=0;
8360                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8361                         /* compare castling rights */
8362                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8363                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8364                                 rights++; /* King lost rights, while rook still had them */
8365                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8366                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8367                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8368                                    rights++; /* but at least one rook lost them */
8369                         }
8370                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8371                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8372                                 rights++;
8373                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8374                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8375                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8376                                    rights++;
8377                         }
8378                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8379                             && appData.drawRepeats > 1) {
8380                              /* adjudicate after user-specified nr of repeats */
8381                              int result = GameIsDrawn;
8382                              char *details = "XBoard adjudication: repetition draw";
8383                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8384                                 // [HGM] xiangqi: check for forbidden perpetuals
8385                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8386                                 for(m=forwardMostMove; m>k; m-=2) {
8387                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8388                                         ourPerpetual = 0; // the current mover did not always check
8389                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8390                                         hisPerpetual = 0; // the opponent did not always check
8391                                 }
8392                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8393                                                                         ourPerpetual, hisPerpetual);
8394                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8395                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8396                                     details = "Xboard adjudication: perpetual checking";
8397                                 } else
8398                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8399                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8400                                 } else
8401                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8402                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8403                                         result = BlackWins;
8404                                         details = "Xboard adjudication: repetition";
8405                                     }
8406                                 } else // it must be XQ
8407                                 // Now check for perpetual chases
8408                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8409                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8410                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8411                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8412                                         static char resdet[MSG_SIZ];
8413                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8414                                         details = resdet;
8415                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8416                                     } else
8417                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8418                                         break; // Abort repetition-checking loop.
8419                                 }
8420                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8421                              }
8422                              if(engineOpponent) {
8423                                SendToProgram("force\n", engineOpponent); // suppress reply
8424                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8425                              }
8426                              GameEnds( result, details, GE_XBOARD );
8427                              return 1;
8428                         }
8429                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8430                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8431                     }
8432                 }
8433
8434                 /* Now we test for 50-move draws. Determine ply count */
8435                 count = forwardMostMove;
8436                 /* look for last irreversble move */
8437                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8438                     count--;
8439                 /* if we hit starting position, add initial plies */
8440                 if( count == backwardMostMove )
8441                     count -= initialRulePlies;
8442                 count = forwardMostMove - count;
8443                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8444                         // adjust reversible move counter for checks in Xiangqi
8445                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8446                         if(i < backwardMostMove) i = backwardMostMove;
8447                         while(i <= forwardMostMove) {
8448                                 lastCheck = inCheck; // check evasion does not count
8449                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8450                                 if(inCheck || lastCheck) count--; // check does not count
8451                                 i++;
8452                         }
8453                 }
8454                 if( count >= 100)
8455                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8456                          /* this is used to judge if draw claims are legal */
8457                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8458                          if(engineOpponent) {
8459                            SendToProgram("force\n", engineOpponent); // suppress reply
8460                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8461                          }
8462                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8463                          return 1;
8464                 }
8465
8466                 /* if draw offer is pending, treat it as a draw claim
8467                  * when draw condition present, to allow engines a way to
8468                  * claim draws before making their move to avoid a race
8469                  * condition occurring after their move
8470                  */
8471                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8472                          char *p = NULL;
8473                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8474                              p = "Draw claim: 50-move rule";
8475                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8476                              p = "Draw claim: 3-fold repetition";
8477                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8478                              p = "Draw claim: insufficient mating material";
8479                          if( p != NULL && canAdjudicate) {
8480                              if(engineOpponent) {
8481                                SendToProgram("force\n", engineOpponent); // suppress reply
8482                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8483                              }
8484                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8485                              return 1;
8486                          }
8487                 }
8488
8489                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8490                     if(engineOpponent) {
8491                       SendToProgram("force\n", engineOpponent); // suppress reply
8492                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8493                     }
8494                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8495                     return 1;
8496                 }
8497         return 0;
8498 }
8499
8500 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8501 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8502 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8503
8504 static int
8505 BitbaseProbe ()
8506 {
8507     int pieces[10], squares[10], cnt=0, r, f, res;
8508     static int loaded;
8509     static PPROBE_EGBB probeBB;
8510     if(!appData.testLegality) return 10;
8511     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8512     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8513     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8514     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8515         ChessSquare piece = boards[forwardMostMove][r][f];
8516         int black = (piece >= BlackPawn);
8517         int type = piece - black*BlackPawn;
8518         if(piece == EmptySquare) continue;
8519         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8520         if(type == WhiteKing) type = WhiteQueen + 1;
8521         type = egbbCode[type];
8522         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8523         pieces[cnt] = type + black*6;
8524         if(++cnt > 5) return 11;
8525     }
8526     pieces[cnt] = squares[cnt] = 0;
8527     // probe EGBB
8528     if(loaded == 2) return 13; // loading failed before
8529     if(loaded == 0) {
8530         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8531         HMODULE lib;
8532         PLOAD_EGBB loadBB;
8533         loaded = 2; // prepare for failure
8534         if(!path) return 13; // no egbb installed
8535         strncpy(buf, path + 8, MSG_SIZ);
8536         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8537         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8538         lib = LoadLibrary(buf);
8539         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8540         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8541         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8542         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8543         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8544         loaded = 1; // success!
8545     }
8546     res = probeBB(forwardMostMove & 1, pieces, squares);
8547     return res > 0 ? 1 : res < 0 ? -1 : 0;
8548 }
8549
8550 char *
8551 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8552 {   // [HGM] book: this routine intercepts moves to simulate book replies
8553     char *bookHit = NULL;
8554
8555     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8556         char buf[MSG_SIZ];
8557         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8558         SendToProgram(buf, cps);
8559     }
8560     //first determine if the incoming move brings opponent into his book
8561     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8562         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8563     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8564     if(bookHit != NULL && !cps->bookSuspend) {
8565         // make sure opponent is not going to reply after receiving move to book position
8566         SendToProgram("force\n", cps);
8567         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8568     }
8569     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8570     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8571     // now arrange restart after book miss
8572     if(bookHit) {
8573         // after a book hit we never send 'go', and the code after the call to this routine
8574         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8575         char buf[MSG_SIZ], *move = bookHit;
8576         if(cps->useSAN) {
8577             int fromX, fromY, toX, toY;
8578             char promoChar;
8579             ChessMove moveType;
8580             move = buf + 30;
8581             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8582                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8583                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8584                                     PosFlags(forwardMostMove),
8585                                     fromY, fromX, toY, toX, promoChar, move);
8586             } else {
8587                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8588                 bookHit = NULL;
8589             }
8590         }
8591         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8592         SendToProgram(buf, cps);
8593         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8594     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8595         SendToProgram("go\n", cps);
8596         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8597     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8598         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8599             SendToProgram("go\n", cps);
8600         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8601     }
8602     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8603 }
8604
8605 int
8606 LoadError (char *errmess, ChessProgramState *cps)
8607 {   // unloads engine and switches back to -ncp mode if it was first
8608     if(cps->initDone) return FALSE;
8609     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8610     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8611     cps->pr = NoProc;
8612     if(cps == &first) {
8613         appData.noChessProgram = TRUE;
8614         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8615         gameMode = BeginningOfGame; ModeHighlight();
8616         SetNCPMode();
8617     }
8618     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8619     DisplayMessage("", ""); // erase waiting message
8620     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8621     return TRUE;
8622 }
8623
8624 char *savedMessage;
8625 ChessProgramState *savedState;
8626 void
8627 DeferredBookMove (void)
8628 {
8629         if(savedState->lastPing != savedState->lastPong)
8630                     ScheduleDelayedEvent(DeferredBookMove, 10);
8631         else
8632         HandleMachineMove(savedMessage, savedState);
8633 }
8634
8635 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8636 static ChessProgramState *stalledEngine;
8637 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8638
8639 void
8640 HandleMachineMove (char *message, ChessProgramState *cps)
8641 {
8642     static char firstLeg[20];
8643     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8644     char realname[MSG_SIZ];
8645     int fromX, fromY, toX, toY;
8646     ChessMove moveType;
8647     char promoChar, roar;
8648     char *p, *pv=buf1;
8649     int oldError;
8650     char *bookHit;
8651
8652     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8653         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8654         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8655             DisplayError(_("Invalid pairing from pairing engine"), 0);
8656             return;
8657         }
8658         pairingReceived = 1;
8659         NextMatchGame();
8660         return; // Skim the pairing messages here.
8661     }
8662
8663     oldError = cps->userError; cps->userError = 0;
8664
8665 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8666     /*
8667      * Kludge to ignore BEL characters
8668      */
8669     while (*message == '\007') message++;
8670
8671     /*
8672      * [HGM] engine debug message: ignore lines starting with '#' character
8673      */
8674     if(cps->debug && *message == '#') return;
8675
8676     /*
8677      * Look for book output
8678      */
8679     if (cps == &first && bookRequested) {
8680         if (message[0] == '\t' || message[0] == ' ') {
8681             /* Part of the book output is here; append it */
8682             strcat(bookOutput, message);
8683             strcat(bookOutput, "  \n");
8684             return;
8685         } else if (bookOutput[0] != NULLCHAR) {
8686             /* All of book output has arrived; display it */
8687             char *p = bookOutput;
8688             while (*p != NULLCHAR) {
8689                 if (*p == '\t') *p = ' ';
8690                 p++;
8691             }
8692             DisplayInformation(bookOutput);
8693             bookRequested = FALSE;
8694             /* Fall through to parse the current output */
8695         }
8696     }
8697
8698     /*
8699      * Look for machine move.
8700      */
8701     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8702         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8703     {
8704         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8705             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8706             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8707             stalledEngine = cps;
8708             if(appData.ponderNextMove) { // bring opponent out of ponder
8709                 if(gameMode == TwoMachinesPlay) {
8710                     if(cps->other->pause)
8711                         PauseEngine(cps->other);
8712                     else
8713                         SendToProgram("easy\n", cps->other);
8714                 }
8715             }
8716             StopClocks();
8717             return;
8718         }
8719
8720       if(cps->usePing) {
8721
8722         /* This method is only useful on engines that support ping */
8723         if(abortEngineThink) {
8724             if (appData.debugMode) {
8725                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8726             }
8727             SendToProgram("undo\n", cps);
8728             return;
8729         }
8730
8731         if (cps->lastPing != cps->lastPong) {
8732             /* Extra move from before last new; ignore */
8733             if (appData.debugMode) {
8734                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8735             }
8736           return;
8737         }
8738
8739       } else {
8740
8741         int machineWhite = FALSE;
8742
8743         switch (gameMode) {
8744           case BeginningOfGame:
8745             /* Extra move from before last reset; ignore */
8746             if (appData.debugMode) {
8747                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8748             }
8749             return;
8750
8751           case EndOfGame:
8752           case IcsIdle:
8753           default:
8754             /* Extra move after we tried to stop.  The mode test is
8755                not a reliable way of detecting this problem, but it's
8756                the best we can do on engines that don't support ping.
8757             */
8758             if (appData.debugMode) {
8759                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8760                         cps->which, gameMode);
8761             }
8762             SendToProgram("undo\n", cps);
8763             return;
8764
8765           case MachinePlaysWhite:
8766           case IcsPlayingWhite:
8767             machineWhite = TRUE;
8768             break;
8769
8770           case MachinePlaysBlack:
8771           case IcsPlayingBlack:
8772             machineWhite = FALSE;
8773             break;
8774
8775           case TwoMachinesPlay:
8776             machineWhite = (cps->twoMachinesColor[0] == 'w');
8777             break;
8778         }
8779         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8780             if (appData.debugMode) {
8781                 fprintf(debugFP,
8782                         "Ignoring move out of turn by %s, gameMode %d"
8783                         ", forwardMost %d\n",
8784                         cps->which, gameMode, forwardMostMove);
8785             }
8786             return;
8787         }
8788       }
8789
8790         if(cps->alphaRank) AlphaRank(machineMove, 4);
8791
8792         // [HGM] lion: (some very limited) support for Alien protocol
8793         killX = killY = kill2X = kill2Y = -1;
8794         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8795             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8796             return;
8797         }
8798         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8799             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8800             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8801         }
8802         if(firstLeg[0]) { // there was a previous leg;
8803             // only support case where same piece makes two step
8804             char buf[20], *p = machineMove+1, *q = buf+1, f;
8805             safeStrCpy(buf, machineMove, 20);
8806             while(isdigit(*q)) q++; // find start of to-square
8807             safeStrCpy(machineMove, firstLeg, 20);
8808             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8809             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8810             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8811             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8812             firstLeg[0] = NULLCHAR;
8813         }
8814
8815         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8816                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8817             /* Machine move could not be parsed; ignore it. */
8818           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8819                     machineMove, _(cps->which));
8820             DisplayMoveError(buf1);
8821             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8822                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8823             if (gameMode == TwoMachinesPlay) {
8824               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8825                        buf1, GE_XBOARD);
8826             }
8827             return;
8828         }
8829
8830         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8831         /* So we have to redo legality test with true e.p. status here,  */
8832         /* to make sure an illegal e.p. capture does not slip through,   */
8833         /* to cause a forfeit on a justified illegal-move complaint      */
8834         /* of the opponent.                                              */
8835         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8836            ChessMove moveType;
8837            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8838                              fromY, fromX, toY, toX, promoChar);
8839             if(moveType == IllegalMove) {
8840               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8841                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8842                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8843                            buf1, GE_XBOARD);
8844                 return;
8845            } else if(!appData.fischerCastling)
8846            /* [HGM] Kludge to handle engines that send FRC-style castling
8847               when they shouldn't (like TSCP-Gothic) */
8848            switch(moveType) {
8849              case WhiteASideCastleFR:
8850              case BlackASideCastleFR:
8851                toX+=2;
8852                currentMoveString[2]++;
8853                break;
8854              case WhiteHSideCastleFR:
8855              case BlackHSideCastleFR:
8856                toX--;
8857                currentMoveString[2]--;
8858                break;
8859              default: ; // nothing to do, but suppresses warning of pedantic compilers
8860            }
8861         }
8862         hintRequested = FALSE;
8863         lastHint[0] = NULLCHAR;
8864         bookRequested = FALSE;
8865         /* Program may be pondering now */
8866         cps->maybeThinking = TRUE;
8867         if (cps->sendTime == 2) cps->sendTime = 1;
8868         if (cps->offeredDraw) cps->offeredDraw--;
8869
8870         /* [AS] Save move info*/
8871         pvInfoList[ forwardMostMove ].score = programStats.score;
8872         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8873         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8874
8875         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8876
8877         /* Test suites abort the 'game' after one move */
8878         if(*appData.finger) {
8879            static FILE *f;
8880            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8881            if(!f) f = fopen(appData.finger, "w");
8882            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8883            else { DisplayFatalError("Bad output file", errno, 0); return; }
8884            free(fen);
8885            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8886         }
8887         if(appData.epd) {
8888            if(solvingTime >= 0) {
8889               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8890               totalTime += solvingTime; first.matchWins++;
8891            } else {
8892               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8893               second.matchWins++;
8894            }
8895            OutputKibitz(2, buf1);
8896            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8897         }
8898
8899         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8900         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8901             int count = 0;
8902
8903             while( count < adjudicateLossPlies ) {
8904                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8905
8906                 if( count & 1 ) {
8907                     score = -score; /* Flip score for winning side */
8908                 }
8909
8910                 if( score > appData.adjudicateLossThreshold ) {
8911                     break;
8912                 }
8913
8914                 count++;
8915             }
8916
8917             if( count >= adjudicateLossPlies ) {
8918                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8919
8920                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8921                     "Xboard adjudication",
8922                     GE_XBOARD );
8923
8924                 return;
8925             }
8926         }
8927
8928         if(Adjudicate(cps)) {
8929             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8930             return; // [HGM] adjudicate: for all automatic game ends
8931         }
8932
8933 #if ZIPPY
8934         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8935             first.initDone) {
8936           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8937                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8938                 SendToICS("draw ");
8939                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8940           }
8941           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8942           ics_user_moved = 1;
8943           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8944                 char buf[3*MSG_SIZ];
8945
8946                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8947                         programStats.score / 100.,
8948                         programStats.depth,
8949                         programStats.time / 100.,
8950                         (unsigned int)programStats.nodes,
8951                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8952                         programStats.movelist);
8953                 SendToICS(buf);
8954           }
8955         }
8956 #endif
8957
8958         /* [AS] Clear stats for next move */
8959         ClearProgramStats();
8960         thinkOutput[0] = NULLCHAR;
8961         hiddenThinkOutputState = 0;
8962
8963         bookHit = NULL;
8964         if (gameMode == TwoMachinesPlay) {
8965             /* [HGM] relaying draw offers moved to after reception of move */
8966             /* and interpreting offer as claim if it brings draw condition */
8967             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8968                 SendToProgram("draw\n", cps->other);
8969             }
8970             if (cps->other->sendTime) {
8971                 SendTimeRemaining(cps->other,
8972                                   cps->other->twoMachinesColor[0] == 'w');
8973             }
8974             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8975             if (firstMove && !bookHit) {
8976                 firstMove = FALSE;
8977                 if (cps->other->useColors) {
8978                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8979                 }
8980                 SendToProgram("go\n", cps->other);
8981             }
8982             cps->other->maybeThinking = TRUE;
8983         }
8984
8985         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8986
8987         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8988
8989         if (!pausing && appData.ringBellAfterMoves) {
8990             if(!roar) RingBell();
8991         }
8992
8993         /*
8994          * Reenable menu items that were disabled while
8995          * machine was thinking
8996          */
8997         if (gameMode != TwoMachinesPlay)
8998             SetUserThinkingEnables();
8999
9000         // [HGM] book: after book hit opponent has received move and is now in force mode
9001         // force the book reply into it, and then fake that it outputted this move by jumping
9002         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9003         if(bookHit) {
9004                 static char bookMove[MSG_SIZ]; // a bit generous?
9005
9006                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9007                 strcat(bookMove, bookHit);
9008                 message = bookMove;
9009                 cps = cps->other;
9010                 programStats.nodes = programStats.depth = programStats.time =
9011                 programStats.score = programStats.got_only_move = 0;
9012                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9013
9014                 if(cps->lastPing != cps->lastPong) {
9015                     savedMessage = message; // args for deferred call
9016                     savedState = cps;
9017                     ScheduleDelayedEvent(DeferredBookMove, 10);
9018                     return;
9019                 }
9020                 goto FakeBookMove;
9021         }
9022
9023         return;
9024     }
9025
9026     /* Set special modes for chess engines.  Later something general
9027      *  could be added here; for now there is just one kludge feature,
9028      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9029      *  when "xboard" is given as an interactive command.
9030      */
9031     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9032         cps->useSigint = FALSE;
9033         cps->useSigterm = FALSE;
9034     }
9035     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9036       ParseFeatures(message+8, cps);
9037       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9038     }
9039
9040     if (!strncmp(message, "setup ", 6) && 
9041         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9042           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9043                                         ) { // [HGM] allow first engine to define opening position
9044       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9045       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9046       *buf = NULLCHAR;
9047       if(sscanf(message, "setup (%s", buf) == 1) {
9048         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9049         ASSIGN(appData.pieceToCharTable, buf);
9050       }
9051       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9052       if(dummy >= 3) {
9053         while(message[s] && message[s++] != ' ');
9054         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9055            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9056             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9057             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9058           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9059           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9060           startedFromSetupPosition = FALSE;
9061         }
9062       }
9063       if(startedFromSetupPosition) return;
9064       ParseFEN(boards[0], &dummy, message+s, FALSE);
9065       DrawPosition(TRUE, boards[0]);
9066       CopyBoard(initialPosition, boards[0]);
9067       startedFromSetupPosition = TRUE;
9068       return;
9069     }
9070     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9071       ChessSquare piece = WhitePawn;
9072       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9073       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9074       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9075       piece += CharToPiece(ID & 255) - WhitePawn;
9076       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9077       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9078       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9079       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9080       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9081       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9082                                                && gameInfo.variant != VariantGreat
9083                                                && gameInfo.variant != VariantFairy    ) return;
9084       if(piece < EmptySquare) {
9085         pieceDefs = TRUE;
9086         ASSIGN(pieceDesc[piece], buf1);
9087         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9088       }
9089       return;
9090     }
9091     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9092       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9093       Sweep(0);
9094       return;
9095     }
9096     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9097      * want this, I was asked to put it in, and obliged.
9098      */
9099     if (!strncmp(message, "setboard ", 9)) {
9100         Board initial_position;
9101
9102         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9103
9104         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9105             DisplayError(_("Bad FEN received from engine"), 0);
9106             return ;
9107         } else {
9108            Reset(TRUE, FALSE);
9109            CopyBoard(boards[0], initial_position);
9110            initialRulePlies = FENrulePlies;
9111            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9112            else gameMode = MachinePlaysBlack;
9113            DrawPosition(FALSE, boards[currentMove]);
9114         }
9115         return;
9116     }
9117
9118     /*
9119      * Look for communication commands
9120      */
9121     if (!strncmp(message, "telluser ", 9)) {
9122         if(message[9] == '\\' && message[10] == '\\')
9123             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9124         PlayTellSound();
9125         DisplayNote(message + 9);
9126         return;
9127     }
9128     if (!strncmp(message, "tellusererror ", 14)) {
9129         cps->userError = 1;
9130         if(message[14] == '\\' && message[15] == '\\')
9131             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9132         PlayTellSound();
9133         DisplayError(message + 14, 0);
9134         return;
9135     }
9136     if (!strncmp(message, "tellopponent ", 13)) {
9137       if (appData.icsActive) {
9138         if (loggedOn) {
9139           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9140           SendToICS(buf1);
9141         }
9142       } else {
9143         DisplayNote(message + 13);
9144       }
9145       return;
9146     }
9147     if (!strncmp(message, "tellothers ", 11)) {
9148       if (appData.icsActive) {
9149         if (loggedOn) {
9150           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9151           SendToICS(buf1);
9152         }
9153       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9154       return;
9155     }
9156     if (!strncmp(message, "tellall ", 8)) {
9157       if (appData.icsActive) {
9158         if (loggedOn) {
9159           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9160           SendToICS(buf1);
9161         }
9162       } else {
9163         DisplayNote(message + 8);
9164       }
9165       return;
9166     }
9167     if (strncmp(message, "warning", 7) == 0) {
9168         /* Undocumented feature, use tellusererror in new code */
9169         DisplayError(message, 0);
9170         return;
9171     }
9172     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9173         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9174         strcat(realname, " query");
9175         AskQuestion(realname, buf2, buf1, cps->pr);
9176         return;
9177     }
9178     /* Commands from the engine directly to ICS.  We don't allow these to be
9179      *  sent until we are logged on. Crafty kibitzes have been known to
9180      *  interfere with the login process.
9181      */
9182     if (loggedOn) {
9183         if (!strncmp(message, "tellics ", 8)) {
9184             SendToICS(message + 8);
9185             SendToICS("\n");
9186             return;
9187         }
9188         if (!strncmp(message, "tellicsnoalias ", 15)) {
9189             SendToICS(ics_prefix);
9190             SendToICS(message + 15);
9191             SendToICS("\n");
9192             return;
9193         }
9194         /* The following are for backward compatibility only */
9195         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9196             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9197             SendToICS(ics_prefix);
9198             SendToICS(message);
9199             SendToICS("\n");
9200             return;
9201         }
9202     }
9203     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9204         if(initPing == cps->lastPong) {
9205             if(gameInfo.variant == VariantUnknown) {
9206                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9207                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9208                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9209             }
9210             initPing = -1;
9211         }
9212         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9213             abortEngineThink = FALSE;
9214             DisplayMessage("", "");
9215             ThawUI();
9216         }
9217         return;
9218     }
9219     if(!strncmp(message, "highlight ", 10)) {
9220         if(appData.testLegality && !*engineVariant && appData.markers) return;
9221         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9222         return;
9223     }
9224     if(!strncmp(message, "click ", 6)) {
9225         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9226         if(appData.testLegality || !appData.oneClick) return;
9227         sscanf(message+6, "%c%d%c", &f, &y, &c);
9228         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9229         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9230         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9231         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9232         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9233         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9234             LeftClick(Release, lastLeftX, lastLeftY);
9235         controlKey  = (c == ',');
9236         LeftClick(Press, x, y);
9237         LeftClick(Release, x, y);
9238         first.highlight = f;
9239         return;
9240     }
9241     /*
9242      * If the move is illegal, cancel it and redraw the board.
9243      * Also deal with other error cases.  Matching is rather loose
9244      * here to accommodate engines written before the spec.
9245      */
9246     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9247         strncmp(message, "Error", 5) == 0) {
9248         if (StrStr(message, "name") ||
9249             StrStr(message, "rating") || StrStr(message, "?") ||
9250             StrStr(message, "result") || StrStr(message, "board") ||
9251             StrStr(message, "bk") || StrStr(message, "computer") ||
9252             StrStr(message, "variant") || StrStr(message, "hint") ||
9253             StrStr(message, "random") || StrStr(message, "depth") ||
9254             StrStr(message, "accepted")) {
9255             return;
9256         }
9257         if (StrStr(message, "protover")) {
9258           /* Program is responding to input, so it's apparently done
9259              initializing, and this error message indicates it is
9260              protocol version 1.  So we don't need to wait any longer
9261              for it to initialize and send feature commands. */
9262           FeatureDone(cps, 1);
9263           cps->protocolVersion = 1;
9264           return;
9265         }
9266         cps->maybeThinking = FALSE;
9267
9268         if (StrStr(message, "draw")) {
9269             /* Program doesn't have "draw" command */
9270             cps->sendDrawOffers = 0;
9271             return;
9272         }
9273         if (cps->sendTime != 1 &&
9274             (StrStr(message, "time") || StrStr(message, "otim"))) {
9275           /* Program apparently doesn't have "time" or "otim" command */
9276           cps->sendTime = 0;
9277           return;
9278         }
9279         if (StrStr(message, "analyze")) {
9280             cps->analysisSupport = FALSE;
9281             cps->analyzing = FALSE;
9282 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9283             EditGameEvent(); // [HGM] try to preserve loaded game
9284             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9285             DisplayError(buf2, 0);
9286             return;
9287         }
9288         if (StrStr(message, "(no matching move)st")) {
9289           /* Special kludge for GNU Chess 4 only */
9290           cps->stKludge = TRUE;
9291           SendTimeControl(cps, movesPerSession, timeControl,
9292                           timeIncrement, appData.searchDepth,
9293                           searchTime);
9294           return;
9295         }
9296         if (StrStr(message, "(no matching move)sd")) {
9297           /* Special kludge for GNU Chess 4 only */
9298           cps->sdKludge = TRUE;
9299           SendTimeControl(cps, movesPerSession, timeControl,
9300                           timeIncrement, appData.searchDepth,
9301                           searchTime);
9302           return;
9303         }
9304         if (!StrStr(message, "llegal")) {
9305             return;
9306         }
9307         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9308             gameMode == IcsIdle) return;
9309         if (forwardMostMove <= backwardMostMove) return;
9310         if (pausing) PauseEvent();
9311       if(appData.forceIllegal) {
9312             // [HGM] illegal: machine refused move; force position after move into it
9313           SendToProgram("force\n", cps);
9314           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9315                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9316                 // when black is to move, while there might be nothing on a2 or black
9317                 // might already have the move. So send the board as if white has the move.
9318                 // But first we must change the stm of the engine, as it refused the last move
9319                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9320                 if(WhiteOnMove(forwardMostMove)) {
9321                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9322                     SendBoard(cps, forwardMostMove); // kludgeless board
9323                 } else {
9324                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9325                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9326                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9327                 }
9328           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9329             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9330                  gameMode == TwoMachinesPlay)
9331               SendToProgram("go\n", cps);
9332             return;
9333       } else
9334         if (gameMode == PlayFromGameFile) {
9335             /* Stop reading this game file */
9336             gameMode = EditGame;
9337             ModeHighlight();
9338         }
9339         /* [HGM] illegal-move claim should forfeit game when Xboard */
9340         /* only passes fully legal moves                            */
9341         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9342             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9343                                 "False illegal-move claim", GE_XBOARD );
9344             return; // do not take back move we tested as valid
9345         }
9346         currentMove = forwardMostMove-1;
9347         DisplayMove(currentMove-1); /* before DisplayMoveError */
9348         SwitchClocks(forwardMostMove-1); // [HGM] race
9349         DisplayBothClocks();
9350         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9351                 parseList[currentMove], _(cps->which));
9352         DisplayMoveError(buf1);
9353         DrawPosition(FALSE, boards[currentMove]);
9354
9355         SetUserThinkingEnables();
9356         return;
9357     }
9358     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9359         /* Program has a broken "time" command that
9360            outputs a string not ending in newline.
9361            Don't use it. */
9362         cps->sendTime = 0;
9363     }
9364     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9365         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9366             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9367     }
9368
9369     /*
9370      * If chess program startup fails, exit with an error message.
9371      * Attempts to recover here are futile. [HGM] Well, we try anyway
9372      */
9373     if ((StrStr(message, "unknown host") != NULL)
9374         || (StrStr(message, "No remote directory") != NULL)
9375         || (StrStr(message, "not found") != NULL)
9376         || (StrStr(message, "No such file") != NULL)
9377         || (StrStr(message, "can't alloc") != NULL)
9378         || (StrStr(message, "Permission denied") != NULL)) {
9379
9380         cps->maybeThinking = FALSE;
9381         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9382                 _(cps->which), cps->program, cps->host, message);
9383         RemoveInputSource(cps->isr);
9384         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9385             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9386             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9387         }
9388         return;
9389     }
9390
9391     /*
9392      * Look for hint output
9393      */
9394     if (sscanf(message, "Hint: %s", buf1) == 1) {
9395         if (cps == &first && hintRequested) {
9396             hintRequested = FALSE;
9397             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9398                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9399                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9400                                     PosFlags(forwardMostMove),
9401                                     fromY, fromX, toY, toX, promoChar, buf1);
9402                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9403                 DisplayInformation(buf2);
9404             } else {
9405                 /* Hint move could not be parsed!? */
9406               snprintf(buf2, sizeof(buf2),
9407                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9408                         buf1, _(cps->which));
9409                 DisplayError(buf2, 0);
9410             }
9411         } else {
9412           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9413         }
9414         return;
9415     }
9416
9417     /*
9418      * Ignore other messages if game is not in progress
9419      */
9420     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9421         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9422
9423     /*
9424      * look for win, lose, draw, or draw offer
9425      */
9426     if (strncmp(message, "1-0", 3) == 0) {
9427         char *p, *q, *r = "";
9428         p = strchr(message, '{');
9429         if (p) {
9430             q = strchr(p, '}');
9431             if (q) {
9432                 *q = NULLCHAR;
9433                 r = p + 1;
9434             }
9435         }
9436         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9437         return;
9438     } else if (strncmp(message, "0-1", 3) == 0) {
9439         char *p, *q, *r = "";
9440         p = strchr(message, '{');
9441         if (p) {
9442             q = strchr(p, '}');
9443             if (q) {
9444                 *q = NULLCHAR;
9445                 r = p + 1;
9446             }
9447         }
9448         /* Kludge for Arasan 4.1 bug */
9449         if (strcmp(r, "Black resigns") == 0) {
9450             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9451             return;
9452         }
9453         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9454         return;
9455     } else if (strncmp(message, "1/2", 3) == 0) {
9456         char *p, *q, *r = "";
9457         p = strchr(message, '{');
9458         if (p) {
9459             q = strchr(p, '}');
9460             if (q) {
9461                 *q = NULLCHAR;
9462                 r = p + 1;
9463             }
9464         }
9465
9466         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9467         return;
9468
9469     } else if (strncmp(message, "White resign", 12) == 0) {
9470         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9471         return;
9472     } else if (strncmp(message, "Black resign", 12) == 0) {
9473         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9474         return;
9475     } else if (strncmp(message, "White matches", 13) == 0 ||
9476                strncmp(message, "Black matches", 13) == 0   ) {
9477         /* [HGM] ignore GNUShogi noises */
9478         return;
9479     } else if (strncmp(message, "White", 5) == 0 &&
9480                message[5] != '(' &&
9481                StrStr(message, "Black") == NULL) {
9482         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9483         return;
9484     } else if (strncmp(message, "Black", 5) == 0 &&
9485                message[5] != '(') {
9486         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9487         return;
9488     } else if (strcmp(message, "resign") == 0 ||
9489                strcmp(message, "computer resigns") == 0) {
9490         switch (gameMode) {
9491           case MachinePlaysBlack:
9492           case IcsPlayingBlack:
9493             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9494             break;
9495           case MachinePlaysWhite:
9496           case IcsPlayingWhite:
9497             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9498             break;
9499           case TwoMachinesPlay:
9500             if (cps->twoMachinesColor[0] == 'w')
9501               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9502             else
9503               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9504             break;
9505           default:
9506             /* can't happen */
9507             break;
9508         }
9509         return;
9510     } else if (strncmp(message, "opponent mates", 14) == 0) {
9511         switch (gameMode) {
9512           case MachinePlaysBlack:
9513           case IcsPlayingBlack:
9514             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9515             break;
9516           case MachinePlaysWhite:
9517           case IcsPlayingWhite:
9518             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9519             break;
9520           case TwoMachinesPlay:
9521             if (cps->twoMachinesColor[0] == 'w')
9522               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9523             else
9524               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9525             break;
9526           default:
9527             /* can't happen */
9528             break;
9529         }
9530         return;
9531     } else if (strncmp(message, "computer mates", 14) == 0) {
9532         switch (gameMode) {
9533           case MachinePlaysBlack:
9534           case IcsPlayingBlack:
9535             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9536             break;
9537           case MachinePlaysWhite:
9538           case IcsPlayingWhite:
9539             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9540             break;
9541           case TwoMachinesPlay:
9542             if (cps->twoMachinesColor[0] == 'w')
9543               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9544             else
9545               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9546             break;
9547           default:
9548             /* can't happen */
9549             break;
9550         }
9551         return;
9552     } else if (strncmp(message, "checkmate", 9) == 0) {
9553         if (WhiteOnMove(forwardMostMove)) {
9554             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9555         } else {
9556             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9557         }
9558         return;
9559     } else if (strstr(message, "Draw") != NULL ||
9560                strstr(message, "game is a draw") != NULL) {
9561         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9562         return;
9563     } else if (strstr(message, "offer") != NULL &&
9564                strstr(message, "draw") != NULL) {
9565 #if ZIPPY
9566         if (appData.zippyPlay && first.initDone) {
9567             /* Relay offer to ICS */
9568             SendToICS(ics_prefix);
9569             SendToICS("draw\n");
9570         }
9571 #endif
9572         cps->offeredDraw = 2; /* valid until this engine moves twice */
9573         if (gameMode == TwoMachinesPlay) {
9574             if (cps->other->offeredDraw) {
9575                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9576             /* [HGM] in two-machine mode we delay relaying draw offer      */
9577             /* until after we also have move, to see if it is really claim */
9578             }
9579         } else if (gameMode == MachinePlaysWhite ||
9580                    gameMode == MachinePlaysBlack) {
9581           if (userOfferedDraw) {
9582             DisplayInformation(_("Machine accepts your draw offer"));
9583             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9584           } else {
9585             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9586           }
9587         }
9588     }
9589
9590
9591     /*
9592      * Look for thinking output
9593      */
9594     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9595           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9596                                 ) {
9597         int plylev, mvleft, mvtot, curscore, time;
9598         char mvname[MOVE_LEN];
9599         u64 nodes; // [DM]
9600         char plyext;
9601         int ignore = FALSE;
9602         int prefixHint = FALSE;
9603         mvname[0] = NULLCHAR;
9604
9605         switch (gameMode) {
9606           case MachinePlaysBlack:
9607           case IcsPlayingBlack:
9608             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9609             break;
9610           case MachinePlaysWhite:
9611           case IcsPlayingWhite:
9612             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9613             break;
9614           case AnalyzeMode:
9615           case AnalyzeFile:
9616             break;
9617           case IcsObserving: /* [DM] icsEngineAnalyze */
9618             if (!appData.icsEngineAnalyze) ignore = TRUE;
9619             break;
9620           case TwoMachinesPlay:
9621             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9622                 ignore = TRUE;
9623             }
9624             break;
9625           default:
9626             ignore = TRUE;
9627             break;
9628         }
9629
9630         if (!ignore) {
9631             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9632             buf1[0] = NULLCHAR;
9633             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9634                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9635                 char score_buf[MSG_SIZ];
9636
9637                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9638                     nodes += u64Const(0x100000000);
9639
9640                 if (plyext != ' ' && plyext != '\t') {
9641                     time *= 100;
9642                 }
9643
9644                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9645                 if( cps->scoreIsAbsolute &&
9646                     ( gameMode == MachinePlaysBlack ||
9647                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9648                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9649                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9650                      !WhiteOnMove(currentMove)
9651                     ) )
9652                 {
9653                     curscore = -curscore;
9654                 }
9655
9656                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9657
9658                 if(*bestMove) { // rememer time best EPD move was first found
9659                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9660                     ChessMove mt;
9661                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9662                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9663                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9664                 }
9665
9666                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9667                         char buf[MSG_SIZ];
9668                         FILE *f;
9669                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9670                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9671                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9672                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9673                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9674                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9675                                 fclose(f);
9676                         }
9677                         else
9678                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9679                           DisplayError(_("failed writing PV"), 0);
9680                 }
9681
9682                 tempStats.depth = plylev;
9683                 tempStats.nodes = nodes;
9684                 tempStats.time = time;
9685                 tempStats.score = curscore;
9686                 tempStats.got_only_move = 0;
9687
9688                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9689                         int ticklen;
9690
9691                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9692                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9693                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9694                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9695                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9696                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9697                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9698                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9699                 }
9700
9701                 /* Buffer overflow protection */
9702                 if (pv[0] != NULLCHAR) {
9703                     if (strlen(pv) >= sizeof(tempStats.movelist)
9704                         && appData.debugMode) {
9705                         fprintf(debugFP,
9706                                 "PV is too long; using the first %u bytes.\n",
9707                                 (unsigned) sizeof(tempStats.movelist) - 1);
9708                     }
9709
9710                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9711                 } else {
9712                     sprintf(tempStats.movelist, " no PV\n");
9713                 }
9714
9715                 if (tempStats.seen_stat) {
9716                     tempStats.ok_to_send = 1;
9717                 }
9718
9719                 if (strchr(tempStats.movelist, '(') != NULL) {
9720                     tempStats.line_is_book = 1;
9721                     tempStats.nr_moves = 0;
9722                     tempStats.moves_left = 0;
9723                 } else {
9724                     tempStats.line_is_book = 0;
9725                 }
9726
9727                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9728                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9729
9730                 SendProgramStatsToFrontend( cps, &tempStats );
9731
9732                 /*
9733                     [AS] Protect the thinkOutput buffer from overflow... this
9734                     is only useful if buf1 hasn't overflowed first!
9735                 */
9736                 if(curscore >= MATE_SCORE) 
9737                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9738                 else if(curscore <= -MATE_SCORE) 
9739                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9740                 else
9741                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9742                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9743                          plylev,
9744                          (gameMode == TwoMachinesPlay ?
9745                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9746                          score_buf,
9747                          prefixHint ? lastHint : "",
9748                          prefixHint ? " " : "" );
9749
9750                 if( buf1[0] != NULLCHAR ) {
9751                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9752
9753                     if( strlen(pv) > max_len ) {
9754                         if( appData.debugMode) {
9755                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9756                         }
9757                         pv[max_len+1] = '\0';
9758                     }
9759
9760                     strcat( thinkOutput, pv);
9761                 }
9762
9763                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9764                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9765                     DisplayMove(currentMove - 1);
9766                 }
9767                 return;
9768
9769             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9770                 /* crafty (9.25+) says "(only move) <move>"
9771                  * if there is only 1 legal move
9772                  */
9773                 sscanf(p, "(only move) %s", buf1);
9774                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9775                 sprintf(programStats.movelist, "%s (only move)", buf1);
9776                 programStats.depth = 1;
9777                 programStats.nr_moves = 1;
9778                 programStats.moves_left = 1;
9779                 programStats.nodes = 1;
9780                 programStats.time = 1;
9781                 programStats.got_only_move = 1;
9782
9783                 /* Not really, but we also use this member to
9784                    mean "line isn't going to change" (Crafty
9785                    isn't searching, so stats won't change) */
9786                 programStats.line_is_book = 1;
9787
9788                 SendProgramStatsToFrontend( cps, &programStats );
9789
9790                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9791                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9792                     DisplayMove(currentMove - 1);
9793                 }
9794                 return;
9795             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9796                               &time, &nodes, &plylev, &mvleft,
9797                               &mvtot, mvname) >= 5) {
9798                 /* The stat01: line is from Crafty (9.29+) in response
9799                    to the "." command */
9800                 programStats.seen_stat = 1;
9801                 cps->maybeThinking = TRUE;
9802
9803                 if (programStats.got_only_move || !appData.periodicUpdates)
9804                   return;
9805
9806                 programStats.depth = plylev;
9807                 programStats.time = time;
9808                 programStats.nodes = nodes;
9809                 programStats.moves_left = mvleft;
9810                 programStats.nr_moves = mvtot;
9811                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9812                 programStats.ok_to_send = 1;
9813                 programStats.movelist[0] = '\0';
9814
9815                 SendProgramStatsToFrontend( cps, &programStats );
9816
9817                 return;
9818
9819             } else if (strncmp(message,"++",2) == 0) {
9820                 /* Crafty 9.29+ outputs this */
9821                 programStats.got_fail = 2;
9822                 return;
9823
9824             } else if (strncmp(message,"--",2) == 0) {
9825                 /* Crafty 9.29+ outputs this */
9826                 programStats.got_fail = 1;
9827                 return;
9828
9829             } else if (thinkOutput[0] != NULLCHAR &&
9830                        strncmp(message, "    ", 4) == 0) {
9831                 unsigned message_len;
9832
9833                 p = message;
9834                 while (*p && *p == ' ') p++;
9835
9836                 message_len = strlen( p );
9837
9838                 /* [AS] Avoid buffer overflow */
9839                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9840                     strcat(thinkOutput, " ");
9841                     strcat(thinkOutput, p);
9842                 }
9843
9844                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9845                     strcat(programStats.movelist, " ");
9846                     strcat(programStats.movelist, p);
9847                 }
9848
9849                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9850                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9851                     DisplayMove(currentMove - 1);
9852                 }
9853                 return;
9854             }
9855         }
9856         else {
9857             buf1[0] = NULLCHAR;
9858
9859             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9860                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9861             {
9862                 ChessProgramStats cpstats;
9863
9864                 if (plyext != ' ' && plyext != '\t') {
9865                     time *= 100;
9866                 }
9867
9868                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9869                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9870                     curscore = -curscore;
9871                 }
9872
9873                 cpstats.depth = plylev;
9874                 cpstats.nodes = nodes;
9875                 cpstats.time = time;
9876                 cpstats.score = curscore;
9877                 cpstats.got_only_move = 0;
9878                 cpstats.movelist[0] = '\0';
9879
9880                 if (buf1[0] != NULLCHAR) {
9881                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9882                 }
9883
9884                 cpstats.ok_to_send = 0;
9885                 cpstats.line_is_book = 0;
9886                 cpstats.nr_moves = 0;
9887                 cpstats.moves_left = 0;
9888
9889                 SendProgramStatsToFrontend( cps, &cpstats );
9890             }
9891         }
9892     }
9893 }
9894
9895
9896 /* Parse a game score from the character string "game", and
9897    record it as the history of the current game.  The game
9898    score is NOT assumed to start from the standard position.
9899    The display is not updated in any way.
9900    */
9901 void
9902 ParseGameHistory (char *game)
9903 {
9904     ChessMove moveType;
9905     int fromX, fromY, toX, toY, boardIndex;
9906     char promoChar;
9907     char *p, *q;
9908     char buf[MSG_SIZ];
9909
9910     if (appData.debugMode)
9911       fprintf(debugFP, "Parsing game history: %s\n", game);
9912
9913     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9914     gameInfo.site = StrSave(appData.icsHost);
9915     gameInfo.date = PGNDate();
9916     gameInfo.round = StrSave("-");
9917
9918     /* Parse out names of players */
9919     while (*game == ' ') game++;
9920     p = buf;
9921     while (*game != ' ') *p++ = *game++;
9922     *p = NULLCHAR;
9923     gameInfo.white = StrSave(buf);
9924     while (*game == ' ') game++;
9925     p = buf;
9926     while (*game != ' ' && *game != '\n') *p++ = *game++;
9927     *p = NULLCHAR;
9928     gameInfo.black = StrSave(buf);
9929
9930     /* Parse moves */
9931     boardIndex = blackPlaysFirst ? 1 : 0;
9932     yynewstr(game);
9933     for (;;) {
9934         yyboardindex = boardIndex;
9935         moveType = (ChessMove) Myylex();
9936         switch (moveType) {
9937           case IllegalMove:             /* maybe suicide chess, etc. */
9938   if (appData.debugMode) {
9939     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9940     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9941     setbuf(debugFP, NULL);
9942   }
9943           case WhitePromotion:
9944           case BlackPromotion:
9945           case WhiteNonPromotion:
9946           case BlackNonPromotion:
9947           case NormalMove:
9948           case FirstLeg:
9949           case WhiteCapturesEnPassant:
9950           case BlackCapturesEnPassant:
9951           case WhiteKingSideCastle:
9952           case WhiteQueenSideCastle:
9953           case BlackKingSideCastle:
9954           case BlackQueenSideCastle:
9955           case WhiteKingSideCastleWild:
9956           case WhiteQueenSideCastleWild:
9957           case BlackKingSideCastleWild:
9958           case BlackQueenSideCastleWild:
9959           /* PUSH Fabien */
9960           case WhiteHSideCastleFR:
9961           case WhiteASideCastleFR:
9962           case BlackHSideCastleFR:
9963           case BlackASideCastleFR:
9964           /* POP Fabien */
9965             fromX = currentMoveString[0] - AAA;
9966             fromY = currentMoveString[1] - ONE;
9967             toX = currentMoveString[2] - AAA;
9968             toY = currentMoveString[3] - ONE;
9969             promoChar = currentMoveString[4];
9970             break;
9971           case WhiteDrop:
9972           case BlackDrop:
9973             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9974             fromX = moveType == WhiteDrop ?
9975               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9976             (int) CharToPiece(ToLower(currentMoveString[0]));
9977             fromY = DROP_RANK;
9978             toX = currentMoveString[2] - AAA;
9979             toY = currentMoveString[3] - ONE;
9980             promoChar = NULLCHAR;
9981             break;
9982           case AmbiguousMove:
9983             /* bug? */
9984             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9985   if (appData.debugMode) {
9986     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9987     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9988     setbuf(debugFP, NULL);
9989   }
9990             DisplayError(buf, 0);
9991             return;
9992           case ImpossibleMove:
9993             /* bug? */
9994             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9995   if (appData.debugMode) {
9996     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9997     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9998     setbuf(debugFP, NULL);
9999   }
10000             DisplayError(buf, 0);
10001             return;
10002           case EndOfFile:
10003             if (boardIndex < backwardMostMove) {
10004                 /* Oops, gap.  How did that happen? */
10005                 DisplayError(_("Gap in move list"), 0);
10006                 return;
10007             }
10008             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10009             if (boardIndex > forwardMostMove) {
10010                 forwardMostMove = boardIndex;
10011             }
10012             return;
10013           case ElapsedTime:
10014             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10015                 strcat(parseList[boardIndex-1], " ");
10016                 strcat(parseList[boardIndex-1], yy_text);
10017             }
10018             continue;
10019           case Comment:
10020           case PGNTag:
10021           case NAG:
10022           default:
10023             /* ignore */
10024             continue;
10025           case WhiteWins:
10026           case BlackWins:
10027           case GameIsDrawn:
10028           case GameUnfinished:
10029             if (gameMode == IcsExamining) {
10030                 if (boardIndex < backwardMostMove) {
10031                     /* Oops, gap.  How did that happen? */
10032                     return;
10033                 }
10034                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10035                 return;
10036             }
10037             gameInfo.result = moveType;
10038             p = strchr(yy_text, '{');
10039             if (p == NULL) p = strchr(yy_text, '(');
10040             if (p == NULL) {
10041                 p = yy_text;
10042                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10043             } else {
10044                 q = strchr(p, *p == '{' ? '}' : ')');
10045                 if (q != NULL) *q = NULLCHAR;
10046                 p++;
10047             }
10048             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10049             gameInfo.resultDetails = StrSave(p);
10050             continue;
10051         }
10052         if (boardIndex >= forwardMostMove &&
10053             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10054             backwardMostMove = blackPlaysFirst ? 1 : 0;
10055             return;
10056         }
10057         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10058                                  fromY, fromX, toY, toX, promoChar,
10059                                  parseList[boardIndex]);
10060         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10061         /* currentMoveString is set as a side-effect of yylex */
10062         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10063         strcat(moveList[boardIndex], "\n");
10064         boardIndex++;
10065         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10066         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10067           case MT_NONE:
10068           case MT_STALEMATE:
10069           default:
10070             break;
10071           case MT_CHECK:
10072             if(!IS_SHOGI(gameInfo.variant))
10073                 strcat(parseList[boardIndex - 1], "+");
10074             break;
10075           case MT_CHECKMATE:
10076           case MT_STAINMATE:
10077             strcat(parseList[boardIndex - 1], "#");
10078             break;
10079         }
10080     }
10081 }
10082
10083
10084 /* Apply a move to the given board  */
10085 void
10086 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10087 {
10088   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10089   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10090
10091     /* [HGM] compute & store e.p. status and castling rights for new position */
10092     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10093
10094       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10095       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10096       board[EP_STATUS] = EP_NONE;
10097       board[EP_FILE] = board[EP_RANK] = 100;
10098
10099   if (fromY == DROP_RANK) {
10100         /* must be first */
10101         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10102             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10103             return;
10104         }
10105         piece = board[toY][toX] = (ChessSquare) fromX;
10106   } else {
10107 //      ChessSquare victim;
10108       int i;
10109
10110       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10111 //           victim = board[killY][killX],
10112            killed = board[killY][killX],
10113            board[killY][killX] = EmptySquare,
10114            board[EP_STATUS] = EP_CAPTURE;
10115            if( kill2X >= 0 && kill2Y >= 0)
10116              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10117       }
10118
10119       if( board[toY][toX] != EmptySquare ) {
10120            board[EP_STATUS] = EP_CAPTURE;
10121            if( (fromX != toX || fromY != toY) && // not igui!
10122                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10123                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10124                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10125            }
10126       }
10127
10128       pawn = board[fromY][fromX];
10129       if( pawn == WhiteLance || pawn == BlackLance ) {
10130            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10131                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10132                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10133            }
10134       }
10135       if( pawn == WhitePawn ) {
10136            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10137                board[EP_STATUS] = EP_PAWN_MOVE;
10138            if( toY-fromY>=2) {
10139                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10140                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10141                         gameInfo.variant != VariantBerolina || toX < fromX)
10142                       board[EP_STATUS] = toX | berolina;
10143                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10144                         gameInfo.variant != VariantBerolina || toX > fromX)
10145                       board[EP_STATUS] = toX;
10146            }
10147       } else
10148       if( pawn == BlackPawn ) {
10149            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10150                board[EP_STATUS] = EP_PAWN_MOVE;
10151            if( toY-fromY<= -2) {
10152                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10153                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10154                         gameInfo.variant != VariantBerolina || toX < fromX)
10155                       board[EP_STATUS] = toX | berolina;
10156                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10157                         gameInfo.variant != VariantBerolina || toX > fromX)
10158                       board[EP_STATUS] = toX;
10159            }
10160        }
10161
10162        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10163        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10164        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10165        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10166
10167        for(i=0; i<nrCastlingRights; i++) {
10168            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10169               board[CASTLING][i] == toX   && castlingRank[i] == toY
10170              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10171        }
10172
10173        if(gameInfo.variant == VariantSChess) { // update virginity
10174            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10175            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10176            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10177            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10178        }
10179
10180      if (fromX == toX && fromY == toY) return;
10181
10182      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10183      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10184      if(gameInfo.variant == VariantKnightmate)
10185          king += (int) WhiteUnicorn - (int) WhiteKing;
10186
10187     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10188        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10189         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10190         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10191         board[EP_STATUS] = EP_NONE; // capture was fake!
10192     } else
10193     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10194         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10195         board[toY][toX] = piece;
10196         board[EP_STATUS] = EP_NONE; // capture was fake!
10197     } else
10198     /* Code added by Tord: */
10199     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10200     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10201         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10202       board[EP_STATUS] = EP_NONE; // capture was fake!
10203       board[fromY][fromX] = EmptySquare;
10204       board[toY][toX] = EmptySquare;
10205       if((toX > fromX) != (piece == WhiteRook)) {
10206         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10207       } else {
10208         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10209       }
10210     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10211                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10212       board[EP_STATUS] = EP_NONE;
10213       board[fromY][fromX] = EmptySquare;
10214       board[toY][toX] = EmptySquare;
10215       if((toX > fromX) != (piece == BlackRook)) {
10216         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10217       } else {
10218         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10219       }
10220     /* End of code added by Tord */
10221
10222     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10223         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10224         board[toY][toX] = piece;
10225     } else if (board[fromY][fromX] == king
10226         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10227         && toY == fromY && toX > fromX+1) {
10228         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10229         board[fromY][toX-1] = board[fromY][rookX];
10230         board[fromY][rookX] = EmptySquare;
10231         board[fromY][fromX] = EmptySquare;
10232         board[toY][toX] = king;
10233     } else if (board[fromY][fromX] == king
10234         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10235                && toY == fromY && toX < fromX-1) {
10236         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10237         board[fromY][toX+1] = board[fromY][rookX];
10238         board[fromY][rookX] = EmptySquare;
10239         board[fromY][fromX] = EmptySquare;
10240         board[toY][toX] = king;
10241     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10242                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10243                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10244                ) {
10245         /* white pawn promotion */
10246         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10247         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10248             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10249         board[fromY][fromX] = EmptySquare;
10250     } else if ((fromY >= BOARD_HEIGHT>>1)
10251                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10252                && (toX != fromX)
10253                && gameInfo.variant != VariantXiangqi
10254                && gameInfo.variant != VariantBerolina
10255                && (pawn == WhitePawn)
10256                && (board[toY][toX] == EmptySquare)) {
10257         board[fromY][fromX] = EmptySquare;
10258         board[toY][toX] = piece;
10259         if(toY == epRank - 128 + 1)
10260             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10261         else
10262             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10263     } else if ((fromY == BOARD_HEIGHT-4)
10264                && (toX == fromX)
10265                && gameInfo.variant == VariantBerolina
10266                && (board[fromY][fromX] == WhitePawn)
10267                && (board[toY][toX] == EmptySquare)) {
10268         board[fromY][fromX] = EmptySquare;
10269         board[toY][toX] = WhitePawn;
10270         if(oldEP & EP_BEROLIN_A) {
10271                 captured = board[fromY][fromX-1];
10272                 board[fromY][fromX-1] = EmptySquare;
10273         }else{  captured = board[fromY][fromX+1];
10274                 board[fromY][fromX+1] = EmptySquare;
10275         }
10276     } else if (board[fromY][fromX] == king
10277         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10278                && toY == fromY && toX > fromX+1) {
10279         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10280         board[fromY][toX-1] = board[fromY][rookX];
10281         board[fromY][rookX] = EmptySquare;
10282         board[fromY][fromX] = EmptySquare;
10283         board[toY][toX] = king;
10284     } else if (board[fromY][fromX] == king
10285         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10286                && toY == fromY && toX < fromX-1) {
10287         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10288         board[fromY][toX+1] = board[fromY][rookX];
10289         board[fromY][rookX] = EmptySquare;
10290         board[fromY][fromX] = EmptySquare;
10291         board[toY][toX] = king;
10292     } else if (fromY == 7 && fromX == 3
10293                && board[fromY][fromX] == BlackKing
10294                && toY == 7 && toX == 5) {
10295         board[fromY][fromX] = EmptySquare;
10296         board[toY][toX] = BlackKing;
10297         board[fromY][7] = EmptySquare;
10298         board[toY][4] = BlackRook;
10299     } else if (fromY == 7 && fromX == 3
10300                && board[fromY][fromX] == BlackKing
10301                && toY == 7 && toX == 1) {
10302         board[fromY][fromX] = EmptySquare;
10303         board[toY][toX] = BlackKing;
10304         board[fromY][0] = EmptySquare;
10305         board[toY][2] = BlackRook;
10306     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10307                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10308                && toY < promoRank && promoChar
10309                ) {
10310         /* black pawn promotion */
10311         board[toY][toX] = CharToPiece(ToLower(promoChar));
10312         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10313             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10314         board[fromY][fromX] = EmptySquare;
10315     } else if ((fromY < BOARD_HEIGHT>>1)
10316                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10317                && (toX != fromX)
10318                && gameInfo.variant != VariantXiangqi
10319                && gameInfo.variant != VariantBerolina
10320                && (pawn == BlackPawn)
10321                && (board[toY][toX] == EmptySquare)) {
10322         board[fromY][fromX] = EmptySquare;
10323         board[toY][toX] = piece;
10324         if(toY == epRank - 128 - 1)
10325             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10326         else
10327             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10328     } else if ((fromY == 3)
10329                && (toX == fromX)
10330                && gameInfo.variant == VariantBerolina
10331                && (board[fromY][fromX] == BlackPawn)
10332                && (board[toY][toX] == EmptySquare)) {
10333         board[fromY][fromX] = EmptySquare;
10334         board[toY][toX] = BlackPawn;
10335         if(oldEP & EP_BEROLIN_A) {
10336                 captured = board[fromY][fromX-1];
10337                 board[fromY][fromX-1] = EmptySquare;
10338         }else{  captured = board[fromY][fromX+1];
10339                 board[fromY][fromX+1] = EmptySquare;
10340         }
10341     } else {
10342         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10343         board[fromY][fromX] = EmptySquare;
10344         board[toY][toX] = piece;
10345     }
10346   }
10347
10348     if (gameInfo.holdingsWidth != 0) {
10349
10350       /* !!A lot more code needs to be written to support holdings  */
10351       /* [HGM] OK, so I have written it. Holdings are stored in the */
10352       /* penultimate board files, so they are automaticlly stored   */
10353       /* in the game history.                                       */
10354       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10355                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10356         /* Delete from holdings, by decreasing count */
10357         /* and erasing image if necessary            */
10358         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10359         if(p < (int) BlackPawn) { /* white drop */
10360              p -= (int)WhitePawn;
10361                  p = PieceToNumber((ChessSquare)p);
10362              if(p >= gameInfo.holdingsSize) p = 0;
10363              if(--board[p][BOARD_WIDTH-2] <= 0)
10364                   board[p][BOARD_WIDTH-1] = EmptySquare;
10365              if((int)board[p][BOARD_WIDTH-2] < 0)
10366                         board[p][BOARD_WIDTH-2] = 0;
10367         } else {                  /* black drop */
10368              p -= (int)BlackPawn;
10369                  p = PieceToNumber((ChessSquare)p);
10370              if(p >= gameInfo.holdingsSize) p = 0;
10371              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10372                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10373              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10374                         board[BOARD_HEIGHT-1-p][1] = 0;
10375         }
10376       }
10377       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10378           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10379         /* [HGM] holdings: Add to holdings, if holdings exist */
10380         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10381                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10382                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10383         }
10384         p = (int) captured;
10385         if (p >= (int) BlackPawn) {
10386           p -= (int)BlackPawn;
10387           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10388                   /* Restore shogi-promoted piece to its original  first */
10389                   captured = (ChessSquare) (DEMOTED(captured));
10390                   p = DEMOTED(p);
10391           }
10392           p = PieceToNumber((ChessSquare)p);
10393           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10394           board[p][BOARD_WIDTH-2]++;
10395           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10396         } else {
10397           p -= (int)WhitePawn;
10398           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10399                   captured = (ChessSquare) (DEMOTED(captured));
10400                   p = DEMOTED(p);
10401           }
10402           p = PieceToNumber((ChessSquare)p);
10403           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10404           board[BOARD_HEIGHT-1-p][1]++;
10405           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10406         }
10407       }
10408     } else if (gameInfo.variant == VariantAtomic) {
10409       if (captured != EmptySquare) {
10410         int y, x;
10411         for (y = toY-1; y <= toY+1; y++) {
10412           for (x = toX-1; x <= toX+1; x++) {
10413             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10414                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10415               board[y][x] = EmptySquare;
10416             }
10417           }
10418         }
10419         board[toY][toX] = EmptySquare;
10420       }
10421     }
10422
10423     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10424         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10425     } else
10426     if(promoChar == '+') {
10427         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10428         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10429         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10430           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10431     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10432         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10433         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10434            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10435         board[toY][toX] = newPiece;
10436     }
10437     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10438                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10439         // [HGM] superchess: take promotion piece out of holdings
10440         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10441         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10442             if(!--board[k][BOARD_WIDTH-2])
10443                 board[k][BOARD_WIDTH-1] = EmptySquare;
10444         } else {
10445             if(!--board[BOARD_HEIGHT-1-k][1])
10446                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10447         }
10448     }
10449 }
10450
10451 /* Updates forwardMostMove */
10452 void
10453 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10454 {
10455     int x = toX, y = toY;
10456     char *s = parseList[forwardMostMove];
10457     ChessSquare p = boards[forwardMostMove][toY][toX];
10458 //    forwardMostMove++; // [HGM] bare: moved downstream
10459
10460     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10461     (void) CoordsToAlgebraic(boards[forwardMostMove],
10462                              PosFlags(forwardMostMove),
10463                              fromY, fromX, y, x, promoChar,
10464                              s);
10465     if(killX >= 0 && killY >= 0)
10466         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10467
10468     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10469         int timeLeft; static int lastLoadFlag=0; int king, piece;
10470         piece = boards[forwardMostMove][fromY][fromX];
10471         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10472         if(gameInfo.variant == VariantKnightmate)
10473             king += (int) WhiteUnicorn - (int) WhiteKing;
10474         if(forwardMostMove == 0) {
10475             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10476                 fprintf(serverMoves, "%s;", UserName());
10477             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10478                 fprintf(serverMoves, "%s;", second.tidy);
10479             fprintf(serverMoves, "%s;", first.tidy);
10480             if(gameMode == MachinePlaysWhite)
10481                 fprintf(serverMoves, "%s;", UserName());
10482             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10483                 fprintf(serverMoves, "%s;", second.tidy);
10484         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10485         lastLoadFlag = loadFlag;
10486         // print base move
10487         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10488         // print castling suffix
10489         if( toY == fromY && piece == king ) {
10490             if(toX-fromX > 1)
10491                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10492             if(fromX-toX >1)
10493                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10494         }
10495         // e.p. suffix
10496         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10497              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10498              boards[forwardMostMove][toY][toX] == EmptySquare
10499              && fromX != toX && fromY != toY)
10500                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10501         // promotion suffix
10502         if(promoChar != NULLCHAR) {
10503             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10504                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10505                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10506             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10507         }
10508         if(!loadFlag) {
10509                 char buf[MOVE_LEN*2], *p; int len;
10510             fprintf(serverMoves, "/%d/%d",
10511                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10512             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10513             else                      timeLeft = blackTimeRemaining/1000;
10514             fprintf(serverMoves, "/%d", timeLeft);
10515                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10516                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10517                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10518                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10519             fprintf(serverMoves, "/%s", buf);
10520         }
10521         fflush(serverMoves);
10522     }
10523
10524     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10525         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10526       return;
10527     }
10528     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10529     if (commentList[forwardMostMove+1] != NULL) {
10530         free(commentList[forwardMostMove+1]);
10531         commentList[forwardMostMove+1] = NULL;
10532     }
10533     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10534     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10535     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10536     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10537     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10538     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10539     adjustedClock = FALSE;
10540     gameInfo.result = GameUnfinished;
10541     if (gameInfo.resultDetails != NULL) {
10542         free(gameInfo.resultDetails);
10543         gameInfo.resultDetails = NULL;
10544     }
10545     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10546                               moveList[forwardMostMove - 1]);
10547     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10548       case MT_NONE:
10549       case MT_STALEMATE:
10550       default:
10551         break;
10552       case MT_CHECK:
10553         if(!IS_SHOGI(gameInfo.variant))
10554             strcat(parseList[forwardMostMove - 1], "+");
10555         break;
10556       case MT_CHECKMATE:
10557       case MT_STAINMATE:
10558         strcat(parseList[forwardMostMove - 1], "#");
10559         break;
10560     }
10561 }
10562
10563 /* Updates currentMove if not pausing */
10564 void
10565 ShowMove (int fromX, int fromY, int toX, int toY)
10566 {
10567     int instant = (gameMode == PlayFromGameFile) ?
10568         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10569     if(appData.noGUI) return;
10570     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10571         if (!instant) {
10572             if (forwardMostMove == currentMove + 1) {
10573                 AnimateMove(boards[forwardMostMove - 1],
10574                             fromX, fromY, toX, toY);
10575             }
10576         }
10577         currentMove = forwardMostMove;
10578     }
10579
10580     killX = killY = -1; // [HGM] lion: used up
10581
10582     if (instant) return;
10583
10584     DisplayMove(currentMove - 1);
10585     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10586             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10587                 SetHighlights(fromX, fromY, toX, toY);
10588             }
10589     }
10590     DrawPosition(FALSE, boards[currentMove]);
10591     DisplayBothClocks();
10592     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10593 }
10594
10595 void
10596 SendEgtPath (ChessProgramState *cps)
10597 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10598         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10599
10600         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10601
10602         while(*p) {
10603             char c, *q = name+1, *r, *s;
10604
10605             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10606             while(*p && *p != ',') *q++ = *p++;
10607             *q++ = ':'; *q = 0;
10608             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10609                 strcmp(name, ",nalimov:") == 0 ) {
10610                 // take nalimov path from the menu-changeable option first, if it is defined
10611               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10612                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10613             } else
10614             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10615                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10616                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10617                 s = r = StrStr(s, ":") + 1; // beginning of path info
10618                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10619                 c = *r; *r = 0;             // temporarily null-terminate path info
10620                     *--q = 0;               // strip of trailig ':' from name
10621                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10622                 *r = c;
10623                 SendToProgram(buf,cps);     // send egtbpath command for this format
10624             }
10625             if(*p == ',') p++; // read away comma to position for next format name
10626         }
10627 }
10628
10629 static int
10630 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10631 {
10632       int width = 8, height = 8, holdings = 0;             // most common sizes
10633       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10634       // correct the deviations default for each variant
10635       if( v == VariantXiangqi ) width = 9,  height = 10;
10636       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10637       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10638       if( v == VariantCapablanca || v == VariantCapaRandom ||
10639           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10640                                 width = 10;
10641       if( v == VariantCourier ) width = 12;
10642       if( v == VariantSuper )                            holdings = 8;
10643       if( v == VariantGreat )   width = 10,              holdings = 8;
10644       if( v == VariantSChess )                           holdings = 7;
10645       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10646       if( v == VariantChuChess) width = 10, height = 10;
10647       if( v == VariantChu )     width = 12, height = 12;
10648       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10649              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10650              holdingsSize >= 0 && holdingsSize != holdings;
10651 }
10652
10653 char variantError[MSG_SIZ];
10654
10655 char *
10656 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10657 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10658       char *p, *variant = VariantName(v);
10659       static char b[MSG_SIZ];
10660       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10661            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10662                                                holdingsSize, variant); // cook up sized variant name
10663            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10664            if(StrStr(list, b) == NULL) {
10665                // specific sized variant not known, check if general sizing allowed
10666                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10667                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10668                             boardWidth, boardHeight, holdingsSize, engine);
10669                    return NULL;
10670                }
10671                /* [HGM] here we really should compare with the maximum supported board size */
10672            }
10673       } else snprintf(b, MSG_SIZ,"%s", variant);
10674       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10675       p = StrStr(list, b);
10676       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10677       if(p == NULL) {
10678           // occurs not at all in list, or only as sub-string
10679           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10680           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10681               int l = strlen(variantError);
10682               char *q;
10683               while(p != list && p[-1] != ',') p--;
10684               q = strchr(p, ',');
10685               if(q) *q = NULLCHAR;
10686               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10687               if(q) *q= ',';
10688           }
10689           return NULL;
10690       }
10691       return b;
10692 }
10693
10694 void
10695 InitChessProgram (ChessProgramState *cps, int setup)
10696 /* setup needed to setup FRC opening position */
10697 {
10698     char buf[MSG_SIZ], *b;
10699     if (appData.noChessProgram) return;
10700     hintRequested = FALSE;
10701     bookRequested = FALSE;
10702
10703     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10704     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10705     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10706     if(cps->memSize) { /* [HGM] memory */
10707       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10708         SendToProgram(buf, cps);
10709     }
10710     SendEgtPath(cps); /* [HGM] EGT */
10711     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10712       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10713         SendToProgram(buf, cps);
10714     }
10715
10716     setboardSpoiledMachineBlack = FALSE;
10717     SendToProgram(cps->initString, cps);
10718     if (gameInfo.variant != VariantNormal &&
10719         gameInfo.variant != VariantLoadable
10720         /* [HGM] also send variant if board size non-standard */
10721         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10722
10723       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10724                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10725       if (b == NULL) {
10726         VariantClass v;
10727         char c, *q = cps->variants, *p = strchr(q, ',');
10728         if(p) *p = NULLCHAR;
10729         v = StringToVariant(q);
10730         DisplayError(variantError, 0);
10731         if(v != VariantUnknown && cps == &first) {
10732             int w, h, s;
10733             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10734                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10735             ASSIGN(appData.variant, q);
10736             Reset(TRUE, FALSE);
10737         }
10738         if(p) *p = ',';
10739         return;
10740       }
10741
10742       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10743       SendToProgram(buf, cps);
10744     }
10745     currentlyInitializedVariant = gameInfo.variant;
10746
10747     /* [HGM] send opening position in FRC to first engine */
10748     if(setup) {
10749           SendToProgram("force\n", cps);
10750           SendBoard(cps, 0);
10751           /* engine is now in force mode! Set flag to wake it up after first move. */
10752           setboardSpoiledMachineBlack = 1;
10753     }
10754
10755     if (cps->sendICS) {
10756       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10757       SendToProgram(buf, cps);
10758     }
10759     cps->maybeThinking = FALSE;
10760     cps->offeredDraw = 0;
10761     if (!appData.icsActive) {
10762         SendTimeControl(cps, movesPerSession, timeControl,
10763                         timeIncrement, appData.searchDepth,
10764                         searchTime);
10765     }
10766     if (appData.showThinking
10767         // [HGM] thinking: four options require thinking output to be sent
10768         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10769                                 ) {
10770         SendToProgram("post\n", cps);
10771     }
10772     SendToProgram("hard\n", cps);
10773     if (!appData.ponderNextMove) {
10774         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10775            it without being sure what state we are in first.  "hard"
10776            is not a toggle, so that one is OK.
10777          */
10778         SendToProgram("easy\n", cps);
10779     }
10780     if (cps->usePing) {
10781       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10782       SendToProgram(buf, cps);
10783     }
10784     cps->initDone = TRUE;
10785     ClearEngineOutputPane(cps == &second);
10786 }
10787
10788
10789 void
10790 ResendOptions (ChessProgramState *cps)
10791 { // send the stored value of the options
10792   int i;
10793   char buf[MSG_SIZ];
10794   Option *opt = cps->option;
10795   for(i=0; i<cps->nrOptions; i++, opt++) {
10796       switch(opt->type) {
10797         case Spin:
10798         case Slider:
10799         case CheckBox:
10800             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10801           break;
10802         case ComboBox:
10803           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10804           break;
10805         default:
10806             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10807           break;
10808         case Button:
10809         case SaveButton:
10810           continue;
10811       }
10812       SendToProgram(buf, cps);
10813   }
10814 }
10815
10816 void
10817 StartChessProgram (ChessProgramState *cps)
10818 {
10819     char buf[MSG_SIZ];
10820     int err;
10821
10822     if (appData.noChessProgram) return;
10823     cps->initDone = FALSE;
10824
10825     if (strcmp(cps->host, "localhost") == 0) {
10826         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10827     } else if (*appData.remoteShell == NULLCHAR) {
10828         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10829     } else {
10830         if (*appData.remoteUser == NULLCHAR) {
10831           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10832                     cps->program);
10833         } else {
10834           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10835                     cps->host, appData.remoteUser, cps->program);
10836         }
10837         err = StartChildProcess(buf, "", &cps->pr);
10838     }
10839
10840     if (err != 0) {
10841       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10842         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10843         if(cps != &first) return;
10844         appData.noChessProgram = TRUE;
10845         ThawUI();
10846         SetNCPMode();
10847 //      DisplayFatalError(buf, err, 1);
10848 //      cps->pr = NoProc;
10849 //      cps->isr = NULL;
10850         return;
10851     }
10852
10853     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10854     if (cps->protocolVersion > 1) {
10855       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10856       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10857         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10858         cps->comboCnt = 0;  //                and values of combo boxes
10859       }
10860       SendToProgram(buf, cps);
10861       if(cps->reload) ResendOptions(cps);
10862     } else {
10863       SendToProgram("xboard\n", cps);
10864     }
10865 }
10866
10867 void
10868 TwoMachinesEventIfReady P((void))
10869 {
10870   static int curMess = 0;
10871   if (first.lastPing != first.lastPong) {
10872     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10873     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10874     return;
10875   }
10876   if (second.lastPing != second.lastPong) {
10877     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10878     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10879     return;
10880   }
10881   DisplayMessage("", ""); curMess = 0;
10882   TwoMachinesEvent();
10883 }
10884
10885 char *
10886 MakeName (char *template)
10887 {
10888     time_t clock;
10889     struct tm *tm;
10890     static char buf[MSG_SIZ];
10891     char *p = buf;
10892     int i;
10893
10894     clock = time((time_t *)NULL);
10895     tm = localtime(&clock);
10896
10897     while(*p++ = *template++) if(p[-1] == '%') {
10898         switch(*template++) {
10899           case 0:   *p = 0; return buf;
10900           case 'Y': i = tm->tm_year+1900; break;
10901           case 'y': i = tm->tm_year-100; break;
10902           case 'M': i = tm->tm_mon+1; break;
10903           case 'd': i = tm->tm_mday; break;
10904           case 'h': i = tm->tm_hour; break;
10905           case 'm': i = tm->tm_min; break;
10906           case 's': i = tm->tm_sec; break;
10907           default:  i = 0;
10908         }
10909         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10910     }
10911     return buf;
10912 }
10913
10914 int
10915 CountPlayers (char *p)
10916 {
10917     int n = 0;
10918     while(p = strchr(p, '\n')) p++, n++; // count participants
10919     return n;
10920 }
10921
10922 FILE *
10923 WriteTourneyFile (char *results, FILE *f)
10924 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10925     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10926     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10927         // create a file with tournament description
10928         fprintf(f, "-participants {%s}\n", appData.participants);
10929         fprintf(f, "-seedBase %d\n", appData.seedBase);
10930         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10931         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10932         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10933         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10934         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10935         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10936         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10937         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10938         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10939         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10940         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10941         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10942         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10943         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10944         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10945         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10946         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10947         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10948         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10949         fprintf(f, "-smpCores %d\n", appData.smpCores);
10950         if(searchTime > 0)
10951                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10952         else {
10953                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10954                 fprintf(f, "-tc %s\n", appData.timeControl);
10955                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10956         }
10957         fprintf(f, "-results \"%s\"\n", results);
10958     }
10959     return f;
10960 }
10961
10962 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10963
10964 void
10965 Substitute (char *participants, int expunge)
10966 {
10967     int i, changed, changes=0, nPlayers=0;
10968     char *p, *q, *r, buf[MSG_SIZ];
10969     if(participants == NULL) return;
10970     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10971     r = p = participants; q = appData.participants;
10972     while(*p && *p == *q) {
10973         if(*p == '\n') r = p+1, nPlayers++;
10974         p++; q++;
10975     }
10976     if(*p) { // difference
10977         while(*p && *p++ != '\n');
10978         while(*q && *q++ != '\n');
10979       changed = nPlayers;
10980         changes = 1 + (strcmp(p, q) != 0);
10981     }
10982     if(changes == 1) { // a single engine mnemonic was changed
10983         q = r; while(*q) nPlayers += (*q++ == '\n');
10984         p = buf; while(*r && (*p = *r++) != '\n') p++;
10985         *p = NULLCHAR;
10986         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10987         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10988         if(mnemonic[i]) { // The substitute is valid
10989             FILE *f;
10990             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10991                 flock(fileno(f), LOCK_EX);
10992                 ParseArgsFromFile(f);
10993                 fseek(f, 0, SEEK_SET);
10994                 FREE(appData.participants); appData.participants = participants;
10995                 if(expunge) { // erase results of replaced engine
10996                     int len = strlen(appData.results), w, b, dummy;
10997                     for(i=0; i<len; i++) {
10998                         Pairing(i, nPlayers, &w, &b, &dummy);
10999                         if((w == changed || b == changed) && appData.results[i] == '*') {
11000                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11001                             fclose(f);
11002                             return;
11003                         }
11004                     }
11005                     for(i=0; i<len; i++) {
11006                         Pairing(i, nPlayers, &w, &b, &dummy);
11007                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11008                     }
11009                 }
11010                 WriteTourneyFile(appData.results, f);
11011                 fclose(f); // release lock
11012                 return;
11013             }
11014         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11015     }
11016     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11017     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11018     free(participants);
11019     return;
11020 }
11021
11022 int
11023 CheckPlayers (char *participants)
11024 {
11025         int i;
11026         char buf[MSG_SIZ], *p;
11027         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11028         while(p = strchr(participants, '\n')) {
11029             *p = NULLCHAR;
11030             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11031             if(!mnemonic[i]) {
11032                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11033                 *p = '\n';
11034                 DisplayError(buf, 0);
11035                 return 1;
11036             }
11037             *p = '\n';
11038             participants = p + 1;
11039         }
11040         return 0;
11041 }
11042
11043 int
11044 CreateTourney (char *name)
11045 {
11046         FILE *f;
11047         if(matchMode && strcmp(name, appData.tourneyFile)) {
11048              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11049         }
11050         if(name[0] == NULLCHAR) {
11051             if(appData.participants[0])
11052                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11053             return 0;
11054         }
11055         f = fopen(name, "r");
11056         if(f) { // file exists
11057             ASSIGN(appData.tourneyFile, name);
11058             ParseArgsFromFile(f); // parse it
11059         } else {
11060             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11061             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11062                 DisplayError(_("Not enough participants"), 0);
11063                 return 0;
11064             }
11065             if(CheckPlayers(appData.participants)) return 0;
11066             ASSIGN(appData.tourneyFile, name);
11067             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11068             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11069         }
11070         fclose(f);
11071         appData.noChessProgram = FALSE;
11072         appData.clockMode = TRUE;
11073         SetGNUMode();
11074         return 1;
11075 }
11076
11077 int
11078 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11079 {
11080     char buf[MSG_SIZ], *p, *q;
11081     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11082     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11083     skip = !all && group[0]; // if group requested, we start in skip mode
11084     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11085         p = names; q = buf; header = 0;
11086         while(*p && *p != '\n') *q++ = *p++;
11087         *q = 0;
11088         if(*p == '\n') p++;
11089         if(buf[0] == '#') {
11090             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11091             depth++; // we must be entering a new group
11092             if(all) continue; // suppress printing group headers when complete list requested
11093             header = 1;
11094             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11095         }
11096         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11097         if(engineList[i]) free(engineList[i]);
11098         engineList[i] = strdup(buf);
11099         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11100         if(engineMnemonic[i]) free(engineMnemonic[i]);
11101         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11102             strcat(buf, " (");
11103             sscanf(q + 8, "%s", buf + strlen(buf));
11104             strcat(buf, ")");
11105         }
11106         engineMnemonic[i] = strdup(buf);
11107         i++;
11108     }
11109     engineList[i] = engineMnemonic[i] = NULL;
11110     return i;
11111 }
11112
11113 // following implemented as macro to avoid type limitations
11114 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11115
11116 void
11117 SwapEngines (int n)
11118 {   // swap settings for first engine and other engine (so far only some selected options)
11119     int h;
11120     char *p;
11121     if(n == 0) return;
11122     SWAP(directory, p)
11123     SWAP(chessProgram, p)
11124     SWAP(isUCI, h)
11125     SWAP(hasOwnBookUCI, h)
11126     SWAP(protocolVersion, h)
11127     SWAP(reuse, h)
11128     SWAP(scoreIsAbsolute, h)
11129     SWAP(timeOdds, h)
11130     SWAP(logo, p)
11131     SWAP(pgnName, p)
11132     SWAP(pvSAN, h)
11133     SWAP(engOptions, p)
11134     SWAP(engInitString, p)
11135     SWAP(computerString, p)
11136     SWAP(features, p)
11137     SWAP(fenOverride, p)
11138     SWAP(NPS, h)
11139     SWAP(accumulateTC, h)
11140     SWAP(drawDepth, h)
11141     SWAP(host, p)
11142     SWAP(pseudo, h)
11143 }
11144
11145 int
11146 GetEngineLine (char *s, int n)
11147 {
11148     int i;
11149     char buf[MSG_SIZ];
11150     extern char *icsNames;
11151     if(!s || !*s) return 0;
11152     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11153     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11154     if(!mnemonic[i]) return 0;
11155     if(n == 11) return 1; // just testing if there was a match
11156     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11157     if(n == 1) SwapEngines(n);
11158     ParseArgsFromString(buf);
11159     if(n == 1) SwapEngines(n);
11160     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11161         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11162         ParseArgsFromString(buf);
11163     }
11164     return 1;
11165 }
11166
11167 int
11168 SetPlayer (int player, char *p)
11169 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11170     int i;
11171     char buf[MSG_SIZ], *engineName;
11172     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11173     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11174     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11175     if(mnemonic[i]) {
11176         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11177         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11178         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11179         ParseArgsFromString(buf);
11180     } else { // no engine with this nickname is installed!
11181         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11182         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11183         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11184         ModeHighlight();
11185         DisplayError(buf, 0);
11186         return 0;
11187     }
11188     free(engineName);
11189     return i;
11190 }
11191
11192 char *recentEngines;
11193
11194 void
11195 RecentEngineEvent (int nr)
11196 {
11197     int n;
11198 //    SwapEngines(1); // bump first to second
11199 //    ReplaceEngine(&second, 1); // and load it there
11200     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11201     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11202     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11203         ReplaceEngine(&first, 0);
11204         FloatToFront(&appData.recentEngineList, command[n]);
11205     }
11206 }
11207
11208 int
11209 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11210 {   // determine players from game number
11211     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11212
11213     if(appData.tourneyType == 0) {
11214         roundsPerCycle = (nPlayers - 1) | 1;
11215         pairingsPerRound = nPlayers / 2;
11216     } else if(appData.tourneyType > 0) {
11217         roundsPerCycle = nPlayers - appData.tourneyType;
11218         pairingsPerRound = appData.tourneyType;
11219     }
11220     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11221     gamesPerCycle = gamesPerRound * roundsPerCycle;
11222     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11223     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11224     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11225     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11226     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11227     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11228
11229     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11230     if(appData.roundSync) *syncInterval = gamesPerRound;
11231
11232     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11233
11234     if(appData.tourneyType == 0) {
11235         if(curPairing == (nPlayers-1)/2 ) {
11236             *whitePlayer = curRound;
11237             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11238         } else {
11239             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11240             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11241             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11242             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11243         }
11244     } else if(appData.tourneyType > 1) {
11245         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11246         *whitePlayer = curRound + appData.tourneyType;
11247     } else if(appData.tourneyType > 0) {
11248         *whitePlayer = curPairing;
11249         *blackPlayer = curRound + appData.tourneyType;
11250     }
11251
11252     // take care of white/black alternation per round.
11253     // For cycles and games this is already taken care of by default, derived from matchGame!
11254     return curRound & 1;
11255 }
11256
11257 int
11258 NextTourneyGame (int nr, int *swapColors)
11259 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11260     char *p, *q;
11261     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11262     FILE *tf;
11263     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11264     tf = fopen(appData.tourneyFile, "r");
11265     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11266     ParseArgsFromFile(tf); fclose(tf);
11267     InitTimeControls(); // TC might be altered from tourney file
11268
11269     nPlayers = CountPlayers(appData.participants); // count participants
11270     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11271     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11272
11273     if(syncInterval) {
11274         p = q = appData.results;
11275         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11276         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11277             DisplayMessage(_("Waiting for other game(s)"),"");
11278             waitingForGame = TRUE;
11279             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11280             return 0;
11281         }
11282         waitingForGame = FALSE;
11283     }
11284
11285     if(appData.tourneyType < 0) {
11286         if(nr>=0 && !pairingReceived) {
11287             char buf[1<<16];
11288             if(pairing.pr == NoProc) {
11289                 if(!appData.pairingEngine[0]) {
11290                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11291                     return 0;
11292                 }
11293                 StartChessProgram(&pairing); // starts the pairing engine
11294             }
11295             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11296             SendToProgram(buf, &pairing);
11297             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11298             SendToProgram(buf, &pairing);
11299             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11300         }
11301         pairingReceived = 0;                              // ... so we continue here
11302         *swapColors = 0;
11303         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11304         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11305         matchGame = 1; roundNr = nr / syncInterval + 1;
11306     }
11307
11308     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11309
11310     // redefine engines, engine dir, etc.
11311     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11312     if(first.pr == NoProc) {
11313       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11314       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11315     }
11316     if(second.pr == NoProc) {
11317       SwapEngines(1);
11318       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11319       SwapEngines(1);         // and make that valid for second engine by swapping
11320       InitEngine(&second, 1);
11321     }
11322     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11323     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11324     return OK;
11325 }
11326
11327 void
11328 NextMatchGame ()
11329 {   // performs game initialization that does not invoke engines, and then tries to start the game
11330     int res, firstWhite, swapColors = 0;
11331     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11332     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
11333         char buf[MSG_SIZ];
11334         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11335         if(strcmp(buf, currentDebugFile)) { // name has changed
11336             FILE *f = fopen(buf, "w");
11337             if(f) { // if opening the new file failed, just keep using the old one
11338                 ASSIGN(currentDebugFile, buf);
11339                 fclose(debugFP);
11340                 debugFP = f;
11341             }
11342             if(appData.serverFileName) {
11343                 if(serverFP) fclose(serverFP);
11344                 serverFP = fopen(appData.serverFileName, "w");
11345                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11346                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11347             }
11348         }
11349     }
11350     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11351     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11352     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11353     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11354     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11355     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11356     Reset(FALSE, first.pr != NoProc);
11357     res = LoadGameOrPosition(matchGame); // setup game
11358     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11359     if(!res) return; // abort when bad game/pos file
11360     TwoMachinesEvent();
11361 }
11362
11363 void
11364 UserAdjudicationEvent (int result)
11365 {
11366     ChessMove gameResult = GameIsDrawn;
11367
11368     if( result > 0 ) {
11369         gameResult = WhiteWins;
11370     }
11371     else if( result < 0 ) {
11372         gameResult = BlackWins;
11373     }
11374
11375     if( gameMode == TwoMachinesPlay ) {
11376         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11377     }
11378 }
11379
11380
11381 // [HGM] save: calculate checksum of game to make games easily identifiable
11382 int
11383 StringCheckSum (char *s)
11384 {
11385         int i = 0;
11386         if(s==NULL) return 0;
11387         while(*s) i = i*259 + *s++;
11388         return i;
11389 }
11390
11391 int
11392 GameCheckSum ()
11393 {
11394         int i, sum=0;
11395         for(i=backwardMostMove; i<forwardMostMove; i++) {
11396                 sum += pvInfoList[i].depth;
11397                 sum += StringCheckSum(parseList[i]);
11398                 sum += StringCheckSum(commentList[i]);
11399                 sum *= 261;
11400         }
11401         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11402         return sum + StringCheckSum(commentList[i]);
11403 } // end of save patch
11404
11405 void
11406 GameEnds (ChessMove result, char *resultDetails, int whosays)
11407 {
11408     GameMode nextGameMode;
11409     int isIcsGame;
11410     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11411
11412     if(endingGame) return; /* [HGM] crash: forbid recursion */
11413     endingGame = 1;
11414     if(twoBoards) { // [HGM] dual: switch back to one board
11415         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11416         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11417     }
11418     if (appData.debugMode) {
11419       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11420               result, resultDetails ? resultDetails : "(null)", whosays);
11421     }
11422
11423     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11424
11425     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11426
11427     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11428         /* If we are playing on ICS, the server decides when the
11429            game is over, but the engine can offer to draw, claim
11430            a draw, or resign.
11431          */
11432 #if ZIPPY
11433         if (appData.zippyPlay && first.initDone) {
11434             if (result == GameIsDrawn) {
11435                 /* In case draw still needs to be claimed */
11436                 SendToICS(ics_prefix);
11437                 SendToICS("draw\n");
11438             } else if (StrCaseStr(resultDetails, "resign")) {
11439                 SendToICS(ics_prefix);
11440                 SendToICS("resign\n");
11441             }
11442         }
11443 #endif
11444         endingGame = 0; /* [HGM] crash */
11445         return;
11446     }
11447
11448     /* If we're loading the game from a file, stop */
11449     if (whosays == GE_FILE) {
11450       (void) StopLoadGameTimer();
11451       gameFileFP = NULL;
11452     }
11453
11454     /* Cancel draw offers */
11455     first.offeredDraw = second.offeredDraw = 0;
11456
11457     /* If this is an ICS game, only ICS can really say it's done;
11458        if not, anyone can. */
11459     isIcsGame = (gameMode == IcsPlayingWhite ||
11460                  gameMode == IcsPlayingBlack ||
11461                  gameMode == IcsObserving    ||
11462                  gameMode == IcsExamining);
11463
11464     if (!isIcsGame || whosays == GE_ICS) {
11465         /* OK -- not an ICS game, or ICS said it was done */
11466         StopClocks();
11467         if (!isIcsGame && !appData.noChessProgram)
11468           SetUserThinkingEnables();
11469
11470         /* [HGM] if a machine claims the game end we verify this claim */
11471         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11472             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11473                 char claimer;
11474                 ChessMove trueResult = (ChessMove) -1;
11475
11476                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11477                                             first.twoMachinesColor[0] :
11478                                             second.twoMachinesColor[0] ;
11479
11480                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11481                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11482                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11483                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11484                 } else
11485                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11486                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11487                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11488                 } else
11489                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11490                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11491                 }
11492
11493                 // now verify win claims, but not in drop games, as we don't understand those yet
11494                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11495                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11496                     (result == WhiteWins && claimer == 'w' ||
11497                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11498                       if (appData.debugMode) {
11499                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11500                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11501                       }
11502                       if(result != trueResult) {
11503                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11504                               result = claimer == 'w' ? BlackWins : WhiteWins;
11505                               resultDetails = buf;
11506                       }
11507                 } else
11508                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11509                     && (forwardMostMove <= backwardMostMove ||
11510                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11511                         (claimer=='b')==(forwardMostMove&1))
11512                                                                                   ) {
11513                       /* [HGM] verify: draws that were not flagged are false claims */
11514                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11515                       result = claimer == 'w' ? BlackWins : WhiteWins;
11516                       resultDetails = buf;
11517                 }
11518                 /* (Claiming a loss is accepted no questions asked!) */
11519             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11520                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11521                 result = GameUnfinished;
11522                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11523             }
11524             /* [HGM] bare: don't allow bare King to win */
11525             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11526                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11527                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11528                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11529                && result != GameIsDrawn)
11530             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11531                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11532                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11533                         if(p >= 0 && p <= (int)WhiteKing) k++;
11534                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11535                 }
11536                 if (appData.debugMode) {
11537                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11538                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11539                 }
11540                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11541                         result = GameIsDrawn;
11542                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11543                         resultDetails = buf;
11544                 }
11545             }
11546         }
11547
11548
11549         if(serverMoves != NULL && !loadFlag) { char c = '=';
11550             if(result==WhiteWins) c = '+';
11551             if(result==BlackWins) c = '-';
11552             if(resultDetails != NULL)
11553                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11554         }
11555         if (resultDetails != NULL) {
11556             gameInfo.result = result;
11557             gameInfo.resultDetails = StrSave(resultDetails);
11558
11559             /* display last move only if game was not loaded from file */
11560             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11561                 DisplayMove(currentMove - 1);
11562
11563             if (forwardMostMove != 0) {
11564                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11565                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11566                                                                 ) {
11567                     if (*appData.saveGameFile != NULLCHAR) {
11568                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11569                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11570                         else
11571                         SaveGameToFile(appData.saveGameFile, TRUE);
11572                     } else if (appData.autoSaveGames) {
11573                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11574                     }
11575                     if (*appData.savePositionFile != NULLCHAR) {
11576                         SavePositionToFile(appData.savePositionFile);
11577                     }
11578                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11579                 }
11580             }
11581
11582             /* Tell program how game ended in case it is learning */
11583             /* [HGM] Moved this to after saving the PGN, just in case */
11584             /* engine died and we got here through time loss. In that */
11585             /* case we will get a fatal error writing the pipe, which */
11586             /* would otherwise lose us the PGN.                       */
11587             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11588             /* output during GameEnds should never be fatal anymore   */
11589             if (gameMode == MachinePlaysWhite ||
11590                 gameMode == MachinePlaysBlack ||
11591                 gameMode == TwoMachinesPlay ||
11592                 gameMode == IcsPlayingWhite ||
11593                 gameMode == IcsPlayingBlack ||
11594                 gameMode == BeginningOfGame) {
11595                 char buf[MSG_SIZ];
11596                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11597                         resultDetails);
11598                 if (first.pr != NoProc) {
11599                     SendToProgram(buf, &first);
11600                 }
11601                 if (second.pr != NoProc &&
11602                     gameMode == TwoMachinesPlay) {
11603                     SendToProgram(buf, &second);
11604                 }
11605             }
11606         }
11607
11608         if (appData.icsActive) {
11609             if (appData.quietPlay &&
11610                 (gameMode == IcsPlayingWhite ||
11611                  gameMode == IcsPlayingBlack)) {
11612                 SendToICS(ics_prefix);
11613                 SendToICS("set shout 1\n");
11614             }
11615             nextGameMode = IcsIdle;
11616             ics_user_moved = FALSE;
11617             /* clean up premove.  It's ugly when the game has ended and the
11618              * premove highlights are still on the board.
11619              */
11620             if (gotPremove) {
11621               gotPremove = FALSE;
11622               ClearPremoveHighlights();
11623               DrawPosition(FALSE, boards[currentMove]);
11624             }
11625             if (whosays == GE_ICS) {
11626                 switch (result) {
11627                 case WhiteWins:
11628                     if (gameMode == IcsPlayingWhite)
11629                         PlayIcsWinSound();
11630                     else if(gameMode == IcsPlayingBlack)
11631                         PlayIcsLossSound();
11632                     break;
11633                 case BlackWins:
11634                     if (gameMode == IcsPlayingBlack)
11635                         PlayIcsWinSound();
11636                     else if(gameMode == IcsPlayingWhite)
11637                         PlayIcsLossSound();
11638                     break;
11639                 case GameIsDrawn:
11640                     PlayIcsDrawSound();
11641                     break;
11642                 default:
11643                     PlayIcsUnfinishedSound();
11644                 }
11645             }
11646             if(appData.quitNext) { ExitEvent(0); return; }
11647         } else if (gameMode == EditGame ||
11648                    gameMode == PlayFromGameFile ||
11649                    gameMode == AnalyzeMode ||
11650                    gameMode == AnalyzeFile) {
11651             nextGameMode = gameMode;
11652         } else {
11653             nextGameMode = EndOfGame;
11654         }
11655         pausing = FALSE;
11656         ModeHighlight();
11657     } else {
11658         nextGameMode = gameMode;
11659     }
11660
11661     if (appData.noChessProgram) {
11662         gameMode = nextGameMode;
11663         ModeHighlight();
11664         endingGame = 0; /* [HGM] crash */
11665         return;
11666     }
11667
11668     if (first.reuse) {
11669         /* Put first chess program into idle state */
11670         if (first.pr != NoProc &&
11671             (gameMode == MachinePlaysWhite ||
11672              gameMode == MachinePlaysBlack ||
11673              gameMode == TwoMachinesPlay ||
11674              gameMode == IcsPlayingWhite ||
11675              gameMode == IcsPlayingBlack ||
11676              gameMode == BeginningOfGame)) {
11677             SendToProgram("force\n", &first);
11678             if (first.usePing) {
11679               char buf[MSG_SIZ];
11680               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11681               SendToProgram(buf, &first);
11682             }
11683         }
11684     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11685         /* Kill off first chess program */
11686         if (first.isr != NULL)
11687           RemoveInputSource(first.isr);
11688         first.isr = NULL;
11689
11690         if (first.pr != NoProc) {
11691             ExitAnalyzeMode();
11692             DoSleep( appData.delayBeforeQuit );
11693             SendToProgram("quit\n", &first);
11694             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11695             first.reload = TRUE;
11696         }
11697         first.pr = NoProc;
11698     }
11699     if (second.reuse) {
11700         /* Put second chess program into idle state */
11701         if (second.pr != NoProc &&
11702             gameMode == TwoMachinesPlay) {
11703             SendToProgram("force\n", &second);
11704             if (second.usePing) {
11705               char buf[MSG_SIZ];
11706               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11707               SendToProgram(buf, &second);
11708             }
11709         }
11710     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11711         /* Kill off second chess program */
11712         if (second.isr != NULL)
11713           RemoveInputSource(second.isr);
11714         second.isr = NULL;
11715
11716         if (second.pr != NoProc) {
11717             DoSleep( appData.delayBeforeQuit );
11718             SendToProgram("quit\n", &second);
11719             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11720             second.reload = TRUE;
11721         }
11722         second.pr = NoProc;
11723     }
11724
11725     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11726         char resChar = '=';
11727         switch (result) {
11728         case WhiteWins:
11729           resChar = '+';
11730           if (first.twoMachinesColor[0] == 'w') {
11731             first.matchWins++;
11732           } else {
11733             second.matchWins++;
11734           }
11735           break;
11736         case BlackWins:
11737           resChar = '-';
11738           if (first.twoMachinesColor[0] == 'b') {
11739             first.matchWins++;
11740           } else {
11741             second.matchWins++;
11742           }
11743           break;
11744         case GameUnfinished:
11745           resChar = ' ';
11746         default:
11747           break;
11748         }
11749
11750         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11751         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11752             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11753             ReserveGame(nextGame, resChar); // sets nextGame
11754             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11755             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11756         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11757
11758         if (nextGame <= appData.matchGames && !abortMatch) {
11759             gameMode = nextGameMode;
11760             matchGame = nextGame; // this will be overruled in tourney mode!
11761             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11762             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11763             endingGame = 0; /* [HGM] crash */
11764             return;
11765         } else {
11766             gameMode = nextGameMode;
11767             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11768                      first.tidy, second.tidy,
11769                      first.matchWins, second.matchWins,
11770                      appData.matchGames - (first.matchWins + second.matchWins));
11771             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11772             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11773             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11774             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11775                 first.twoMachinesColor = "black\n";
11776                 second.twoMachinesColor = "white\n";
11777             } else {
11778                 first.twoMachinesColor = "white\n";
11779                 second.twoMachinesColor = "black\n";
11780             }
11781         }
11782     }
11783     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11784         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11785       ExitAnalyzeMode();
11786     gameMode = nextGameMode;
11787     ModeHighlight();
11788     endingGame = 0;  /* [HGM] crash */
11789     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11790         if(matchMode == TRUE) { // match through command line: exit with or without popup
11791             if(ranking) {
11792                 ToNrEvent(forwardMostMove);
11793                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11794                 else ExitEvent(0);
11795             } else DisplayFatalError(buf, 0, 0);
11796         } else { // match through menu; just stop, with or without popup
11797             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11798             ModeHighlight();
11799             if(ranking){
11800                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11801             } else DisplayNote(buf);
11802       }
11803       if(ranking) free(ranking);
11804     }
11805 }
11806
11807 /* Assumes program was just initialized (initString sent).
11808    Leaves program in force mode. */
11809 void
11810 FeedMovesToProgram (ChessProgramState *cps, int upto)
11811 {
11812     int i;
11813
11814     if (appData.debugMode)
11815       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11816               startedFromSetupPosition ? "position and " : "",
11817               backwardMostMove, upto, cps->which);
11818     if(currentlyInitializedVariant != gameInfo.variant) {
11819       char buf[MSG_SIZ];
11820         // [HGM] variantswitch: make engine aware of new variant
11821         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11822                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11823                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11824         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11825         SendToProgram(buf, cps);
11826         currentlyInitializedVariant = gameInfo.variant;
11827     }
11828     SendToProgram("force\n", cps);
11829     if (startedFromSetupPosition) {
11830         SendBoard(cps, backwardMostMove);
11831     if (appData.debugMode) {
11832         fprintf(debugFP, "feedMoves\n");
11833     }
11834     }
11835     for (i = backwardMostMove; i < upto; i++) {
11836         SendMoveToProgram(i, cps);
11837     }
11838 }
11839
11840
11841 int
11842 ResurrectChessProgram ()
11843 {
11844      /* The chess program may have exited.
11845         If so, restart it and feed it all the moves made so far. */
11846     static int doInit = 0;
11847
11848     if (appData.noChessProgram) return 1;
11849
11850     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11851         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11852         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11853         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11854     } else {
11855         if (first.pr != NoProc) return 1;
11856         StartChessProgram(&first);
11857     }
11858     InitChessProgram(&first, FALSE);
11859     FeedMovesToProgram(&first, currentMove);
11860
11861     if (!first.sendTime) {
11862         /* can't tell gnuchess what its clock should read,
11863            so we bow to its notion. */
11864         ResetClocks();
11865         timeRemaining[0][currentMove] = whiteTimeRemaining;
11866         timeRemaining[1][currentMove] = blackTimeRemaining;
11867     }
11868
11869     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11870                 appData.icsEngineAnalyze) && first.analysisSupport) {
11871       SendToProgram("analyze\n", &first);
11872       first.analyzing = TRUE;
11873     }
11874     return 1;
11875 }
11876
11877 /*
11878  * Button procedures
11879  */
11880 void
11881 Reset (int redraw, int init)
11882 {
11883     int i;
11884
11885     if (appData.debugMode) {
11886         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11887                 redraw, init, gameMode);
11888     }
11889     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11890     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11891     CleanupTail(); // [HGM] vari: delete any stored variations
11892     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11893     pausing = pauseExamInvalid = FALSE;
11894     startedFromSetupPosition = blackPlaysFirst = FALSE;
11895     firstMove = TRUE;
11896     whiteFlag = blackFlag = FALSE;
11897     userOfferedDraw = FALSE;
11898     hintRequested = bookRequested = FALSE;
11899     first.maybeThinking = FALSE;
11900     second.maybeThinking = FALSE;
11901     first.bookSuspend = FALSE; // [HGM] book
11902     second.bookSuspend = FALSE;
11903     thinkOutput[0] = NULLCHAR;
11904     lastHint[0] = NULLCHAR;
11905     ClearGameInfo(&gameInfo);
11906     gameInfo.variant = StringToVariant(appData.variant);
11907     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11908     ics_user_moved = ics_clock_paused = FALSE;
11909     ics_getting_history = H_FALSE;
11910     ics_gamenum = -1;
11911     white_holding[0] = black_holding[0] = NULLCHAR;
11912     ClearProgramStats();
11913     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11914
11915     ResetFrontEnd();
11916     ClearHighlights();
11917     flipView = appData.flipView;
11918     ClearPremoveHighlights();
11919     gotPremove = FALSE;
11920     alarmSounded = FALSE;
11921     killX = killY = -1; // [HGM] lion
11922
11923     GameEnds(EndOfFile, NULL, GE_PLAYER);
11924     if(appData.serverMovesName != NULL) {
11925         /* [HGM] prepare to make moves file for broadcasting */
11926         clock_t t = clock();
11927         if(serverMoves != NULL) fclose(serverMoves);
11928         serverMoves = fopen(appData.serverMovesName, "r");
11929         if(serverMoves != NULL) {
11930             fclose(serverMoves);
11931             /* delay 15 sec before overwriting, so all clients can see end */
11932             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11933         }
11934         serverMoves = fopen(appData.serverMovesName, "w");
11935     }
11936
11937     ExitAnalyzeMode();
11938     gameMode = BeginningOfGame;
11939     ModeHighlight();
11940     if(appData.icsActive) gameInfo.variant = VariantNormal;
11941     currentMove = forwardMostMove = backwardMostMove = 0;
11942     MarkTargetSquares(1);
11943     InitPosition(redraw);
11944     for (i = 0; i < MAX_MOVES; i++) {
11945         if (commentList[i] != NULL) {
11946             free(commentList[i]);
11947             commentList[i] = NULL;
11948         }
11949     }
11950     ResetClocks();
11951     timeRemaining[0][0] = whiteTimeRemaining;
11952     timeRemaining[1][0] = blackTimeRemaining;
11953
11954     if (first.pr == NoProc) {
11955         StartChessProgram(&first);
11956     }
11957     if (init) {
11958             InitChessProgram(&first, startedFromSetupPosition);
11959     }
11960     DisplayTitle("");
11961     DisplayMessage("", "");
11962     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11963     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11964     ClearMap();        // [HGM] exclude: invalidate map
11965 }
11966
11967 void
11968 AutoPlayGameLoop ()
11969 {
11970     for (;;) {
11971         if (!AutoPlayOneMove())
11972           return;
11973         if (matchMode || appData.timeDelay == 0)
11974           continue;
11975         if (appData.timeDelay < 0)
11976           return;
11977         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11978         break;
11979     }
11980 }
11981
11982 void
11983 AnalyzeNextGame()
11984 {
11985     ReloadGame(1); // next game
11986 }
11987
11988 int
11989 AutoPlayOneMove ()
11990 {
11991     int fromX, fromY, toX, toY;
11992
11993     if (appData.debugMode) {
11994       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11995     }
11996
11997     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11998       return FALSE;
11999
12000     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12001       pvInfoList[currentMove].depth = programStats.depth;
12002       pvInfoList[currentMove].score = programStats.score;
12003       pvInfoList[currentMove].time  = 0;
12004       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12005       else { // append analysis of final position as comment
12006         char buf[MSG_SIZ];
12007         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12008         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12009       }
12010       programStats.depth = 0;
12011     }
12012
12013     if (currentMove >= forwardMostMove) {
12014       if(gameMode == AnalyzeFile) {
12015           if(appData.loadGameIndex == -1) {
12016             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12017           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12018           } else {
12019           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12020         }
12021       }
12022 //      gameMode = EndOfGame;
12023 //      ModeHighlight();
12024
12025       /* [AS] Clear current move marker at the end of a game */
12026       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12027
12028       return FALSE;
12029     }
12030
12031     toX = moveList[currentMove][2] - AAA;
12032     toY = moveList[currentMove][3] - ONE;
12033
12034     if (moveList[currentMove][1] == '@') {
12035         if (appData.highlightLastMove) {
12036             SetHighlights(-1, -1, toX, toY);
12037         }
12038     } else {
12039         int viaX = moveList[currentMove][5] - AAA;
12040         int viaY = moveList[currentMove][6] - ONE;
12041         fromX = moveList[currentMove][0] - AAA;
12042         fromY = moveList[currentMove][1] - ONE;
12043
12044         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12045
12046         if(moveList[currentMove][4] == ';') { // multi-leg
12047             ChessSquare piece = boards[currentMove][viaY][viaX];
12048             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12049             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12050             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12051             boards[currentMove][viaY][viaX] = piece;
12052         } else
12053         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12054
12055         if (appData.highlightLastMove) {
12056             SetHighlights(fromX, fromY, toX, toY);
12057         }
12058     }
12059     DisplayMove(currentMove);
12060     SendMoveToProgram(currentMove++, &first);
12061     DisplayBothClocks();
12062     DrawPosition(FALSE, boards[currentMove]);
12063     // [HGM] PV info: always display, routine tests if empty
12064     DisplayComment(currentMove - 1, commentList[currentMove]);
12065     return TRUE;
12066 }
12067
12068
12069 int
12070 LoadGameOneMove (ChessMove readAhead)
12071 {
12072     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12073     char promoChar = NULLCHAR;
12074     ChessMove moveType;
12075     char move[MSG_SIZ];
12076     char *p, *q;
12077
12078     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12079         gameMode != AnalyzeMode && gameMode != Training) {
12080         gameFileFP = NULL;
12081         return FALSE;
12082     }
12083
12084     yyboardindex = forwardMostMove;
12085     if (readAhead != EndOfFile) {
12086       moveType = readAhead;
12087     } else {
12088       if (gameFileFP == NULL)
12089           return FALSE;
12090       moveType = (ChessMove) Myylex();
12091     }
12092
12093     done = FALSE;
12094     switch (moveType) {
12095       case Comment:
12096         if (appData.debugMode)
12097           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12098         p = yy_text;
12099
12100         /* append the comment but don't display it */
12101         AppendComment(currentMove, p, FALSE);
12102         return TRUE;
12103
12104       case WhiteCapturesEnPassant:
12105       case BlackCapturesEnPassant:
12106       case WhitePromotion:
12107       case BlackPromotion:
12108       case WhiteNonPromotion:
12109       case BlackNonPromotion:
12110       case NormalMove:
12111       case FirstLeg:
12112       case WhiteKingSideCastle:
12113       case WhiteQueenSideCastle:
12114       case BlackKingSideCastle:
12115       case BlackQueenSideCastle:
12116       case WhiteKingSideCastleWild:
12117       case WhiteQueenSideCastleWild:
12118       case BlackKingSideCastleWild:
12119       case BlackQueenSideCastleWild:
12120       /* PUSH Fabien */
12121       case WhiteHSideCastleFR:
12122       case WhiteASideCastleFR:
12123       case BlackHSideCastleFR:
12124       case BlackASideCastleFR:
12125       /* POP Fabien */
12126         if (appData.debugMode)
12127           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12128         fromX = currentMoveString[0] - AAA;
12129         fromY = currentMoveString[1] - ONE;
12130         toX = currentMoveString[2] - AAA;
12131         toY = currentMoveString[3] - ONE;
12132         promoChar = currentMoveString[4];
12133         if(promoChar == ';') promoChar = NULLCHAR;
12134         break;
12135
12136       case WhiteDrop:
12137       case BlackDrop:
12138         if (appData.debugMode)
12139           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12140         fromX = moveType == WhiteDrop ?
12141           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12142         (int) CharToPiece(ToLower(currentMoveString[0]));
12143         fromY = DROP_RANK;
12144         toX = currentMoveString[2] - AAA;
12145         toY = currentMoveString[3] - ONE;
12146         break;
12147
12148       case WhiteWins:
12149       case BlackWins:
12150       case GameIsDrawn:
12151       case GameUnfinished:
12152         if (appData.debugMode)
12153           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12154         p = strchr(yy_text, '{');
12155         if (p == NULL) p = strchr(yy_text, '(');
12156         if (p == NULL) {
12157             p = yy_text;
12158             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12159         } else {
12160             q = strchr(p, *p == '{' ? '}' : ')');
12161             if (q != NULL) *q = NULLCHAR;
12162             p++;
12163         }
12164         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12165         GameEnds(moveType, p, GE_FILE);
12166         done = TRUE;
12167         if (cmailMsgLoaded) {
12168             ClearHighlights();
12169             flipView = WhiteOnMove(currentMove);
12170             if (moveType == GameUnfinished) flipView = !flipView;
12171             if (appData.debugMode)
12172               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12173         }
12174         break;
12175
12176       case EndOfFile:
12177         if (appData.debugMode)
12178           fprintf(debugFP, "Parser hit end of file\n");
12179         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12180           case MT_NONE:
12181           case MT_CHECK:
12182             break;
12183           case MT_CHECKMATE:
12184           case MT_STAINMATE:
12185             if (WhiteOnMove(currentMove)) {
12186                 GameEnds(BlackWins, "Black mates", GE_FILE);
12187             } else {
12188                 GameEnds(WhiteWins, "White mates", GE_FILE);
12189             }
12190             break;
12191           case MT_STALEMATE:
12192             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12193             break;
12194         }
12195         done = TRUE;
12196         break;
12197
12198       case MoveNumberOne:
12199         if (lastLoadGameStart == GNUChessGame) {
12200             /* GNUChessGames have numbers, but they aren't move numbers */
12201             if (appData.debugMode)
12202               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12203                       yy_text, (int) moveType);
12204             return LoadGameOneMove(EndOfFile); /* tail recursion */
12205         }
12206         /* else fall thru */
12207
12208       case XBoardGame:
12209       case GNUChessGame:
12210       case PGNTag:
12211         /* Reached start of next game in file */
12212         if (appData.debugMode)
12213           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12214         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12215           case MT_NONE:
12216           case MT_CHECK:
12217             break;
12218           case MT_CHECKMATE:
12219           case MT_STAINMATE:
12220             if (WhiteOnMove(currentMove)) {
12221                 GameEnds(BlackWins, "Black mates", GE_FILE);
12222             } else {
12223                 GameEnds(WhiteWins, "White mates", GE_FILE);
12224             }
12225             break;
12226           case MT_STALEMATE:
12227             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12228             break;
12229         }
12230         done = TRUE;
12231         break;
12232
12233       case PositionDiagram:     /* should not happen; ignore */
12234       case ElapsedTime:         /* ignore */
12235       case NAG:                 /* ignore */
12236         if (appData.debugMode)
12237           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12238                   yy_text, (int) moveType);
12239         return LoadGameOneMove(EndOfFile); /* tail recursion */
12240
12241       case IllegalMove:
12242         if (appData.testLegality) {
12243             if (appData.debugMode)
12244               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12245             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12246                     (forwardMostMove / 2) + 1,
12247                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12248             DisplayError(move, 0);
12249             done = TRUE;
12250         } else {
12251             if (appData.debugMode)
12252               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12253                       yy_text, currentMoveString);
12254             if(currentMoveString[1] == '@') {
12255                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12256                 fromY = DROP_RANK;
12257             } else {
12258                 fromX = currentMoveString[0] - AAA;
12259                 fromY = currentMoveString[1] - ONE;
12260             }
12261             toX = currentMoveString[2] - AAA;
12262             toY = currentMoveString[3] - ONE;
12263             promoChar = currentMoveString[4];
12264         }
12265         break;
12266
12267       case AmbiguousMove:
12268         if (appData.debugMode)
12269           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12270         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12271                 (forwardMostMove / 2) + 1,
12272                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12273         DisplayError(move, 0);
12274         done = TRUE;
12275         break;
12276
12277       default:
12278       case ImpossibleMove:
12279         if (appData.debugMode)
12280           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12281         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12282                 (forwardMostMove / 2) + 1,
12283                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12284         DisplayError(move, 0);
12285         done = TRUE;
12286         break;
12287     }
12288
12289     if (done) {
12290         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12291             DrawPosition(FALSE, boards[currentMove]);
12292             DisplayBothClocks();
12293             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12294               DisplayComment(currentMove - 1, commentList[currentMove]);
12295         }
12296         (void) StopLoadGameTimer();
12297         gameFileFP = NULL;
12298         cmailOldMove = forwardMostMove;
12299         return FALSE;
12300     } else {
12301         /* currentMoveString is set as a side-effect of yylex */
12302
12303         thinkOutput[0] = NULLCHAR;
12304         MakeMove(fromX, fromY, toX, toY, promoChar);
12305         killX = killY = -1; // [HGM] lion: used up
12306         currentMove = forwardMostMove;
12307         return TRUE;
12308     }
12309 }
12310
12311 /* Load the nth game from the given file */
12312 int
12313 LoadGameFromFile (char *filename, int n, char *title, int useList)
12314 {
12315     FILE *f;
12316     char buf[MSG_SIZ];
12317
12318     if (strcmp(filename, "-") == 0) {
12319         f = stdin;
12320         title = "stdin";
12321     } else {
12322         f = fopen(filename, "rb");
12323         if (f == NULL) {
12324           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12325             DisplayError(buf, errno);
12326             return FALSE;
12327         }
12328     }
12329     if (fseek(f, 0, 0) == -1) {
12330         /* f is not seekable; probably a pipe */
12331         useList = FALSE;
12332     }
12333     if (useList && n == 0) {
12334         int error = GameListBuild(f);
12335         if (error) {
12336             DisplayError(_("Cannot build game list"), error);
12337         } else if (!ListEmpty(&gameList) &&
12338                    ((ListGame *) gameList.tailPred)->number > 1) {
12339             GameListPopUp(f, title);
12340             return TRUE;
12341         }
12342         GameListDestroy();
12343         n = 1;
12344     }
12345     if (n == 0) n = 1;
12346     return LoadGame(f, n, title, FALSE);
12347 }
12348
12349
12350 void
12351 MakeRegisteredMove ()
12352 {
12353     int fromX, fromY, toX, toY;
12354     char promoChar;
12355     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12356         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12357           case CMAIL_MOVE:
12358           case CMAIL_DRAW:
12359             if (appData.debugMode)
12360               fprintf(debugFP, "Restoring %s for game %d\n",
12361                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12362
12363             thinkOutput[0] = NULLCHAR;
12364             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12365             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12366             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12367             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12368             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12369             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12370             MakeMove(fromX, fromY, toX, toY, promoChar);
12371             ShowMove(fromX, fromY, toX, toY);
12372
12373             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12374               case MT_NONE:
12375               case MT_CHECK:
12376                 break;
12377
12378               case MT_CHECKMATE:
12379               case MT_STAINMATE:
12380                 if (WhiteOnMove(currentMove)) {
12381                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12382                 } else {
12383                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12384                 }
12385                 break;
12386
12387               case MT_STALEMATE:
12388                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12389                 break;
12390             }
12391
12392             break;
12393
12394           case CMAIL_RESIGN:
12395             if (WhiteOnMove(currentMove)) {
12396                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12397             } else {
12398                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12399             }
12400             break;
12401
12402           case CMAIL_ACCEPT:
12403             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12404             break;
12405
12406           default:
12407             break;
12408         }
12409     }
12410
12411     return;
12412 }
12413
12414 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12415 int
12416 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12417 {
12418     int retVal;
12419
12420     if (gameNumber > nCmailGames) {
12421         DisplayError(_("No more games in this message"), 0);
12422         return FALSE;
12423     }
12424     if (f == lastLoadGameFP) {
12425         int offset = gameNumber - lastLoadGameNumber;
12426         if (offset == 0) {
12427             cmailMsg[0] = NULLCHAR;
12428             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12429                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12430                 nCmailMovesRegistered--;
12431             }
12432             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12433             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12434                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12435             }
12436         } else {
12437             if (! RegisterMove()) return FALSE;
12438         }
12439     }
12440
12441     retVal = LoadGame(f, gameNumber, title, useList);
12442
12443     /* Make move registered during previous look at this game, if any */
12444     MakeRegisteredMove();
12445
12446     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12447         commentList[currentMove]
12448           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12449         DisplayComment(currentMove - 1, commentList[currentMove]);
12450     }
12451
12452     return retVal;
12453 }
12454
12455 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12456 int
12457 ReloadGame (int offset)
12458 {
12459     int gameNumber = lastLoadGameNumber + offset;
12460     if (lastLoadGameFP == NULL) {
12461         DisplayError(_("No game has been loaded yet"), 0);
12462         return FALSE;
12463     }
12464     if (gameNumber <= 0) {
12465         DisplayError(_("Can't back up any further"), 0);
12466         return FALSE;
12467     }
12468     if (cmailMsgLoaded) {
12469         return CmailLoadGame(lastLoadGameFP, gameNumber,
12470                              lastLoadGameTitle, lastLoadGameUseList);
12471     } else {
12472         return LoadGame(lastLoadGameFP, gameNumber,
12473                         lastLoadGameTitle, lastLoadGameUseList);
12474     }
12475 }
12476
12477 int keys[EmptySquare+1];
12478
12479 int
12480 PositionMatches (Board b1, Board b2)
12481 {
12482     int r, f, sum=0;
12483     switch(appData.searchMode) {
12484         case 1: return CompareWithRights(b1, b2);
12485         case 2:
12486             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12487                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12488             }
12489             return TRUE;
12490         case 3:
12491             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12492               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12493                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12494             }
12495             return sum==0;
12496         case 4:
12497             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12498                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12499             }
12500             return sum==0;
12501     }
12502     return TRUE;
12503 }
12504
12505 #define Q_PROMO  4
12506 #define Q_EP     3
12507 #define Q_BCASTL 2
12508 #define Q_WCASTL 1
12509
12510 int pieceList[256], quickBoard[256];
12511 ChessSquare pieceType[256] = { EmptySquare };
12512 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12513 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12514 int soughtTotal, turn;
12515 Boolean epOK, flipSearch;
12516
12517 typedef struct {
12518     unsigned char piece, to;
12519 } Move;
12520
12521 #define DSIZE (250000)
12522
12523 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12524 Move *moveDatabase = initialSpace;
12525 unsigned int movePtr, dataSize = DSIZE;
12526
12527 int
12528 MakePieceList (Board board, int *counts)
12529 {
12530     int r, f, n=Q_PROMO, total=0;
12531     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12532     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12533         int sq = f + (r<<4);
12534         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12535             quickBoard[sq] = ++n;
12536             pieceList[n] = sq;
12537             pieceType[n] = board[r][f];
12538             counts[board[r][f]]++;
12539             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12540             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12541             total++;
12542         }
12543     }
12544     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12545     return total;
12546 }
12547
12548 void
12549 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12550 {
12551     int sq = fromX + (fromY<<4);
12552     int piece = quickBoard[sq], rook;
12553     quickBoard[sq] = 0;
12554     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12555     if(piece == pieceList[1] && fromY == toY) {
12556       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12557         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12558         moveDatabase[movePtr++].piece = Q_WCASTL;
12559         quickBoard[sq] = piece;
12560         piece = quickBoard[from]; quickBoard[from] = 0;
12561         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12562       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12563         quickBoard[sq] = 0; // remove Rook
12564         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12565         moveDatabase[movePtr++].piece = Q_WCASTL;
12566         quickBoard[sq] = pieceList[1]; // put King
12567         piece = rook;
12568         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12569       }
12570     } else
12571     if(piece == pieceList[2] && fromY == toY) {
12572       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12573         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12574         moveDatabase[movePtr++].piece = Q_BCASTL;
12575         quickBoard[sq] = piece;
12576         piece = quickBoard[from]; quickBoard[from] = 0;
12577         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12578       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12579         quickBoard[sq] = 0; // remove Rook
12580         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12581         moveDatabase[movePtr++].piece = Q_BCASTL;
12582         quickBoard[sq] = pieceList[2]; // put King
12583         piece = rook;
12584         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12585       }
12586     } else
12587     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12588         quickBoard[(fromY<<4)+toX] = 0;
12589         moveDatabase[movePtr].piece = Q_EP;
12590         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12591         moveDatabase[movePtr].to = sq;
12592     } else
12593     if(promoPiece != pieceType[piece]) {
12594         moveDatabase[movePtr++].piece = Q_PROMO;
12595         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12596     }
12597     moveDatabase[movePtr].piece = piece;
12598     quickBoard[sq] = piece;
12599     movePtr++;
12600 }
12601
12602 int
12603 PackGame (Board board)
12604 {
12605     Move *newSpace = NULL;
12606     moveDatabase[movePtr].piece = 0; // terminate previous game
12607     if(movePtr > dataSize) {
12608         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12609         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12610         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12611         if(newSpace) {
12612             int i;
12613             Move *p = moveDatabase, *q = newSpace;
12614             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12615             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12616             moveDatabase = newSpace;
12617         } else { // calloc failed, we must be out of memory. Too bad...
12618             dataSize = 0; // prevent calloc events for all subsequent games
12619             return 0;     // and signal this one isn't cached
12620         }
12621     }
12622     movePtr++;
12623     MakePieceList(board, counts);
12624     return movePtr;
12625 }
12626
12627 int
12628 QuickCompare (Board board, int *minCounts, int *maxCounts)
12629 {   // compare according to search mode
12630     int r, f;
12631     switch(appData.searchMode)
12632     {
12633       case 1: // exact position match
12634         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12635         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12636             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12637         }
12638         break;
12639       case 2: // can have extra material on empty squares
12640         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12641             if(board[r][f] == EmptySquare) continue;
12642             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12643         }
12644         break;
12645       case 3: // material with exact Pawn structure
12646         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12647             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12648             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12649         } // fall through to material comparison
12650       case 4: // exact material
12651         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12652         break;
12653       case 6: // material range with given imbalance
12654         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12655         // fall through to range comparison
12656       case 5: // material range
12657         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12658     }
12659     return TRUE;
12660 }
12661
12662 int
12663 QuickScan (Board board, Move *move)
12664 {   // reconstruct game,and compare all positions in it
12665     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12666     do {
12667         int piece = move->piece;
12668         int to = move->to, from = pieceList[piece];
12669         if(found < 0) { // if already found just scan to game end for final piece count
12670           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12671            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12672            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12673                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12674             ) {
12675             static int lastCounts[EmptySquare+1];
12676             int i;
12677             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12678             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12679           } else stretch = 0;
12680           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12681           if(found >= 0 && !appData.minPieces) return found;
12682         }
12683         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12684           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12685           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12686             piece = (++move)->piece;
12687             from = pieceList[piece];
12688             counts[pieceType[piece]]--;
12689             pieceType[piece] = (ChessSquare) move->to;
12690             counts[move->to]++;
12691           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12692             counts[pieceType[quickBoard[to]]]--;
12693             quickBoard[to] = 0; total--;
12694             move++;
12695             continue;
12696           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12697             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12698             from  = pieceList[piece]; // so this must be King
12699             quickBoard[from] = 0;
12700             pieceList[piece] = to;
12701             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12702             quickBoard[from] = 0; // rook
12703             quickBoard[to] = piece;
12704             to = move->to; piece = move->piece;
12705             goto aftercastle;
12706           }
12707         }
12708         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12709         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12710         quickBoard[from] = 0;
12711       aftercastle:
12712         quickBoard[to] = piece;
12713         pieceList[piece] = to;
12714         cnt++; turn ^= 3;
12715         move++;
12716     } while(1);
12717 }
12718
12719 void
12720 InitSearch ()
12721 {
12722     int r, f;
12723     flipSearch = FALSE;
12724     CopyBoard(soughtBoard, boards[currentMove]);
12725     soughtTotal = MakePieceList(soughtBoard, maxSought);
12726     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12727     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12728     CopyBoard(reverseBoard, boards[currentMove]);
12729     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12730         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12731         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12732         reverseBoard[r][f] = piece;
12733     }
12734     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12735     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12736     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12737                  || (boards[currentMove][CASTLING][2] == NoRights ||
12738                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12739                  && (boards[currentMove][CASTLING][5] == NoRights ||
12740                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12741       ) {
12742         flipSearch = TRUE;
12743         CopyBoard(flipBoard, soughtBoard);
12744         CopyBoard(rotateBoard, reverseBoard);
12745         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12746             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12747             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12748         }
12749     }
12750     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12751     if(appData.searchMode >= 5) {
12752         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12753         MakePieceList(soughtBoard, minSought);
12754         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12755     }
12756     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12757         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12758 }
12759
12760 GameInfo dummyInfo;
12761 static int creatingBook;
12762
12763 int
12764 GameContainsPosition (FILE *f, ListGame *lg)
12765 {
12766     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12767     int fromX, fromY, toX, toY;
12768     char promoChar;
12769     static int initDone=FALSE;
12770
12771     // weed out games based on numerical tag comparison
12772     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12773     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12774     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12775     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12776     if(!initDone) {
12777         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12778         initDone = TRUE;
12779     }
12780     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12781     else CopyBoard(boards[scratch], initialPosition); // default start position
12782     if(lg->moves) {
12783         turn = btm + 1;
12784         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12785         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12786     }
12787     if(btm) plyNr++;
12788     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12789     fseek(f, lg->offset, 0);
12790     yynewfile(f);
12791     while(1) {
12792         yyboardindex = scratch;
12793         quickFlag = plyNr+1;
12794         next = Myylex();
12795         quickFlag = 0;
12796         switch(next) {
12797             case PGNTag:
12798                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12799             default:
12800                 continue;
12801
12802             case XBoardGame:
12803             case GNUChessGame:
12804                 if(plyNr) return -1; // after we have seen moves, this is for new game
12805               continue;
12806
12807             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12808             case ImpossibleMove:
12809             case WhiteWins: // game ends here with these four
12810             case BlackWins:
12811             case GameIsDrawn:
12812             case GameUnfinished:
12813                 return -1;
12814
12815             case IllegalMove:
12816                 if(appData.testLegality) return -1;
12817             case WhiteCapturesEnPassant:
12818             case BlackCapturesEnPassant:
12819             case WhitePromotion:
12820             case BlackPromotion:
12821             case WhiteNonPromotion:
12822             case BlackNonPromotion:
12823             case NormalMove:
12824             case FirstLeg:
12825             case WhiteKingSideCastle:
12826             case WhiteQueenSideCastle:
12827             case BlackKingSideCastle:
12828             case BlackQueenSideCastle:
12829             case WhiteKingSideCastleWild:
12830             case WhiteQueenSideCastleWild:
12831             case BlackKingSideCastleWild:
12832             case BlackQueenSideCastleWild:
12833             case WhiteHSideCastleFR:
12834             case WhiteASideCastleFR:
12835             case BlackHSideCastleFR:
12836             case BlackASideCastleFR:
12837                 fromX = currentMoveString[0] - AAA;
12838                 fromY = currentMoveString[1] - ONE;
12839                 toX = currentMoveString[2] - AAA;
12840                 toY = currentMoveString[3] - ONE;
12841                 promoChar = currentMoveString[4];
12842                 break;
12843             case WhiteDrop:
12844             case BlackDrop:
12845                 fromX = next == WhiteDrop ?
12846                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12847                   (int) CharToPiece(ToLower(currentMoveString[0]));
12848                 fromY = DROP_RANK;
12849                 toX = currentMoveString[2] - AAA;
12850                 toY = currentMoveString[3] - ONE;
12851                 promoChar = 0;
12852                 break;
12853         }
12854         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12855         plyNr++;
12856         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12857         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12858         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12859         if(appData.findMirror) {
12860             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12861             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12862         }
12863     }
12864 }
12865
12866 /* Load the nth game from open file f */
12867 int
12868 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12869 {
12870     ChessMove cm;
12871     char buf[MSG_SIZ];
12872     int gn = gameNumber;
12873     ListGame *lg = NULL;
12874     int numPGNTags = 0;
12875     int err, pos = -1;
12876     GameMode oldGameMode;
12877     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12878     char oldName[MSG_SIZ];
12879
12880     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12881
12882     if (appData.debugMode)
12883         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12884
12885     if (gameMode == Training )
12886         SetTrainingModeOff();
12887
12888     oldGameMode = gameMode;
12889     if (gameMode != BeginningOfGame) {
12890       Reset(FALSE, TRUE);
12891     }
12892     killX = killY = -1; // [HGM] lion: in case we did not Reset
12893
12894     gameFileFP = f;
12895     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12896         fclose(lastLoadGameFP);
12897     }
12898
12899     if (useList) {
12900         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12901
12902         if (lg) {
12903             fseek(f, lg->offset, 0);
12904             GameListHighlight(gameNumber);
12905             pos = lg->position;
12906             gn = 1;
12907         }
12908         else {
12909             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12910               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12911             else
12912             DisplayError(_("Game number out of range"), 0);
12913             return FALSE;
12914         }
12915     } else {
12916         GameListDestroy();
12917         if (fseek(f, 0, 0) == -1) {
12918             if (f == lastLoadGameFP ?
12919                 gameNumber == lastLoadGameNumber + 1 :
12920                 gameNumber == 1) {
12921                 gn = 1;
12922             } else {
12923                 DisplayError(_("Can't seek on game file"), 0);
12924                 return FALSE;
12925             }
12926         }
12927     }
12928     lastLoadGameFP = f;
12929     lastLoadGameNumber = gameNumber;
12930     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12931     lastLoadGameUseList = useList;
12932
12933     yynewfile(f);
12934
12935     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12936       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12937                 lg->gameInfo.black);
12938             DisplayTitle(buf);
12939     } else if (*title != NULLCHAR) {
12940         if (gameNumber > 1) {
12941           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12942             DisplayTitle(buf);
12943         } else {
12944             DisplayTitle(title);
12945         }
12946     }
12947
12948     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12949         gameMode = PlayFromGameFile;
12950         ModeHighlight();
12951     }
12952
12953     currentMove = forwardMostMove = backwardMostMove = 0;
12954     CopyBoard(boards[0], initialPosition);
12955     StopClocks();
12956
12957     /*
12958      * Skip the first gn-1 games in the file.
12959      * Also skip over anything that precedes an identifiable
12960      * start of game marker, to avoid being confused by
12961      * garbage at the start of the file.  Currently
12962      * recognized start of game markers are the move number "1",
12963      * the pattern "gnuchess .* game", the pattern
12964      * "^[#;%] [^ ]* game file", and a PGN tag block.
12965      * A game that starts with one of the latter two patterns
12966      * will also have a move number 1, possibly
12967      * following a position diagram.
12968      * 5-4-02: Let's try being more lenient and allowing a game to
12969      * start with an unnumbered move.  Does that break anything?
12970      */
12971     cm = lastLoadGameStart = EndOfFile;
12972     while (gn > 0) {
12973         yyboardindex = forwardMostMove;
12974         cm = (ChessMove) Myylex();
12975         switch (cm) {
12976           case EndOfFile:
12977             if (cmailMsgLoaded) {
12978                 nCmailGames = CMAIL_MAX_GAMES - gn;
12979             } else {
12980                 Reset(TRUE, TRUE);
12981                 DisplayError(_("Game not found in file"), 0);
12982             }
12983             return FALSE;
12984
12985           case GNUChessGame:
12986           case XBoardGame:
12987             gn--;
12988             lastLoadGameStart = cm;
12989             break;
12990
12991           case MoveNumberOne:
12992             switch (lastLoadGameStart) {
12993               case GNUChessGame:
12994               case XBoardGame:
12995               case PGNTag:
12996                 break;
12997               case MoveNumberOne:
12998               case EndOfFile:
12999                 gn--;           /* count this game */
13000                 lastLoadGameStart = cm;
13001                 break;
13002               default:
13003                 /* impossible */
13004                 break;
13005             }
13006             break;
13007
13008           case PGNTag:
13009             switch (lastLoadGameStart) {
13010               case GNUChessGame:
13011               case PGNTag:
13012               case MoveNumberOne:
13013               case EndOfFile:
13014                 gn--;           /* count this game */
13015                 lastLoadGameStart = cm;
13016                 break;
13017               case XBoardGame:
13018                 lastLoadGameStart = cm; /* game counted already */
13019                 break;
13020               default:
13021                 /* impossible */
13022                 break;
13023             }
13024             if (gn > 0) {
13025                 do {
13026                     yyboardindex = forwardMostMove;
13027                     cm = (ChessMove) Myylex();
13028                 } while (cm == PGNTag || cm == Comment);
13029             }
13030             break;
13031
13032           case WhiteWins:
13033           case BlackWins:
13034           case GameIsDrawn:
13035             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13036                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13037                     != CMAIL_OLD_RESULT) {
13038                     nCmailResults ++ ;
13039                     cmailResult[  CMAIL_MAX_GAMES
13040                                 - gn - 1] = CMAIL_OLD_RESULT;
13041                 }
13042             }
13043             break;
13044
13045           case NormalMove:
13046           case FirstLeg:
13047             /* Only a NormalMove can be at the start of a game
13048              * without a position diagram. */
13049             if (lastLoadGameStart == EndOfFile ) {
13050               gn--;
13051               lastLoadGameStart = MoveNumberOne;
13052             }
13053             break;
13054
13055           default:
13056             break;
13057         }
13058     }
13059
13060     if (appData.debugMode)
13061       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13062
13063     if (cm == XBoardGame) {
13064         /* Skip any header junk before position diagram and/or move 1 */
13065         for (;;) {
13066             yyboardindex = forwardMostMove;
13067             cm = (ChessMove) Myylex();
13068
13069             if (cm == EndOfFile ||
13070                 cm == GNUChessGame || cm == XBoardGame) {
13071                 /* Empty game; pretend end-of-file and handle later */
13072                 cm = EndOfFile;
13073                 break;
13074             }
13075
13076             if (cm == MoveNumberOne || cm == PositionDiagram ||
13077                 cm == PGNTag || cm == Comment)
13078               break;
13079         }
13080     } else if (cm == GNUChessGame) {
13081         if (gameInfo.event != NULL) {
13082             free(gameInfo.event);
13083         }
13084         gameInfo.event = StrSave(yy_text);
13085     }
13086
13087     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13088     while (cm == PGNTag) {
13089         if (appData.debugMode)
13090           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13091         err = ParsePGNTag(yy_text, &gameInfo);
13092         if (!err) numPGNTags++;
13093
13094         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13095         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13096             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13097             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13098             InitPosition(TRUE);
13099             oldVariant = gameInfo.variant;
13100             if (appData.debugMode)
13101               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13102         }
13103
13104
13105         if (gameInfo.fen != NULL) {
13106           Board initial_position;
13107           startedFromSetupPosition = TRUE;
13108           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13109             Reset(TRUE, TRUE);
13110             DisplayError(_("Bad FEN position in file"), 0);
13111             return FALSE;
13112           }
13113           CopyBoard(boards[0], initial_position);
13114           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13115             CopyBoard(initialPosition, initial_position);
13116           if (blackPlaysFirst) {
13117             currentMove = forwardMostMove = backwardMostMove = 1;
13118             CopyBoard(boards[1], initial_position);
13119             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13120             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13121             timeRemaining[0][1] = whiteTimeRemaining;
13122             timeRemaining[1][1] = blackTimeRemaining;
13123             if (commentList[0] != NULL) {
13124               commentList[1] = commentList[0];
13125               commentList[0] = NULL;
13126             }
13127           } else {
13128             currentMove = forwardMostMove = backwardMostMove = 0;
13129           }
13130           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13131           {   int i;
13132               initialRulePlies = FENrulePlies;
13133               for( i=0; i< nrCastlingRights; i++ )
13134                   initialRights[i] = initial_position[CASTLING][i];
13135           }
13136           yyboardindex = forwardMostMove;
13137           free(gameInfo.fen);
13138           gameInfo.fen = NULL;
13139         }
13140
13141         yyboardindex = forwardMostMove;
13142         cm = (ChessMove) Myylex();
13143
13144         /* Handle comments interspersed among the tags */
13145         while (cm == Comment) {
13146             char *p;
13147             if (appData.debugMode)
13148               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13149             p = yy_text;
13150             AppendComment(currentMove, p, FALSE);
13151             yyboardindex = forwardMostMove;
13152             cm = (ChessMove) Myylex();
13153         }
13154     }
13155
13156     /* don't rely on existence of Event tag since if game was
13157      * pasted from clipboard the Event tag may not exist
13158      */
13159     if (numPGNTags > 0){
13160         char *tags;
13161         if (gameInfo.variant == VariantNormal) {
13162           VariantClass v = StringToVariant(gameInfo.event);
13163           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13164           if(v < VariantShogi) gameInfo.variant = v;
13165         }
13166         if (!matchMode) {
13167           if( appData.autoDisplayTags ) {
13168             tags = PGNTags(&gameInfo);
13169             TagsPopUp(tags, CmailMsg());
13170             free(tags);
13171           }
13172         }
13173     } else {
13174         /* Make something up, but don't display it now */
13175         SetGameInfo();
13176         TagsPopDown();
13177     }
13178
13179     if (cm == PositionDiagram) {
13180         int i, j;
13181         char *p;
13182         Board initial_position;
13183
13184         if (appData.debugMode)
13185           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13186
13187         if (!startedFromSetupPosition) {
13188             p = yy_text;
13189             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13190               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13191                 switch (*p) {
13192                   case '{':
13193                   case '[':
13194                   case '-':
13195                   case ' ':
13196                   case '\t':
13197                   case '\n':
13198                   case '\r':
13199                     break;
13200                   default:
13201                     initial_position[i][j++] = CharToPiece(*p);
13202                     break;
13203                 }
13204             while (*p == ' ' || *p == '\t' ||
13205                    *p == '\n' || *p == '\r') p++;
13206
13207             if (strncmp(p, "black", strlen("black"))==0)
13208               blackPlaysFirst = TRUE;
13209             else
13210               blackPlaysFirst = FALSE;
13211             startedFromSetupPosition = TRUE;
13212
13213             CopyBoard(boards[0], initial_position);
13214             if (blackPlaysFirst) {
13215                 currentMove = forwardMostMove = backwardMostMove = 1;
13216                 CopyBoard(boards[1], initial_position);
13217                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13218                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13219                 timeRemaining[0][1] = whiteTimeRemaining;
13220                 timeRemaining[1][1] = blackTimeRemaining;
13221                 if (commentList[0] != NULL) {
13222                     commentList[1] = commentList[0];
13223                     commentList[0] = NULL;
13224                 }
13225             } else {
13226                 currentMove = forwardMostMove = backwardMostMove = 0;
13227             }
13228         }
13229         yyboardindex = forwardMostMove;
13230         cm = (ChessMove) Myylex();
13231     }
13232
13233   if(!creatingBook) {
13234     if (first.pr == NoProc) {
13235         StartChessProgram(&first);
13236     }
13237     InitChessProgram(&first, FALSE);
13238     if(gameInfo.variant == VariantUnknown && *oldName) {
13239         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13240         gameInfo.variant = v;
13241     }
13242     SendToProgram("force\n", &first);
13243     if (startedFromSetupPosition) {
13244         SendBoard(&first, forwardMostMove);
13245     if (appData.debugMode) {
13246         fprintf(debugFP, "Load Game\n");
13247     }
13248         DisplayBothClocks();
13249     }
13250   }
13251
13252     /* [HGM] server: flag to write setup moves in broadcast file as one */
13253     loadFlag = appData.suppressLoadMoves;
13254
13255     while (cm == Comment) {
13256         char *p;
13257         if (appData.debugMode)
13258           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13259         p = yy_text;
13260         AppendComment(currentMove, p, FALSE);
13261         yyboardindex = forwardMostMove;
13262         cm = (ChessMove) Myylex();
13263     }
13264
13265     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13266         cm == WhiteWins || cm == BlackWins ||
13267         cm == GameIsDrawn || cm == GameUnfinished) {
13268         DisplayMessage("", _("No moves in game"));
13269         if (cmailMsgLoaded) {
13270             if (appData.debugMode)
13271               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13272             ClearHighlights();
13273             flipView = FALSE;
13274         }
13275         DrawPosition(FALSE, boards[currentMove]);
13276         DisplayBothClocks();
13277         gameMode = EditGame;
13278         ModeHighlight();
13279         gameFileFP = NULL;
13280         cmailOldMove = 0;
13281         return TRUE;
13282     }
13283
13284     // [HGM] PV info: routine tests if comment empty
13285     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13286         DisplayComment(currentMove - 1, commentList[currentMove]);
13287     }
13288     if (!matchMode && appData.timeDelay != 0)
13289       DrawPosition(FALSE, boards[currentMove]);
13290
13291     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13292       programStats.ok_to_send = 1;
13293     }
13294
13295     /* if the first token after the PGN tags is a move
13296      * and not move number 1, retrieve it from the parser
13297      */
13298     if (cm != MoveNumberOne)
13299         LoadGameOneMove(cm);
13300
13301     /* load the remaining moves from the file */
13302     while (LoadGameOneMove(EndOfFile)) {
13303       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13304       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13305     }
13306
13307     /* rewind to the start of the game */
13308     currentMove = backwardMostMove;
13309
13310     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13311
13312     if (oldGameMode == AnalyzeFile) {
13313       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13314       AnalyzeFileEvent();
13315     } else
13316     if (oldGameMode == AnalyzeMode) {
13317       AnalyzeFileEvent();
13318     }
13319
13320     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13321         long int w, b; // [HGM] adjourn: restore saved clock times
13322         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13323         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13324             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13325             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13326         }
13327     }
13328
13329     if(creatingBook) return TRUE;
13330     if (!matchMode && pos > 0) {
13331         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13332     } else
13333     if (matchMode || appData.timeDelay == 0) {
13334       ToEndEvent();
13335     } else if (appData.timeDelay > 0) {
13336       AutoPlayGameLoop();
13337     }
13338
13339     if (appData.debugMode)
13340         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13341
13342     loadFlag = 0; /* [HGM] true game starts */
13343     return TRUE;
13344 }
13345
13346 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13347 int
13348 ReloadPosition (int offset)
13349 {
13350     int positionNumber = lastLoadPositionNumber + offset;
13351     if (lastLoadPositionFP == NULL) {
13352         DisplayError(_("No position has been loaded yet"), 0);
13353         return FALSE;
13354     }
13355     if (positionNumber <= 0) {
13356         DisplayError(_("Can't back up any further"), 0);
13357         return FALSE;
13358     }
13359     return LoadPosition(lastLoadPositionFP, positionNumber,
13360                         lastLoadPositionTitle);
13361 }
13362
13363 /* Load the nth position from the given file */
13364 int
13365 LoadPositionFromFile (char *filename, int n, char *title)
13366 {
13367     FILE *f;
13368     char buf[MSG_SIZ];
13369
13370     if (strcmp(filename, "-") == 0) {
13371         return LoadPosition(stdin, n, "stdin");
13372     } else {
13373         f = fopen(filename, "rb");
13374         if (f == NULL) {
13375             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13376             DisplayError(buf, errno);
13377             return FALSE;
13378         } else {
13379             return LoadPosition(f, n, title);
13380         }
13381     }
13382 }
13383
13384 /* Load the nth position from the given open file, and close it */
13385 int
13386 LoadPosition (FILE *f, int positionNumber, char *title)
13387 {
13388     char *p, line[MSG_SIZ];
13389     Board initial_position;
13390     int i, j, fenMode, pn;
13391
13392     if (gameMode == Training )
13393         SetTrainingModeOff();
13394
13395     if (gameMode != BeginningOfGame) {
13396         Reset(FALSE, TRUE);
13397     }
13398     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13399         fclose(lastLoadPositionFP);
13400     }
13401     if (positionNumber == 0) positionNumber = 1;
13402     lastLoadPositionFP = f;
13403     lastLoadPositionNumber = positionNumber;
13404     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13405     if (first.pr == NoProc && !appData.noChessProgram) {
13406       StartChessProgram(&first);
13407       InitChessProgram(&first, FALSE);
13408     }
13409     pn = positionNumber;
13410     if (positionNumber < 0) {
13411         /* Negative position number means to seek to that byte offset */
13412         if (fseek(f, -positionNumber, 0) == -1) {
13413             DisplayError(_("Can't seek on position file"), 0);
13414             return FALSE;
13415         };
13416         pn = 1;
13417     } else {
13418         if (fseek(f, 0, 0) == -1) {
13419             if (f == lastLoadPositionFP ?
13420                 positionNumber == lastLoadPositionNumber + 1 :
13421                 positionNumber == 1) {
13422                 pn = 1;
13423             } else {
13424                 DisplayError(_("Can't seek on position file"), 0);
13425                 return FALSE;
13426             }
13427         }
13428     }
13429     /* See if this file is FEN or old-style xboard */
13430     if (fgets(line, MSG_SIZ, f) == NULL) {
13431         DisplayError(_("Position not found in file"), 0);
13432         return FALSE;
13433     }
13434     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13435     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13436
13437     if (pn >= 2) {
13438         if (fenMode || line[0] == '#') pn--;
13439         while (pn > 0) {
13440             /* skip positions before number pn */
13441             if (fgets(line, MSG_SIZ, f) == NULL) {
13442                 Reset(TRUE, TRUE);
13443                 DisplayError(_("Position not found in file"), 0);
13444                 return FALSE;
13445             }
13446             if (fenMode || line[0] == '#') pn--;
13447         }
13448     }
13449
13450     if (fenMode) {
13451         char *p;
13452         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13453             DisplayError(_("Bad FEN position in file"), 0);
13454             return FALSE;
13455         }
13456         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13457             sscanf(p+3, "%s", bestMove);
13458         } else *bestMove = NULLCHAR;
13459     } else {
13460         (void) fgets(line, MSG_SIZ, f);
13461         (void) fgets(line, MSG_SIZ, f);
13462
13463         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13464             (void) fgets(line, MSG_SIZ, f);
13465             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13466                 if (*p == ' ')
13467                   continue;
13468                 initial_position[i][j++] = CharToPiece(*p);
13469             }
13470         }
13471
13472         blackPlaysFirst = FALSE;
13473         if (!feof(f)) {
13474             (void) fgets(line, MSG_SIZ, f);
13475             if (strncmp(line, "black", strlen("black"))==0)
13476               blackPlaysFirst = TRUE;
13477         }
13478     }
13479     startedFromSetupPosition = TRUE;
13480
13481     CopyBoard(boards[0], initial_position);
13482     if (blackPlaysFirst) {
13483         currentMove = forwardMostMove = backwardMostMove = 1;
13484         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13485         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13486         CopyBoard(boards[1], initial_position);
13487         DisplayMessage("", _("Black to play"));
13488     } else {
13489         currentMove = forwardMostMove = backwardMostMove = 0;
13490         DisplayMessage("", _("White to play"));
13491     }
13492     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13493     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13494         SendToProgram("force\n", &first);
13495         SendBoard(&first, forwardMostMove);
13496     }
13497     if (appData.debugMode) {
13498 int i, j;
13499   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13500   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13501         fprintf(debugFP, "Load Position\n");
13502     }
13503
13504     if (positionNumber > 1) {
13505       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13506         DisplayTitle(line);
13507     } else {
13508         DisplayTitle(title);
13509     }
13510     gameMode = EditGame;
13511     ModeHighlight();
13512     ResetClocks();
13513     timeRemaining[0][1] = whiteTimeRemaining;
13514     timeRemaining[1][1] = blackTimeRemaining;
13515     DrawPosition(FALSE, boards[currentMove]);
13516
13517     return TRUE;
13518 }
13519
13520
13521 void
13522 CopyPlayerNameIntoFileName (char **dest, char *src)
13523 {
13524     while (*src != NULLCHAR && *src != ',') {
13525         if (*src == ' ') {
13526             *(*dest)++ = '_';
13527             src++;
13528         } else {
13529             *(*dest)++ = *src++;
13530         }
13531     }
13532 }
13533
13534 char *
13535 DefaultFileName (char *ext)
13536 {
13537     static char def[MSG_SIZ];
13538     char *p;
13539
13540     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13541         p = def;
13542         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13543         *p++ = '-';
13544         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13545         *p++ = '.';
13546         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13547     } else {
13548         def[0] = NULLCHAR;
13549     }
13550     return def;
13551 }
13552
13553 /* Save the current game to the given file */
13554 int
13555 SaveGameToFile (char *filename, int append)
13556 {
13557     FILE *f;
13558     char buf[MSG_SIZ];
13559     int result, i, t,tot=0;
13560
13561     if (strcmp(filename, "-") == 0) {
13562         return SaveGame(stdout, 0, NULL);
13563     } else {
13564         for(i=0; i<10; i++) { // upto 10 tries
13565              f = fopen(filename, append ? "a" : "w");
13566              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13567              if(f || errno != 13) break;
13568              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13569              tot += t;
13570         }
13571         if (f == NULL) {
13572             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13573             DisplayError(buf, errno);
13574             return FALSE;
13575         } else {
13576             safeStrCpy(buf, lastMsg, MSG_SIZ);
13577             DisplayMessage(_("Waiting for access to save file"), "");
13578             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13579             DisplayMessage(_("Saving game"), "");
13580             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13581             result = SaveGame(f, 0, NULL);
13582             DisplayMessage(buf, "");
13583             return result;
13584         }
13585     }
13586 }
13587
13588 char *
13589 SavePart (char *str)
13590 {
13591     static char buf[MSG_SIZ];
13592     char *p;
13593
13594     p = strchr(str, ' ');
13595     if (p == NULL) return str;
13596     strncpy(buf, str, p - str);
13597     buf[p - str] = NULLCHAR;
13598     return buf;
13599 }
13600
13601 #define PGN_MAX_LINE 75
13602
13603 #define PGN_SIDE_WHITE  0
13604 #define PGN_SIDE_BLACK  1
13605
13606 static int
13607 FindFirstMoveOutOfBook (int side)
13608 {
13609     int result = -1;
13610
13611     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13612         int index = backwardMostMove;
13613         int has_book_hit = 0;
13614
13615         if( (index % 2) != side ) {
13616             index++;
13617         }
13618
13619         while( index < forwardMostMove ) {
13620             /* Check to see if engine is in book */
13621             int depth = pvInfoList[index].depth;
13622             int score = pvInfoList[index].score;
13623             int in_book = 0;
13624
13625             if( depth <= 2 ) {
13626                 in_book = 1;
13627             }
13628             else if( score == 0 && depth == 63 ) {
13629                 in_book = 1; /* Zappa */
13630             }
13631             else if( score == 2 && depth == 99 ) {
13632                 in_book = 1; /* Abrok */
13633             }
13634
13635             has_book_hit += in_book;
13636
13637             if( ! in_book ) {
13638                 result = index;
13639
13640                 break;
13641             }
13642
13643             index += 2;
13644         }
13645     }
13646
13647     return result;
13648 }
13649
13650 void
13651 GetOutOfBookInfo (char * buf)
13652 {
13653     int oob[2];
13654     int i;
13655     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13656
13657     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13658     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13659
13660     *buf = '\0';
13661
13662     if( oob[0] >= 0 || oob[1] >= 0 ) {
13663         for( i=0; i<2; i++ ) {
13664             int idx = oob[i];
13665
13666             if( idx >= 0 ) {
13667                 if( i > 0 && oob[0] >= 0 ) {
13668                     strcat( buf, "   " );
13669                 }
13670
13671                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13672                 sprintf( buf+strlen(buf), "%s%.2f",
13673                     pvInfoList[idx].score >= 0 ? "+" : "",
13674                     pvInfoList[idx].score / 100.0 );
13675             }
13676         }
13677     }
13678 }
13679
13680 /* Save game in PGN style */
13681 static void
13682 SaveGamePGN2 (FILE *f)
13683 {
13684     int i, offset, linelen, newblock;
13685 //    char *movetext;
13686     char numtext[32];
13687     int movelen, numlen, blank;
13688     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13689
13690     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13691
13692     PrintPGNTags(f, &gameInfo);
13693
13694     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13695
13696     if (backwardMostMove > 0 || startedFromSetupPosition) {
13697         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13698         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13699         fprintf(f, "\n{--------------\n");
13700         PrintPosition(f, backwardMostMove);
13701         fprintf(f, "--------------}\n");
13702         free(fen);
13703     }
13704     else {
13705         /* [AS] Out of book annotation */
13706         if( appData.saveOutOfBookInfo ) {
13707             char buf[64];
13708
13709             GetOutOfBookInfo( buf );
13710
13711             if( buf[0] != '\0' ) {
13712                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13713             }
13714         }
13715
13716         fprintf(f, "\n");
13717     }
13718
13719     i = backwardMostMove;
13720     linelen = 0;
13721     newblock = TRUE;
13722
13723     while (i < forwardMostMove) {
13724         /* Print comments preceding this move */
13725         if (commentList[i] != NULL) {
13726             if (linelen > 0) fprintf(f, "\n");
13727             fprintf(f, "%s", commentList[i]);
13728             linelen = 0;
13729             newblock = TRUE;
13730         }
13731
13732         /* Format move number */
13733         if ((i % 2) == 0)
13734           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13735         else
13736           if (newblock)
13737             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13738           else
13739             numtext[0] = NULLCHAR;
13740
13741         numlen = strlen(numtext);
13742         newblock = FALSE;
13743
13744         /* Print move number */
13745         blank = linelen > 0 && numlen > 0;
13746         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13747             fprintf(f, "\n");
13748             linelen = 0;
13749             blank = 0;
13750         }
13751         if (blank) {
13752             fprintf(f, " ");
13753             linelen++;
13754         }
13755         fprintf(f, "%s", numtext);
13756         linelen += numlen;
13757
13758         /* Get move */
13759         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13760         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13761
13762         /* Print move */
13763         blank = linelen > 0 && movelen > 0;
13764         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13765             fprintf(f, "\n");
13766             linelen = 0;
13767             blank = 0;
13768         }
13769         if (blank) {
13770             fprintf(f, " ");
13771             linelen++;
13772         }
13773         fprintf(f, "%s", move_buffer);
13774         linelen += movelen;
13775
13776         /* [AS] Add PV info if present */
13777         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13778             /* [HGM] add time */
13779             char buf[MSG_SIZ]; int seconds;
13780
13781             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13782
13783             if( seconds <= 0)
13784               buf[0] = 0;
13785             else
13786               if( seconds < 30 )
13787                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13788               else
13789                 {
13790                   seconds = (seconds + 4)/10; // round to full seconds
13791                   if( seconds < 60 )
13792                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13793                   else
13794                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13795                 }
13796
13797             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13798                       pvInfoList[i].score >= 0 ? "+" : "",
13799                       pvInfoList[i].score / 100.0,
13800                       pvInfoList[i].depth,
13801                       buf );
13802
13803             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13804
13805             /* Print score/depth */
13806             blank = linelen > 0 && movelen > 0;
13807             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13808                 fprintf(f, "\n");
13809                 linelen = 0;
13810                 blank = 0;
13811             }
13812             if (blank) {
13813                 fprintf(f, " ");
13814                 linelen++;
13815             }
13816             fprintf(f, "%s", move_buffer);
13817             linelen += movelen;
13818         }
13819
13820         i++;
13821     }
13822
13823     /* Start a new line */
13824     if (linelen > 0) fprintf(f, "\n");
13825
13826     /* Print comments after last move */
13827     if (commentList[i] != NULL) {
13828         fprintf(f, "%s\n", commentList[i]);
13829     }
13830
13831     /* Print result */
13832     if (gameInfo.resultDetails != NULL &&
13833         gameInfo.resultDetails[0] != NULLCHAR) {
13834         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13835         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13836            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13837             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13838         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13839     } else {
13840         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13841     }
13842 }
13843
13844 /* Save game in PGN style and close the file */
13845 int
13846 SaveGamePGN (FILE *f)
13847 {
13848     SaveGamePGN2(f);
13849     fclose(f);
13850     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13851     return TRUE;
13852 }
13853
13854 /* Save game in old style and close the file */
13855 int
13856 SaveGameOldStyle (FILE *f)
13857 {
13858     int i, offset;
13859     time_t tm;
13860
13861     tm = time((time_t *) NULL);
13862
13863     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13864     PrintOpponents(f);
13865
13866     if (backwardMostMove > 0 || startedFromSetupPosition) {
13867         fprintf(f, "\n[--------------\n");
13868         PrintPosition(f, backwardMostMove);
13869         fprintf(f, "--------------]\n");
13870     } else {
13871         fprintf(f, "\n");
13872     }
13873
13874     i = backwardMostMove;
13875     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13876
13877     while (i < forwardMostMove) {
13878         if (commentList[i] != NULL) {
13879             fprintf(f, "[%s]\n", commentList[i]);
13880         }
13881
13882         if ((i % 2) == 1) {
13883             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13884             i++;
13885         } else {
13886             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13887             i++;
13888             if (commentList[i] != NULL) {
13889                 fprintf(f, "\n");
13890                 continue;
13891             }
13892             if (i >= forwardMostMove) {
13893                 fprintf(f, "\n");
13894                 break;
13895             }
13896             fprintf(f, "%s\n", parseList[i]);
13897             i++;
13898         }
13899     }
13900
13901     if (commentList[i] != NULL) {
13902         fprintf(f, "[%s]\n", commentList[i]);
13903     }
13904
13905     /* This isn't really the old style, but it's close enough */
13906     if (gameInfo.resultDetails != NULL &&
13907         gameInfo.resultDetails[0] != NULLCHAR) {
13908         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13909                 gameInfo.resultDetails);
13910     } else {
13911         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13912     }
13913
13914     fclose(f);
13915     return TRUE;
13916 }
13917
13918 /* Save the current game to open file f and close the file */
13919 int
13920 SaveGame (FILE *f, int dummy, char *dummy2)
13921 {
13922     if (gameMode == EditPosition) EditPositionDone(TRUE);
13923     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13924     if (appData.oldSaveStyle)
13925       return SaveGameOldStyle(f);
13926     else
13927       return SaveGamePGN(f);
13928 }
13929
13930 /* Save the current position to the given file */
13931 int
13932 SavePositionToFile (char *filename)
13933 {
13934     FILE *f;
13935     char buf[MSG_SIZ];
13936
13937     if (strcmp(filename, "-") == 0) {
13938         return SavePosition(stdout, 0, NULL);
13939     } else {
13940         f = fopen(filename, "a");
13941         if (f == NULL) {
13942             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13943             DisplayError(buf, errno);
13944             return FALSE;
13945         } else {
13946             safeStrCpy(buf, lastMsg, MSG_SIZ);
13947             DisplayMessage(_("Waiting for access to save file"), "");
13948             flock(fileno(f), LOCK_EX); // [HGM] lock
13949             DisplayMessage(_("Saving position"), "");
13950             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13951             SavePosition(f, 0, NULL);
13952             DisplayMessage(buf, "");
13953             return TRUE;
13954         }
13955     }
13956 }
13957
13958 /* Save the current position to the given open file and close the file */
13959 int
13960 SavePosition (FILE *f, int dummy, char *dummy2)
13961 {
13962     time_t tm;
13963     char *fen;
13964
13965     if (gameMode == EditPosition) EditPositionDone(TRUE);
13966     if (appData.oldSaveStyle) {
13967         tm = time((time_t *) NULL);
13968
13969         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13970         PrintOpponents(f);
13971         fprintf(f, "[--------------\n");
13972         PrintPosition(f, currentMove);
13973         fprintf(f, "--------------]\n");
13974     } else {
13975         fen = PositionToFEN(currentMove, NULL, 1);
13976         fprintf(f, "%s\n", fen);
13977         free(fen);
13978     }
13979     fclose(f);
13980     return TRUE;
13981 }
13982
13983 void
13984 ReloadCmailMsgEvent (int unregister)
13985 {
13986 #if !WIN32
13987     static char *inFilename = NULL;
13988     static char *outFilename;
13989     int i;
13990     struct stat inbuf, outbuf;
13991     int status;
13992
13993     /* Any registered moves are unregistered if unregister is set, */
13994     /* i.e. invoked by the signal handler */
13995     if (unregister) {
13996         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13997             cmailMoveRegistered[i] = FALSE;
13998             if (cmailCommentList[i] != NULL) {
13999                 free(cmailCommentList[i]);
14000                 cmailCommentList[i] = NULL;
14001             }
14002         }
14003         nCmailMovesRegistered = 0;
14004     }
14005
14006     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14007         cmailResult[i] = CMAIL_NOT_RESULT;
14008     }
14009     nCmailResults = 0;
14010
14011     if (inFilename == NULL) {
14012         /* Because the filenames are static they only get malloced once  */
14013         /* and they never get freed                                      */
14014         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14015         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14016
14017         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14018         sprintf(outFilename, "%s.out", appData.cmailGameName);
14019     }
14020
14021     status = stat(outFilename, &outbuf);
14022     if (status < 0) {
14023         cmailMailedMove = FALSE;
14024     } else {
14025         status = stat(inFilename, &inbuf);
14026         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14027     }
14028
14029     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14030        counts the games, notes how each one terminated, etc.
14031
14032        It would be nice to remove this kludge and instead gather all
14033        the information while building the game list.  (And to keep it
14034        in the game list nodes instead of having a bunch of fixed-size
14035        parallel arrays.)  Note this will require getting each game's
14036        termination from the PGN tags, as the game list builder does
14037        not process the game moves.  --mann
14038        */
14039     cmailMsgLoaded = TRUE;
14040     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14041
14042     /* Load first game in the file or popup game menu */
14043     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14044
14045 #endif /* !WIN32 */
14046     return;
14047 }
14048
14049 int
14050 RegisterMove ()
14051 {
14052     FILE *f;
14053     char string[MSG_SIZ];
14054
14055     if (   cmailMailedMove
14056         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14057         return TRUE;            /* Allow free viewing  */
14058     }
14059
14060     /* Unregister move to ensure that we don't leave RegisterMove        */
14061     /* with the move registered when the conditions for registering no   */
14062     /* longer hold                                                       */
14063     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14064         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14065         nCmailMovesRegistered --;
14066
14067         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14068           {
14069               free(cmailCommentList[lastLoadGameNumber - 1]);
14070               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14071           }
14072     }
14073
14074     if (cmailOldMove == -1) {
14075         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14076         return FALSE;
14077     }
14078
14079     if (currentMove > cmailOldMove + 1) {
14080         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14081         return FALSE;
14082     }
14083
14084     if (currentMove < cmailOldMove) {
14085         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14086         return FALSE;
14087     }
14088
14089     if (forwardMostMove > currentMove) {
14090         /* Silently truncate extra moves */
14091         TruncateGame();
14092     }
14093
14094     if (   (currentMove == cmailOldMove + 1)
14095         || (   (currentMove == cmailOldMove)
14096             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14097                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14098         if (gameInfo.result != GameUnfinished) {
14099             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14100         }
14101
14102         if (commentList[currentMove] != NULL) {
14103             cmailCommentList[lastLoadGameNumber - 1]
14104               = StrSave(commentList[currentMove]);
14105         }
14106         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14107
14108         if (appData.debugMode)
14109           fprintf(debugFP, "Saving %s for game %d\n",
14110                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14111
14112         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14113
14114         f = fopen(string, "w");
14115         if (appData.oldSaveStyle) {
14116             SaveGameOldStyle(f); /* also closes the file */
14117
14118             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14119             f = fopen(string, "w");
14120             SavePosition(f, 0, NULL); /* also closes the file */
14121         } else {
14122             fprintf(f, "{--------------\n");
14123             PrintPosition(f, currentMove);
14124             fprintf(f, "--------------}\n\n");
14125
14126             SaveGame(f, 0, NULL); /* also closes the file*/
14127         }
14128
14129         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14130         nCmailMovesRegistered ++;
14131     } else if (nCmailGames == 1) {
14132         DisplayError(_("You have not made a move yet"), 0);
14133         return FALSE;
14134     }
14135
14136     return TRUE;
14137 }
14138
14139 void
14140 MailMoveEvent ()
14141 {
14142 #if !WIN32
14143     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14144     FILE *commandOutput;
14145     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14146     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14147     int nBuffers;
14148     int i;
14149     int archived;
14150     char *arcDir;
14151
14152     if (! cmailMsgLoaded) {
14153         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14154         return;
14155     }
14156
14157     if (nCmailGames == nCmailResults) {
14158         DisplayError(_("No unfinished games"), 0);
14159         return;
14160     }
14161
14162 #if CMAIL_PROHIBIT_REMAIL
14163     if (cmailMailedMove) {
14164       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);
14165         DisplayError(msg, 0);
14166         return;
14167     }
14168 #endif
14169
14170     if (! (cmailMailedMove || RegisterMove())) return;
14171
14172     if (   cmailMailedMove
14173         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14174       snprintf(string, MSG_SIZ, partCommandString,
14175                appData.debugMode ? " -v" : "", appData.cmailGameName);
14176         commandOutput = popen(string, "r");
14177
14178         if (commandOutput == NULL) {
14179             DisplayError(_("Failed to invoke cmail"), 0);
14180         } else {
14181             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14182                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14183             }
14184             if (nBuffers > 1) {
14185                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14186                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14187                 nBytes = MSG_SIZ - 1;
14188             } else {
14189                 (void) memcpy(msg, buffer, nBytes);
14190             }
14191             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14192
14193             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14194                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14195
14196                 archived = TRUE;
14197                 for (i = 0; i < nCmailGames; i ++) {
14198                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14199                         archived = FALSE;
14200                     }
14201                 }
14202                 if (   archived
14203                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14204                         != NULL)) {
14205                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14206                            arcDir,
14207                            appData.cmailGameName,
14208                            gameInfo.date);
14209                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14210                     cmailMsgLoaded = FALSE;
14211                 }
14212             }
14213
14214             DisplayInformation(msg);
14215             pclose(commandOutput);
14216         }
14217     } else {
14218         if ((*cmailMsg) != '\0') {
14219             DisplayInformation(cmailMsg);
14220         }
14221     }
14222
14223     return;
14224 #endif /* !WIN32 */
14225 }
14226
14227 char *
14228 CmailMsg ()
14229 {
14230 #if WIN32
14231     return NULL;
14232 #else
14233     int  prependComma = 0;
14234     char number[5];
14235     char string[MSG_SIZ];       /* Space for game-list */
14236     int  i;
14237
14238     if (!cmailMsgLoaded) return "";
14239
14240     if (cmailMailedMove) {
14241       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14242     } else {
14243         /* Create a list of games left */
14244       snprintf(string, MSG_SIZ, "[");
14245         for (i = 0; i < nCmailGames; i ++) {
14246             if (! (   cmailMoveRegistered[i]
14247                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14248                 if (prependComma) {
14249                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14250                 } else {
14251                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14252                     prependComma = 1;
14253                 }
14254
14255                 strcat(string, number);
14256             }
14257         }
14258         strcat(string, "]");
14259
14260         if (nCmailMovesRegistered + nCmailResults == 0) {
14261             switch (nCmailGames) {
14262               case 1:
14263                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14264                 break;
14265
14266               case 2:
14267                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14268                 break;
14269
14270               default:
14271                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14272                          nCmailGames);
14273                 break;
14274             }
14275         } else {
14276             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14277               case 1:
14278                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14279                          string);
14280                 break;
14281
14282               case 0:
14283                 if (nCmailResults == nCmailGames) {
14284                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14285                 } else {
14286                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14287                 }
14288                 break;
14289
14290               default:
14291                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14292                          string);
14293             }
14294         }
14295     }
14296     return cmailMsg;
14297 #endif /* WIN32 */
14298 }
14299
14300 void
14301 ResetGameEvent ()
14302 {
14303     if (gameMode == Training)
14304       SetTrainingModeOff();
14305
14306     Reset(TRUE, TRUE);
14307     cmailMsgLoaded = FALSE;
14308     if (appData.icsActive) {
14309       SendToICS(ics_prefix);
14310       SendToICS("refresh\n");
14311     }
14312 }
14313
14314 void
14315 ExitEvent (int status)
14316 {
14317     exiting++;
14318     if (exiting > 2) {
14319       /* Give up on clean exit */
14320       exit(status);
14321     }
14322     if (exiting > 1) {
14323       /* Keep trying for clean exit */
14324       return;
14325     }
14326
14327     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14328     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14329
14330     if (telnetISR != NULL) {
14331       RemoveInputSource(telnetISR);
14332     }
14333     if (icsPR != NoProc) {
14334       DestroyChildProcess(icsPR, TRUE);
14335     }
14336
14337     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14338     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14339
14340     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14341     /* make sure this other one finishes before killing it!                  */
14342     if(endingGame) { int count = 0;
14343         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14344         while(endingGame && count++ < 10) DoSleep(1);
14345         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14346     }
14347
14348     /* Kill off chess programs */
14349     if (first.pr != NoProc) {
14350         ExitAnalyzeMode();
14351
14352         DoSleep( appData.delayBeforeQuit );
14353         SendToProgram("quit\n", &first);
14354         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14355     }
14356     if (second.pr != NoProc) {
14357         DoSleep( appData.delayBeforeQuit );
14358         SendToProgram("quit\n", &second);
14359         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14360     }
14361     if (first.isr != NULL) {
14362         RemoveInputSource(first.isr);
14363     }
14364     if (second.isr != NULL) {
14365         RemoveInputSource(second.isr);
14366     }
14367
14368     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14369     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14370
14371     ShutDownFrontEnd();
14372     exit(status);
14373 }
14374
14375 void
14376 PauseEngine (ChessProgramState *cps)
14377 {
14378     SendToProgram("pause\n", cps);
14379     cps->pause = 2;
14380 }
14381
14382 void
14383 UnPauseEngine (ChessProgramState *cps)
14384 {
14385     SendToProgram("resume\n", cps);
14386     cps->pause = 1;
14387 }
14388
14389 void
14390 PauseEvent ()
14391 {
14392     if (appData.debugMode)
14393         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14394     if (pausing) {
14395         pausing = FALSE;
14396         ModeHighlight();
14397         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14398             StartClocks();
14399             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14400                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14401                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14402             }
14403             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14404             HandleMachineMove(stashedInputMove, stalledEngine);
14405             stalledEngine = NULL;
14406             return;
14407         }
14408         if (gameMode == MachinePlaysWhite ||
14409             gameMode == TwoMachinesPlay   ||
14410             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14411             if(first.pause)  UnPauseEngine(&first);
14412             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14413             if(second.pause) UnPauseEngine(&second);
14414             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14415             StartClocks();
14416         } else {
14417             DisplayBothClocks();
14418         }
14419         if (gameMode == PlayFromGameFile) {
14420             if (appData.timeDelay >= 0)
14421                 AutoPlayGameLoop();
14422         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14423             Reset(FALSE, TRUE);
14424             SendToICS(ics_prefix);
14425             SendToICS("refresh\n");
14426         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14427             ForwardInner(forwardMostMove);
14428         }
14429         pauseExamInvalid = FALSE;
14430     } else {
14431         switch (gameMode) {
14432           default:
14433             return;
14434           case IcsExamining:
14435             pauseExamForwardMostMove = forwardMostMove;
14436             pauseExamInvalid = FALSE;
14437             /* fall through */
14438           case IcsObserving:
14439           case IcsPlayingWhite:
14440           case IcsPlayingBlack:
14441             pausing = TRUE;
14442             ModeHighlight();
14443             return;
14444           case PlayFromGameFile:
14445             (void) StopLoadGameTimer();
14446             pausing = TRUE;
14447             ModeHighlight();
14448             break;
14449           case BeginningOfGame:
14450             if (appData.icsActive) return;
14451             /* else fall through */
14452           case MachinePlaysWhite:
14453           case MachinePlaysBlack:
14454           case TwoMachinesPlay:
14455             if (forwardMostMove == 0)
14456               return;           /* don't pause if no one has moved */
14457             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14458                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14459                 if(onMove->pause) {           // thinking engine can be paused
14460                     PauseEngine(onMove);      // do it
14461                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14462                         PauseEngine(onMove->other);
14463                     else
14464                         SendToProgram("easy\n", onMove->other);
14465                     StopClocks();
14466                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14467             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14468                 if(first.pause) {
14469                     PauseEngine(&first);
14470                     StopClocks();
14471                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14472             } else { // human on move, pause pondering by either method
14473                 if(first.pause)
14474                     PauseEngine(&first);
14475                 else if(appData.ponderNextMove)
14476                     SendToProgram("easy\n", &first);
14477                 StopClocks();
14478             }
14479             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14480           case AnalyzeMode:
14481             pausing = TRUE;
14482             ModeHighlight();
14483             break;
14484         }
14485     }
14486 }
14487
14488 void
14489 EditCommentEvent ()
14490 {
14491     char title[MSG_SIZ];
14492
14493     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14494       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14495     } else {
14496       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14497                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14498                parseList[currentMove - 1]);
14499     }
14500
14501     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14502 }
14503
14504
14505 void
14506 EditTagsEvent ()
14507 {
14508     char *tags = PGNTags(&gameInfo);
14509     bookUp = FALSE;
14510     EditTagsPopUp(tags, NULL);
14511     free(tags);
14512 }
14513
14514 void
14515 ToggleSecond ()
14516 {
14517   if(second.analyzing) {
14518     SendToProgram("exit\n", &second);
14519     second.analyzing = FALSE;
14520   } else {
14521     if (second.pr == NoProc) StartChessProgram(&second);
14522     InitChessProgram(&second, FALSE);
14523     FeedMovesToProgram(&second, currentMove);
14524
14525     SendToProgram("analyze\n", &second);
14526     second.analyzing = TRUE;
14527   }
14528 }
14529
14530 /* Toggle ShowThinking */
14531 void
14532 ToggleShowThinking()
14533 {
14534   appData.showThinking = !appData.showThinking;
14535   ShowThinkingEvent();
14536 }
14537
14538 int
14539 AnalyzeModeEvent ()
14540 {
14541     char buf[MSG_SIZ];
14542
14543     if (!first.analysisSupport) {
14544       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14545       DisplayError(buf, 0);
14546       return 0;
14547     }
14548     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14549     if (appData.icsActive) {
14550         if (gameMode != IcsObserving) {
14551           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14552             DisplayError(buf, 0);
14553             /* secure check */
14554             if (appData.icsEngineAnalyze) {
14555                 if (appData.debugMode)
14556                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14557                 ExitAnalyzeMode();
14558                 ModeHighlight();
14559             }
14560             return 0;
14561         }
14562         /* if enable, user wants to disable icsEngineAnalyze */
14563         if (appData.icsEngineAnalyze) {
14564                 ExitAnalyzeMode();
14565                 ModeHighlight();
14566                 return 0;
14567         }
14568         appData.icsEngineAnalyze = TRUE;
14569         if (appData.debugMode)
14570             fprintf(debugFP, "ICS engine analyze starting... \n");
14571     }
14572
14573     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14574     if (appData.noChessProgram || gameMode == AnalyzeMode)
14575       return 0;
14576
14577     if (gameMode != AnalyzeFile) {
14578         if (!appData.icsEngineAnalyze) {
14579                EditGameEvent();
14580                if (gameMode != EditGame) return 0;
14581         }
14582         if (!appData.showThinking) ToggleShowThinking();
14583         ResurrectChessProgram();
14584         SendToProgram("analyze\n", &first);
14585         first.analyzing = TRUE;
14586         /*first.maybeThinking = TRUE;*/
14587         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14588         EngineOutputPopUp();
14589     }
14590     if (!appData.icsEngineAnalyze) {
14591         gameMode = AnalyzeMode;
14592         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14593     }
14594     pausing = FALSE;
14595     ModeHighlight();
14596     SetGameInfo();
14597
14598     StartAnalysisClock();
14599     GetTimeMark(&lastNodeCountTime);
14600     lastNodeCount = 0;
14601     return 1;
14602 }
14603
14604 void
14605 AnalyzeFileEvent ()
14606 {
14607     if (appData.noChessProgram || gameMode == AnalyzeFile)
14608       return;
14609
14610     if (!first.analysisSupport) {
14611       char buf[MSG_SIZ];
14612       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14613       DisplayError(buf, 0);
14614       return;
14615     }
14616
14617     if (gameMode != AnalyzeMode) {
14618         keepInfo = 1; // mere annotating should not alter PGN tags
14619         EditGameEvent();
14620         keepInfo = 0;
14621         if (gameMode != EditGame) return;
14622         if (!appData.showThinking) ToggleShowThinking();
14623         ResurrectChessProgram();
14624         SendToProgram("analyze\n", &first);
14625         first.analyzing = TRUE;
14626         /*first.maybeThinking = TRUE;*/
14627         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14628         EngineOutputPopUp();
14629     }
14630     gameMode = AnalyzeFile;
14631     pausing = FALSE;
14632     ModeHighlight();
14633
14634     StartAnalysisClock();
14635     GetTimeMark(&lastNodeCountTime);
14636     lastNodeCount = 0;
14637     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14638     AnalysisPeriodicEvent(1);
14639 }
14640
14641 void
14642 MachineWhiteEvent ()
14643 {
14644     char buf[MSG_SIZ];
14645     char *bookHit = NULL;
14646
14647     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14648       return;
14649
14650
14651     if (gameMode == PlayFromGameFile ||
14652         gameMode == TwoMachinesPlay  ||
14653         gameMode == Training         ||
14654         gameMode == AnalyzeMode      ||
14655         gameMode == EndOfGame)
14656         EditGameEvent();
14657
14658     if (gameMode == EditPosition)
14659         EditPositionDone(TRUE);
14660
14661     if (!WhiteOnMove(currentMove)) {
14662         DisplayError(_("It is not White's turn"), 0);
14663         return;
14664     }
14665
14666     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14667       ExitAnalyzeMode();
14668
14669     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14670         gameMode == AnalyzeFile)
14671         TruncateGame();
14672
14673     ResurrectChessProgram();    /* in case it isn't running */
14674     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14675         gameMode = MachinePlaysWhite;
14676         ResetClocks();
14677     } else
14678     gameMode = MachinePlaysWhite;
14679     pausing = FALSE;
14680     ModeHighlight();
14681     SetGameInfo();
14682     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14683     DisplayTitle(buf);
14684     if (first.sendName) {
14685       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14686       SendToProgram(buf, &first);
14687     }
14688     if (first.sendTime) {
14689       if (first.useColors) {
14690         SendToProgram("black\n", &first); /*gnu kludge*/
14691       }
14692       SendTimeRemaining(&first, TRUE);
14693     }
14694     if (first.useColors) {
14695       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14696     }
14697     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14698     SetMachineThinkingEnables();
14699     first.maybeThinking = TRUE;
14700     StartClocks();
14701     firstMove = FALSE;
14702
14703     if (appData.autoFlipView && !flipView) {
14704       flipView = !flipView;
14705       DrawPosition(FALSE, NULL);
14706       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14707     }
14708
14709     if(bookHit) { // [HGM] book: simulate book reply
14710         static char bookMove[MSG_SIZ]; // a bit generous?
14711
14712         programStats.nodes = programStats.depth = programStats.time =
14713         programStats.score = programStats.got_only_move = 0;
14714         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14715
14716         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14717         strcat(bookMove, bookHit);
14718         HandleMachineMove(bookMove, &first);
14719     }
14720 }
14721
14722 void
14723 MachineBlackEvent ()
14724 {
14725   char buf[MSG_SIZ];
14726   char *bookHit = NULL;
14727
14728     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14729         return;
14730
14731
14732     if (gameMode == PlayFromGameFile ||
14733         gameMode == TwoMachinesPlay  ||
14734         gameMode == Training         ||
14735         gameMode == AnalyzeMode      ||
14736         gameMode == EndOfGame)
14737         EditGameEvent();
14738
14739     if (gameMode == EditPosition)
14740         EditPositionDone(TRUE);
14741
14742     if (WhiteOnMove(currentMove)) {
14743         DisplayError(_("It is not Black's turn"), 0);
14744         return;
14745     }
14746
14747     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14748       ExitAnalyzeMode();
14749
14750     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14751         gameMode == AnalyzeFile)
14752         TruncateGame();
14753
14754     ResurrectChessProgram();    /* in case it isn't running */
14755     gameMode = MachinePlaysBlack;
14756     pausing = FALSE;
14757     ModeHighlight();
14758     SetGameInfo();
14759     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14760     DisplayTitle(buf);
14761     if (first.sendName) {
14762       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14763       SendToProgram(buf, &first);
14764     }
14765     if (first.sendTime) {
14766       if (first.useColors) {
14767         SendToProgram("white\n", &first); /*gnu kludge*/
14768       }
14769       SendTimeRemaining(&first, FALSE);
14770     }
14771     if (first.useColors) {
14772       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14773     }
14774     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14775     SetMachineThinkingEnables();
14776     first.maybeThinking = TRUE;
14777     StartClocks();
14778
14779     if (appData.autoFlipView && flipView) {
14780       flipView = !flipView;
14781       DrawPosition(FALSE, NULL);
14782       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14783     }
14784     if(bookHit) { // [HGM] book: simulate book reply
14785         static char bookMove[MSG_SIZ]; // a bit generous?
14786
14787         programStats.nodes = programStats.depth = programStats.time =
14788         programStats.score = programStats.got_only_move = 0;
14789         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14790
14791         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14792         strcat(bookMove, bookHit);
14793         HandleMachineMove(bookMove, &first);
14794     }
14795 }
14796
14797
14798 void
14799 DisplayTwoMachinesTitle ()
14800 {
14801     char buf[MSG_SIZ];
14802     if (appData.matchGames > 0) {
14803         if(appData.tourneyFile[0]) {
14804           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14805                    gameInfo.white, _("vs."), gameInfo.black,
14806                    nextGame+1, appData.matchGames+1,
14807                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14808         } else
14809         if (first.twoMachinesColor[0] == 'w') {
14810           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14811                    gameInfo.white, _("vs."),  gameInfo.black,
14812                    first.matchWins, second.matchWins,
14813                    matchGame - 1 - (first.matchWins + second.matchWins));
14814         } else {
14815           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14816                    gameInfo.white, _("vs."), gameInfo.black,
14817                    second.matchWins, first.matchWins,
14818                    matchGame - 1 - (first.matchWins + second.matchWins));
14819         }
14820     } else {
14821       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14822     }
14823     DisplayTitle(buf);
14824 }
14825
14826 void
14827 SettingsMenuIfReady ()
14828 {
14829   if (second.lastPing != second.lastPong) {
14830     DisplayMessage("", _("Waiting for second chess program"));
14831     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14832     return;
14833   }
14834   ThawUI();
14835   DisplayMessage("", "");
14836   SettingsPopUp(&second);
14837 }
14838
14839 int
14840 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14841 {
14842     char buf[MSG_SIZ];
14843     if (cps->pr == NoProc) {
14844         StartChessProgram(cps);
14845         if (cps->protocolVersion == 1) {
14846           retry();
14847           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14848         } else {
14849           /* kludge: allow timeout for initial "feature" command */
14850           if(retry != TwoMachinesEventIfReady) FreezeUI();
14851           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14852           DisplayMessage("", buf);
14853           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14854         }
14855         return 1;
14856     }
14857     return 0;
14858 }
14859
14860 void
14861 TwoMachinesEvent P((void))
14862 {
14863     int i;
14864     char buf[MSG_SIZ];
14865     ChessProgramState *onmove;
14866     char *bookHit = NULL;
14867     static int stalling = 0;
14868     TimeMark now;
14869     long wait;
14870
14871     if (appData.noChessProgram) return;
14872
14873     switch (gameMode) {
14874       case TwoMachinesPlay:
14875         return;
14876       case MachinePlaysWhite:
14877       case MachinePlaysBlack:
14878         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14879             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14880             return;
14881         }
14882         /* fall through */
14883       case BeginningOfGame:
14884       case PlayFromGameFile:
14885       case EndOfGame:
14886         EditGameEvent();
14887         if (gameMode != EditGame) return;
14888         break;
14889       case EditPosition:
14890         EditPositionDone(TRUE);
14891         break;
14892       case AnalyzeMode:
14893       case AnalyzeFile:
14894         ExitAnalyzeMode();
14895         break;
14896       case EditGame:
14897       default:
14898         break;
14899     }
14900
14901 //    forwardMostMove = currentMove;
14902     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14903     startingEngine = TRUE;
14904
14905     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14906
14907     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14908     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14909       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14910       return;
14911     }
14912     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14913
14914     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14915                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14916         startingEngine = matchMode = FALSE;
14917         DisplayError("second engine does not play this", 0);
14918         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14919         EditGameEvent(); // switch back to EditGame mode
14920         return;
14921     }
14922
14923     if(!stalling) {
14924       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14925       SendToProgram("force\n", &second);
14926       stalling = 1;
14927       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14928       return;
14929     }
14930     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14931     if(appData.matchPause>10000 || appData.matchPause<10)
14932                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14933     wait = SubtractTimeMarks(&now, &pauseStart);
14934     if(wait < appData.matchPause) {
14935         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14936         return;
14937     }
14938     // we are now committed to starting the game
14939     stalling = 0;
14940     DisplayMessage("", "");
14941     if (startedFromSetupPosition) {
14942         SendBoard(&second, backwardMostMove);
14943     if (appData.debugMode) {
14944         fprintf(debugFP, "Two Machines\n");
14945     }
14946     }
14947     for (i = backwardMostMove; i < forwardMostMove; i++) {
14948         SendMoveToProgram(i, &second);
14949     }
14950
14951     gameMode = TwoMachinesPlay;
14952     pausing = startingEngine = FALSE;
14953     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14954     SetGameInfo();
14955     DisplayTwoMachinesTitle();
14956     firstMove = TRUE;
14957     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14958         onmove = &first;
14959     } else {
14960         onmove = &second;
14961     }
14962     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14963     SendToProgram(first.computerString, &first);
14964     if (first.sendName) {
14965       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14966       SendToProgram(buf, &first);
14967     }
14968     SendToProgram(second.computerString, &second);
14969     if (second.sendName) {
14970       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14971       SendToProgram(buf, &second);
14972     }
14973
14974     ResetClocks();
14975     if (!first.sendTime || !second.sendTime) {
14976         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14977         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14978     }
14979     if (onmove->sendTime) {
14980       if (onmove->useColors) {
14981         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14982       }
14983       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14984     }
14985     if (onmove->useColors) {
14986       SendToProgram(onmove->twoMachinesColor, onmove);
14987     }
14988     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14989 //    SendToProgram("go\n", onmove);
14990     onmove->maybeThinking = TRUE;
14991     SetMachineThinkingEnables();
14992
14993     StartClocks();
14994
14995     if(bookHit) { // [HGM] book: simulate book reply
14996         static char bookMove[MSG_SIZ]; // a bit generous?
14997
14998         programStats.nodes = programStats.depth = programStats.time =
14999         programStats.score = programStats.got_only_move = 0;
15000         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15001
15002         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15003         strcat(bookMove, bookHit);
15004         savedMessage = bookMove; // args for deferred call
15005         savedState = onmove;
15006         ScheduleDelayedEvent(DeferredBookMove, 1);
15007     }
15008 }
15009
15010 void
15011 TrainingEvent ()
15012 {
15013     if (gameMode == Training) {
15014       SetTrainingModeOff();
15015       gameMode = PlayFromGameFile;
15016       DisplayMessage("", _("Training mode off"));
15017     } else {
15018       gameMode = Training;
15019       animateTraining = appData.animate;
15020
15021       /* make sure we are not already at the end of the game */
15022       if (currentMove < forwardMostMove) {
15023         SetTrainingModeOn();
15024         DisplayMessage("", _("Training mode on"));
15025       } else {
15026         gameMode = PlayFromGameFile;
15027         DisplayError(_("Already at end of game"), 0);
15028       }
15029     }
15030     ModeHighlight();
15031 }
15032
15033 void
15034 IcsClientEvent ()
15035 {
15036     if (!appData.icsActive) return;
15037     switch (gameMode) {
15038       case IcsPlayingWhite:
15039       case IcsPlayingBlack:
15040       case IcsObserving:
15041       case IcsIdle:
15042       case BeginningOfGame:
15043       case IcsExamining:
15044         return;
15045
15046       case EditGame:
15047         break;
15048
15049       case EditPosition:
15050         EditPositionDone(TRUE);
15051         break;
15052
15053       case AnalyzeMode:
15054       case AnalyzeFile:
15055         ExitAnalyzeMode();
15056         break;
15057
15058       default:
15059         EditGameEvent();
15060         break;
15061     }
15062
15063     gameMode = IcsIdle;
15064     ModeHighlight();
15065     return;
15066 }
15067
15068 void
15069 EditGameEvent ()
15070 {
15071     int i;
15072
15073     switch (gameMode) {
15074       case Training:
15075         SetTrainingModeOff();
15076         break;
15077       case MachinePlaysWhite:
15078       case MachinePlaysBlack:
15079       case BeginningOfGame:
15080         SendToProgram("force\n", &first);
15081         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15082             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15083                 char buf[MSG_SIZ];
15084                 abortEngineThink = TRUE;
15085                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15086                 SendToProgram(buf, &first);
15087                 DisplayMessage("Aborting engine think", "");
15088                 FreezeUI();
15089             }
15090         }
15091         SetUserThinkingEnables();
15092         break;
15093       case PlayFromGameFile:
15094         (void) StopLoadGameTimer();
15095         if (gameFileFP != NULL) {
15096             gameFileFP = NULL;
15097         }
15098         break;
15099       case EditPosition:
15100         EditPositionDone(TRUE);
15101         break;
15102       case AnalyzeMode:
15103       case AnalyzeFile:
15104         ExitAnalyzeMode();
15105         SendToProgram("force\n", &first);
15106         break;
15107       case TwoMachinesPlay:
15108         GameEnds(EndOfFile, NULL, GE_PLAYER);
15109         ResurrectChessProgram();
15110         SetUserThinkingEnables();
15111         break;
15112       case EndOfGame:
15113         ResurrectChessProgram();
15114         break;
15115       case IcsPlayingBlack:
15116       case IcsPlayingWhite:
15117         DisplayError(_("Warning: You are still playing a game"), 0);
15118         break;
15119       case IcsObserving:
15120         DisplayError(_("Warning: You are still observing a game"), 0);
15121         break;
15122       case IcsExamining:
15123         DisplayError(_("Warning: You are still examining a game"), 0);
15124         break;
15125       case IcsIdle:
15126         break;
15127       case EditGame:
15128       default:
15129         return;
15130     }
15131
15132     pausing = FALSE;
15133     StopClocks();
15134     first.offeredDraw = second.offeredDraw = 0;
15135
15136     if (gameMode == PlayFromGameFile) {
15137         whiteTimeRemaining = timeRemaining[0][currentMove];
15138         blackTimeRemaining = timeRemaining[1][currentMove];
15139         DisplayTitle("");
15140     }
15141
15142     if (gameMode == MachinePlaysWhite ||
15143         gameMode == MachinePlaysBlack ||
15144         gameMode == TwoMachinesPlay ||
15145         gameMode == EndOfGame) {
15146         i = forwardMostMove;
15147         while (i > currentMove) {
15148             SendToProgram("undo\n", &first);
15149             i--;
15150         }
15151         if(!adjustedClock) {
15152         whiteTimeRemaining = timeRemaining[0][currentMove];
15153         blackTimeRemaining = timeRemaining[1][currentMove];
15154         DisplayBothClocks();
15155         }
15156         if (whiteFlag || blackFlag) {
15157             whiteFlag = blackFlag = 0;
15158         }
15159         DisplayTitle("");
15160     }
15161
15162     gameMode = EditGame;
15163     ModeHighlight();
15164     SetGameInfo();
15165 }
15166
15167
15168 void
15169 EditPositionEvent ()
15170 {
15171     if (gameMode == EditPosition) {
15172         EditGameEvent();
15173         return;
15174     }
15175
15176     EditGameEvent();
15177     if (gameMode != EditGame) return;
15178
15179     gameMode = EditPosition;
15180     ModeHighlight();
15181     SetGameInfo();
15182     if (currentMove > 0)
15183       CopyBoard(boards[0], boards[currentMove]);
15184
15185     blackPlaysFirst = !WhiteOnMove(currentMove);
15186     ResetClocks();
15187     currentMove = forwardMostMove = backwardMostMove = 0;
15188     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15189     DisplayMove(-1);
15190     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15191 }
15192
15193 void
15194 ExitAnalyzeMode ()
15195 {
15196     /* [DM] icsEngineAnalyze - possible call from other functions */
15197     if (appData.icsEngineAnalyze) {
15198         appData.icsEngineAnalyze = FALSE;
15199
15200         DisplayMessage("",_("Close ICS engine analyze..."));
15201     }
15202     if (first.analysisSupport && first.analyzing) {
15203       SendToBoth("exit\n");
15204       first.analyzing = second.analyzing = FALSE;
15205     }
15206     thinkOutput[0] = NULLCHAR;
15207 }
15208
15209 void
15210 EditPositionDone (Boolean fakeRights)
15211 {
15212     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15213
15214     startedFromSetupPosition = TRUE;
15215     InitChessProgram(&first, FALSE);
15216     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15217       boards[0][EP_STATUS] = EP_NONE;
15218       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15219       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15220         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15221         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15222       } else boards[0][CASTLING][2] = NoRights;
15223       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15224         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15225         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15226       } else boards[0][CASTLING][5] = NoRights;
15227       if(gameInfo.variant == VariantSChess) {
15228         int i;
15229         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15230           boards[0][VIRGIN][i] = 0;
15231           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15232           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15233         }
15234       }
15235     }
15236     SendToProgram("force\n", &first);
15237     if (blackPlaysFirst) {
15238         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15239         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15240         currentMove = forwardMostMove = backwardMostMove = 1;
15241         CopyBoard(boards[1], boards[0]);
15242     } else {
15243         currentMove = forwardMostMove = backwardMostMove = 0;
15244     }
15245     SendBoard(&first, forwardMostMove);
15246     if (appData.debugMode) {
15247         fprintf(debugFP, "EditPosDone\n");
15248     }
15249     DisplayTitle("");
15250     DisplayMessage("", "");
15251     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15252     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15253     gameMode = EditGame;
15254     ModeHighlight();
15255     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15256     ClearHighlights(); /* [AS] */
15257 }
15258
15259 /* Pause for `ms' milliseconds */
15260 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15261 void
15262 TimeDelay (long ms)
15263 {
15264     TimeMark m1, m2;
15265
15266     GetTimeMark(&m1);
15267     do {
15268         GetTimeMark(&m2);
15269     } while (SubtractTimeMarks(&m2, &m1) < ms);
15270 }
15271
15272 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15273 void
15274 SendMultiLineToICS (char *buf)
15275 {
15276     char temp[MSG_SIZ+1], *p;
15277     int len;
15278
15279     len = strlen(buf);
15280     if (len > MSG_SIZ)
15281       len = MSG_SIZ;
15282
15283     strncpy(temp, buf, len);
15284     temp[len] = 0;
15285
15286     p = temp;
15287     while (*p) {
15288         if (*p == '\n' || *p == '\r')
15289           *p = ' ';
15290         ++p;
15291     }
15292
15293     strcat(temp, "\n");
15294     SendToICS(temp);
15295     SendToPlayer(temp, strlen(temp));
15296 }
15297
15298 void
15299 SetWhiteToPlayEvent ()
15300 {
15301     if (gameMode == EditPosition) {
15302         blackPlaysFirst = FALSE;
15303         DisplayBothClocks();    /* works because currentMove is 0 */
15304     } else if (gameMode == IcsExamining) {
15305         SendToICS(ics_prefix);
15306         SendToICS("tomove white\n");
15307     }
15308 }
15309
15310 void
15311 SetBlackToPlayEvent ()
15312 {
15313     if (gameMode == EditPosition) {
15314         blackPlaysFirst = TRUE;
15315         currentMove = 1;        /* kludge */
15316         DisplayBothClocks();
15317         currentMove = 0;
15318     } else if (gameMode == IcsExamining) {
15319         SendToICS(ics_prefix);
15320         SendToICS("tomove black\n");
15321     }
15322 }
15323
15324 void
15325 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15326 {
15327     char buf[MSG_SIZ];
15328     ChessSquare piece = boards[0][y][x];
15329     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15330     static int lastVariant;
15331
15332     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15333
15334     switch (selection) {
15335       case ClearBoard:
15336         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15337         MarkTargetSquares(1);
15338         CopyBoard(currentBoard, boards[0]);
15339         CopyBoard(menuBoard, initialPosition);
15340         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15341             SendToICS(ics_prefix);
15342             SendToICS("bsetup clear\n");
15343         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15344             SendToICS(ics_prefix);
15345             SendToICS("clearboard\n");
15346         } else {
15347             int nonEmpty = 0;
15348             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15349                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15350                 for (y = 0; y < BOARD_HEIGHT; y++) {
15351                     if (gameMode == IcsExamining) {
15352                         if (boards[currentMove][y][x] != EmptySquare) {
15353                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15354                                     AAA + x, ONE + y);
15355                             SendToICS(buf);
15356                         }
15357                     } else if(boards[0][y][x] != DarkSquare) {
15358                         if(boards[0][y][x] != p) nonEmpty++;
15359                         boards[0][y][x] = p;
15360                     }
15361                 }
15362             }
15363             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15364                 int r;
15365                 for(r = 0; r < BOARD_HEIGHT; r++) {
15366                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15367                     ChessSquare p = menuBoard[r][x];
15368                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15369                   }
15370                 }
15371                 DisplayMessage("Clicking clock again restores position", "");
15372                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15373                 if(!nonEmpty) { // asked to clear an empty board
15374                     CopyBoard(boards[0], menuBoard);
15375                 } else
15376                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15377                     CopyBoard(boards[0], initialPosition);
15378                 } else
15379                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15380                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15381                     CopyBoard(boards[0], erasedBoard);
15382                 } else
15383                     CopyBoard(erasedBoard, currentBoard);
15384
15385             }
15386         }
15387         if (gameMode == EditPosition) {
15388             DrawPosition(FALSE, boards[0]);
15389         }
15390         break;
15391
15392       case WhitePlay:
15393         SetWhiteToPlayEvent();
15394         break;
15395
15396       case BlackPlay:
15397         SetBlackToPlayEvent();
15398         break;
15399
15400       case EmptySquare:
15401         if (gameMode == IcsExamining) {
15402             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15403             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15404             SendToICS(buf);
15405         } else {
15406             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15407                 if(x == BOARD_LEFT-2) {
15408                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15409                     boards[0][y][1] = 0;
15410                 } else
15411                 if(x == BOARD_RGHT+1) {
15412                     if(y >= gameInfo.holdingsSize) break;
15413                     boards[0][y][BOARD_WIDTH-2] = 0;
15414                 } else break;
15415             }
15416             boards[0][y][x] = EmptySquare;
15417             DrawPosition(FALSE, boards[0]);
15418         }
15419         break;
15420
15421       case PromotePiece:
15422         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15423            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15424             selection = (ChessSquare) (PROMOTED(piece));
15425         } else if(piece == EmptySquare) selection = WhiteSilver;
15426         else selection = (ChessSquare)((int)piece - 1);
15427         goto defaultlabel;
15428
15429       case DemotePiece:
15430         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15431            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15432             selection = (ChessSquare) (DEMOTED(piece));
15433         } else if(piece == EmptySquare) selection = BlackSilver;
15434         else selection = (ChessSquare)((int)piece + 1);
15435         goto defaultlabel;
15436
15437       case WhiteQueen:
15438       case BlackQueen:
15439         if(gameInfo.variant == VariantShatranj ||
15440            gameInfo.variant == VariantXiangqi  ||
15441            gameInfo.variant == VariantCourier  ||
15442            gameInfo.variant == VariantASEAN    ||
15443            gameInfo.variant == VariantMakruk     )
15444             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15445         goto defaultlabel;
15446
15447       case WhiteKing:
15448       case BlackKing:
15449         if(gameInfo.variant == VariantXiangqi)
15450             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15451         if(gameInfo.variant == VariantKnightmate)
15452             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15453       default:
15454         defaultlabel:
15455         if (gameMode == IcsExamining) {
15456             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15457             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15458                      PieceToChar(selection), AAA + x, ONE + y);
15459             SendToICS(buf);
15460         } else {
15461             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15462                 int n;
15463                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15464                     n = PieceToNumber(selection - BlackPawn);
15465                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15466                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15467                     boards[0][BOARD_HEIGHT-1-n][1]++;
15468                 } else
15469                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15470                     n = PieceToNumber(selection);
15471                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15472                     boards[0][n][BOARD_WIDTH-1] = selection;
15473                     boards[0][n][BOARD_WIDTH-2]++;
15474                 }
15475             } else
15476             boards[0][y][x] = selection;
15477             DrawPosition(TRUE, boards[0]);
15478             ClearHighlights();
15479             fromX = fromY = -1;
15480         }
15481         break;
15482     }
15483 }
15484
15485
15486 void
15487 DropMenuEvent (ChessSquare selection, int x, int y)
15488 {
15489     ChessMove moveType;
15490
15491     switch (gameMode) {
15492       case IcsPlayingWhite:
15493       case MachinePlaysBlack:
15494         if (!WhiteOnMove(currentMove)) {
15495             DisplayMoveError(_("It is Black's turn"));
15496             return;
15497         }
15498         moveType = WhiteDrop;
15499         break;
15500       case IcsPlayingBlack:
15501       case MachinePlaysWhite:
15502         if (WhiteOnMove(currentMove)) {
15503             DisplayMoveError(_("It is White's turn"));
15504             return;
15505         }
15506         moveType = BlackDrop;
15507         break;
15508       case EditGame:
15509         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15510         break;
15511       default:
15512         return;
15513     }
15514
15515     if (moveType == BlackDrop && selection < BlackPawn) {
15516       selection = (ChessSquare) ((int) selection
15517                                  + (int) BlackPawn - (int) WhitePawn);
15518     }
15519     if (boards[currentMove][y][x] != EmptySquare) {
15520         DisplayMoveError(_("That square is occupied"));
15521         return;
15522     }
15523
15524     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15525 }
15526
15527 void
15528 AcceptEvent ()
15529 {
15530     /* Accept a pending offer of any kind from opponent */
15531
15532     if (appData.icsActive) {
15533         SendToICS(ics_prefix);
15534         SendToICS("accept\n");
15535     } else if (cmailMsgLoaded) {
15536         if (currentMove == cmailOldMove &&
15537             commentList[cmailOldMove] != NULL &&
15538             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15539                    "Black offers a draw" : "White offers a draw")) {
15540             TruncateGame();
15541             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15542             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15543         } else {
15544             DisplayError(_("There is no pending offer on this move"), 0);
15545             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15546         }
15547     } else {
15548         /* Not used for offers from chess program */
15549     }
15550 }
15551
15552 void
15553 DeclineEvent ()
15554 {
15555     /* Decline a pending offer of any kind from opponent */
15556
15557     if (appData.icsActive) {
15558         SendToICS(ics_prefix);
15559         SendToICS("decline\n");
15560     } else if (cmailMsgLoaded) {
15561         if (currentMove == cmailOldMove &&
15562             commentList[cmailOldMove] != NULL &&
15563             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15564                    "Black offers a draw" : "White offers a draw")) {
15565 #ifdef NOTDEF
15566             AppendComment(cmailOldMove, "Draw declined", TRUE);
15567             DisplayComment(cmailOldMove - 1, "Draw declined");
15568 #endif /*NOTDEF*/
15569         } else {
15570             DisplayError(_("There is no pending offer on this move"), 0);
15571         }
15572     } else {
15573         /* Not used for offers from chess program */
15574     }
15575 }
15576
15577 void
15578 RematchEvent ()
15579 {
15580     /* Issue ICS rematch command */
15581     if (appData.icsActive) {
15582         SendToICS(ics_prefix);
15583         SendToICS("rematch\n");
15584     }
15585 }
15586
15587 void
15588 CallFlagEvent ()
15589 {
15590     /* Call your opponent's flag (claim a win on time) */
15591     if (appData.icsActive) {
15592         SendToICS(ics_prefix);
15593         SendToICS("flag\n");
15594     } else {
15595         switch (gameMode) {
15596           default:
15597             return;
15598           case MachinePlaysWhite:
15599             if (whiteFlag) {
15600                 if (blackFlag)
15601                   GameEnds(GameIsDrawn, "Both players ran out of time",
15602                            GE_PLAYER);
15603                 else
15604                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15605             } else {
15606                 DisplayError(_("Your opponent is not out of time"), 0);
15607             }
15608             break;
15609           case MachinePlaysBlack:
15610             if (blackFlag) {
15611                 if (whiteFlag)
15612                   GameEnds(GameIsDrawn, "Both players ran out of time",
15613                            GE_PLAYER);
15614                 else
15615                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15616             } else {
15617                 DisplayError(_("Your opponent is not out of time"), 0);
15618             }
15619             break;
15620         }
15621     }
15622 }
15623
15624 void
15625 ClockClick (int which)
15626 {       // [HGM] code moved to back-end from winboard.c
15627         if(which) { // black clock
15628           if (gameMode == EditPosition || gameMode == IcsExamining) {
15629             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15630             SetBlackToPlayEvent();
15631           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15632                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15633           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15634           } else if (shiftKey) {
15635             AdjustClock(which, -1);
15636           } else if (gameMode == IcsPlayingWhite ||
15637                      gameMode == MachinePlaysBlack) {
15638             CallFlagEvent();
15639           }
15640         } else { // white clock
15641           if (gameMode == EditPosition || gameMode == IcsExamining) {
15642             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15643             SetWhiteToPlayEvent();
15644           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15645                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15646           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15647           } else if (shiftKey) {
15648             AdjustClock(which, -1);
15649           } else if (gameMode == IcsPlayingBlack ||
15650                    gameMode == MachinePlaysWhite) {
15651             CallFlagEvent();
15652           }
15653         }
15654 }
15655
15656 void
15657 DrawEvent ()
15658 {
15659     /* Offer draw or accept pending draw offer from opponent */
15660
15661     if (appData.icsActive) {
15662         /* Note: tournament rules require draw offers to be
15663            made after you make your move but before you punch
15664            your clock.  Currently ICS doesn't let you do that;
15665            instead, you immediately punch your clock after making
15666            a move, but you can offer a draw at any time. */
15667
15668         SendToICS(ics_prefix);
15669         SendToICS("draw\n");
15670         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15671     } else if (cmailMsgLoaded) {
15672         if (currentMove == cmailOldMove &&
15673             commentList[cmailOldMove] != NULL &&
15674             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15675                    "Black offers a draw" : "White offers a draw")) {
15676             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15677             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15678         } else if (currentMove == cmailOldMove + 1) {
15679             char *offer = WhiteOnMove(cmailOldMove) ?
15680               "White offers a draw" : "Black offers a draw";
15681             AppendComment(currentMove, offer, TRUE);
15682             DisplayComment(currentMove - 1, offer);
15683             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15684         } else {
15685             DisplayError(_("You must make your move before offering a draw"), 0);
15686             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15687         }
15688     } else if (first.offeredDraw) {
15689         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15690     } else {
15691         if (first.sendDrawOffers) {
15692             SendToProgram("draw\n", &first);
15693             userOfferedDraw = TRUE;
15694         }
15695     }
15696 }
15697
15698 void
15699 AdjournEvent ()
15700 {
15701     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15702
15703     if (appData.icsActive) {
15704         SendToICS(ics_prefix);
15705         SendToICS("adjourn\n");
15706     } else {
15707         /* Currently GNU Chess doesn't offer or accept Adjourns */
15708     }
15709 }
15710
15711
15712 void
15713 AbortEvent ()
15714 {
15715     /* Offer Abort or accept pending Abort offer from opponent */
15716
15717     if (appData.icsActive) {
15718         SendToICS(ics_prefix);
15719         SendToICS("abort\n");
15720     } else {
15721         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15722     }
15723 }
15724
15725 void
15726 ResignEvent ()
15727 {
15728     /* Resign.  You can do this even if it's not your turn. */
15729
15730     if (appData.icsActive) {
15731         SendToICS(ics_prefix);
15732         SendToICS("resign\n");
15733     } else {
15734         switch (gameMode) {
15735           case MachinePlaysWhite:
15736             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15737             break;
15738           case MachinePlaysBlack:
15739             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15740             break;
15741           case EditGame:
15742             if (cmailMsgLoaded) {
15743                 TruncateGame();
15744                 if (WhiteOnMove(cmailOldMove)) {
15745                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15746                 } else {
15747                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15748                 }
15749                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15750             }
15751             break;
15752           default:
15753             break;
15754         }
15755     }
15756 }
15757
15758
15759 void
15760 StopObservingEvent ()
15761 {
15762     /* Stop observing current games */
15763     SendToICS(ics_prefix);
15764     SendToICS("unobserve\n");
15765 }
15766
15767 void
15768 StopExaminingEvent ()
15769 {
15770     /* Stop observing current game */
15771     SendToICS(ics_prefix);
15772     SendToICS("unexamine\n");
15773 }
15774
15775 void
15776 ForwardInner (int target)
15777 {
15778     int limit; int oldSeekGraphUp = seekGraphUp;
15779
15780     if (appData.debugMode)
15781         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15782                 target, currentMove, forwardMostMove);
15783
15784     if (gameMode == EditPosition)
15785       return;
15786
15787     seekGraphUp = FALSE;
15788     MarkTargetSquares(1);
15789     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15790
15791     if (gameMode == PlayFromGameFile && !pausing)
15792       PauseEvent();
15793
15794     if (gameMode == IcsExamining && pausing)
15795       limit = pauseExamForwardMostMove;
15796     else
15797       limit = forwardMostMove;
15798
15799     if (target > limit) target = limit;
15800
15801     if (target > 0 && moveList[target - 1][0]) {
15802         int fromX, fromY, toX, toY;
15803         toX = moveList[target - 1][2] - AAA;
15804         toY = moveList[target - 1][3] - ONE;
15805         if (moveList[target - 1][1] == '@') {
15806             if (appData.highlightLastMove) {
15807                 SetHighlights(-1, -1, toX, toY);
15808             }
15809         } else {
15810             int viaX = moveList[target - 1][5] - AAA;
15811             int viaY = moveList[target - 1][6] - ONE;
15812             fromX = moveList[target - 1][0] - AAA;
15813             fromY = moveList[target - 1][1] - ONE;
15814             if (target == currentMove + 1) {
15815                 if(moveList[target - 1][4] == ';') { // multi-leg
15816                     ChessSquare piece = boards[currentMove][viaY][viaX];
15817                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15818                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15819                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15820                     boards[currentMove][viaY][viaX] = piece;
15821                 } else
15822                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15823             }
15824             if (appData.highlightLastMove) {
15825                 SetHighlights(fromX, fromY, toX, toY);
15826             }
15827         }
15828     }
15829     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15830         gameMode == Training || gameMode == PlayFromGameFile ||
15831         gameMode == AnalyzeFile) {
15832         while (currentMove < target) {
15833             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15834             SendMoveToProgram(currentMove++, &first);
15835         }
15836     } else {
15837         currentMove = target;
15838     }
15839
15840     if (gameMode == EditGame || gameMode == EndOfGame) {
15841         whiteTimeRemaining = timeRemaining[0][currentMove];
15842         blackTimeRemaining = timeRemaining[1][currentMove];
15843     }
15844     DisplayBothClocks();
15845     DisplayMove(currentMove - 1);
15846     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15847     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15848     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15849         DisplayComment(currentMove - 1, commentList[currentMove]);
15850     }
15851     ClearMap(); // [HGM] exclude: invalidate map
15852 }
15853
15854
15855 void
15856 ForwardEvent ()
15857 {
15858     if (gameMode == IcsExamining && !pausing) {
15859         SendToICS(ics_prefix);
15860         SendToICS("forward\n");
15861     } else {
15862         ForwardInner(currentMove + 1);
15863     }
15864 }
15865
15866 void
15867 ToEndEvent ()
15868 {
15869     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15870         /* to optimze, we temporarily turn off analysis mode while we feed
15871          * the remaining moves to the engine. Otherwise we get analysis output
15872          * after each move.
15873          */
15874         if (first.analysisSupport) {
15875           SendToProgram("exit\nforce\n", &first);
15876           first.analyzing = FALSE;
15877         }
15878     }
15879
15880     if (gameMode == IcsExamining && !pausing) {
15881         SendToICS(ics_prefix);
15882         SendToICS("forward 999999\n");
15883     } else {
15884         ForwardInner(forwardMostMove);
15885     }
15886
15887     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15888         /* we have fed all the moves, so reactivate analysis mode */
15889         SendToProgram("analyze\n", &first);
15890         first.analyzing = TRUE;
15891         /*first.maybeThinking = TRUE;*/
15892         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15893     }
15894 }
15895
15896 void
15897 BackwardInner (int target)
15898 {
15899     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15900
15901     if (appData.debugMode)
15902         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15903                 target, currentMove, forwardMostMove);
15904
15905     if (gameMode == EditPosition) return;
15906     seekGraphUp = FALSE;
15907     MarkTargetSquares(1);
15908     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15909     if (currentMove <= backwardMostMove) {
15910         ClearHighlights();
15911         DrawPosition(full_redraw, boards[currentMove]);
15912         return;
15913     }
15914     if (gameMode == PlayFromGameFile && !pausing)
15915       PauseEvent();
15916
15917     if (moveList[target][0]) {
15918         int fromX, fromY, toX, toY;
15919         toX = moveList[target][2] - AAA;
15920         toY = moveList[target][3] - ONE;
15921         if (moveList[target][1] == '@') {
15922             if (appData.highlightLastMove) {
15923                 SetHighlights(-1, -1, toX, toY);
15924             }
15925         } else {
15926             fromX = moveList[target][0] - AAA;
15927             fromY = moveList[target][1] - ONE;
15928             if (target == currentMove - 1) {
15929                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15930             }
15931             if (appData.highlightLastMove) {
15932                 SetHighlights(fromX, fromY, toX, toY);
15933             }
15934         }
15935     }
15936     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15937         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15938         while (currentMove > target) {
15939             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15940                 // null move cannot be undone. Reload program with move history before it.
15941                 int i;
15942                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15943                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15944                 }
15945                 SendBoard(&first, i);
15946               if(second.analyzing) SendBoard(&second, i);
15947                 for(currentMove=i; currentMove<target; currentMove++) {
15948                     SendMoveToProgram(currentMove, &first);
15949                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15950                 }
15951                 break;
15952             }
15953             SendToBoth("undo\n");
15954             currentMove--;
15955         }
15956     } else {
15957         currentMove = target;
15958     }
15959
15960     if (gameMode == EditGame || gameMode == EndOfGame) {
15961         whiteTimeRemaining = timeRemaining[0][currentMove];
15962         blackTimeRemaining = timeRemaining[1][currentMove];
15963     }
15964     DisplayBothClocks();
15965     DisplayMove(currentMove - 1);
15966     DrawPosition(full_redraw, boards[currentMove]);
15967     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15968     // [HGM] PV info: routine tests if comment empty
15969     DisplayComment(currentMove - 1, commentList[currentMove]);
15970     ClearMap(); // [HGM] exclude: invalidate map
15971 }
15972
15973 void
15974 BackwardEvent ()
15975 {
15976     if (gameMode == IcsExamining && !pausing) {
15977         SendToICS(ics_prefix);
15978         SendToICS("backward\n");
15979     } else {
15980         BackwardInner(currentMove - 1);
15981     }
15982 }
15983
15984 void
15985 ToStartEvent ()
15986 {
15987     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15988         /* to optimize, we temporarily turn off analysis mode while we undo
15989          * all the moves. Otherwise we get analysis output after each undo.
15990          */
15991         if (first.analysisSupport) {
15992           SendToProgram("exit\nforce\n", &first);
15993           first.analyzing = FALSE;
15994         }
15995     }
15996
15997     if (gameMode == IcsExamining && !pausing) {
15998         SendToICS(ics_prefix);
15999         SendToICS("backward 999999\n");
16000     } else {
16001         BackwardInner(backwardMostMove);
16002     }
16003
16004     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16005         /* we have fed all the moves, so reactivate analysis mode */
16006         SendToProgram("analyze\n", &first);
16007         first.analyzing = TRUE;
16008         /*first.maybeThinking = TRUE;*/
16009         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16010     }
16011 }
16012
16013 void
16014 ToNrEvent (int to)
16015 {
16016   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16017   if (to >= forwardMostMove) to = forwardMostMove;
16018   if (to <= backwardMostMove) to = backwardMostMove;
16019   if (to < currentMove) {
16020     BackwardInner(to);
16021   } else {
16022     ForwardInner(to);
16023   }
16024 }
16025
16026 void
16027 RevertEvent (Boolean annotate)
16028 {
16029     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16030         return;
16031     }
16032     if (gameMode != IcsExamining) {
16033         DisplayError(_("You are not examining a game"), 0);
16034         return;
16035     }
16036     if (pausing) {
16037         DisplayError(_("You can't revert while pausing"), 0);
16038         return;
16039     }
16040     SendToICS(ics_prefix);
16041     SendToICS("revert\n");
16042 }
16043
16044 void
16045 RetractMoveEvent ()
16046 {
16047     switch (gameMode) {
16048       case MachinePlaysWhite:
16049       case MachinePlaysBlack:
16050         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16051             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16052             return;
16053         }
16054         if (forwardMostMove < 2) return;
16055         currentMove = forwardMostMove = forwardMostMove - 2;
16056         whiteTimeRemaining = timeRemaining[0][currentMove];
16057         blackTimeRemaining = timeRemaining[1][currentMove];
16058         DisplayBothClocks();
16059         DisplayMove(currentMove - 1);
16060         ClearHighlights();/*!! could figure this out*/
16061         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16062         SendToProgram("remove\n", &first);
16063         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16064         break;
16065
16066       case BeginningOfGame:
16067       default:
16068         break;
16069
16070       case IcsPlayingWhite:
16071       case IcsPlayingBlack:
16072         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16073             SendToICS(ics_prefix);
16074             SendToICS("takeback 2\n");
16075         } else {
16076             SendToICS(ics_prefix);
16077             SendToICS("takeback 1\n");
16078         }
16079         break;
16080     }
16081 }
16082
16083 void
16084 MoveNowEvent ()
16085 {
16086     ChessProgramState *cps;
16087
16088     switch (gameMode) {
16089       case MachinePlaysWhite:
16090         if (!WhiteOnMove(forwardMostMove)) {
16091             DisplayError(_("It is your turn"), 0);
16092             return;
16093         }
16094         cps = &first;
16095         break;
16096       case MachinePlaysBlack:
16097         if (WhiteOnMove(forwardMostMove)) {
16098             DisplayError(_("It is your turn"), 0);
16099             return;
16100         }
16101         cps = &first;
16102         break;
16103       case TwoMachinesPlay:
16104         if (WhiteOnMove(forwardMostMove) ==
16105             (first.twoMachinesColor[0] == 'w')) {
16106             cps = &first;
16107         } else {
16108             cps = &second;
16109         }
16110         break;
16111       case BeginningOfGame:
16112       default:
16113         return;
16114     }
16115     SendToProgram("?\n", cps);
16116 }
16117
16118 void
16119 TruncateGameEvent ()
16120 {
16121     EditGameEvent();
16122     if (gameMode != EditGame) return;
16123     TruncateGame();
16124 }
16125
16126 void
16127 TruncateGame ()
16128 {
16129     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16130     if (forwardMostMove > currentMove) {
16131         if (gameInfo.resultDetails != NULL) {
16132             free(gameInfo.resultDetails);
16133             gameInfo.resultDetails = NULL;
16134             gameInfo.result = GameUnfinished;
16135         }
16136         forwardMostMove = currentMove;
16137         HistorySet(parseList, backwardMostMove, forwardMostMove,
16138                    currentMove-1);
16139     }
16140 }
16141
16142 void
16143 HintEvent ()
16144 {
16145     if (appData.noChessProgram) return;
16146     switch (gameMode) {
16147       case MachinePlaysWhite:
16148         if (WhiteOnMove(forwardMostMove)) {
16149             DisplayError(_("Wait until your turn."), 0);
16150             return;
16151         }
16152         break;
16153       case BeginningOfGame:
16154       case MachinePlaysBlack:
16155         if (!WhiteOnMove(forwardMostMove)) {
16156             DisplayError(_("Wait until your turn."), 0);
16157             return;
16158         }
16159         break;
16160       default:
16161         DisplayError(_("No hint available"), 0);
16162         return;
16163     }
16164     SendToProgram("hint\n", &first);
16165     hintRequested = TRUE;
16166 }
16167
16168 int
16169 SaveSelected (FILE *g, int dummy, char *dummy2)
16170 {
16171     ListGame * lg = (ListGame *) gameList.head;
16172     int nItem, cnt=0;
16173     FILE *f;
16174
16175     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16176         DisplayError(_("Game list not loaded or empty"), 0);
16177         return 0;
16178     }
16179
16180     creatingBook = TRUE; // suppresses stuff during load game
16181
16182     /* Get list size */
16183     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16184         if(lg->position >= 0) { // selected?
16185             LoadGame(f, nItem, "", TRUE);
16186             SaveGamePGN2(g); // leaves g open
16187             cnt++; DoEvents();
16188         }
16189         lg = (ListGame *) lg->node.succ;
16190     }
16191
16192     fclose(g);
16193     creatingBook = FALSE;
16194
16195     return cnt;
16196 }
16197
16198 void
16199 CreateBookEvent ()
16200 {
16201     ListGame * lg = (ListGame *) gameList.head;
16202     FILE *f, *g;
16203     int nItem;
16204     static int secondTime = FALSE;
16205
16206     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16207         DisplayError(_("Game list not loaded or empty"), 0);
16208         return;
16209     }
16210
16211     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16212         fclose(g);
16213         secondTime++;
16214         DisplayNote(_("Book file exists! Try again for overwrite."));
16215         return;
16216     }
16217
16218     creatingBook = TRUE;
16219     secondTime = FALSE;
16220
16221     /* Get list size */
16222     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16223         if(lg->position >= 0) {
16224             LoadGame(f, nItem, "", TRUE);
16225             AddGameToBook(TRUE);
16226             DoEvents();
16227         }
16228         lg = (ListGame *) lg->node.succ;
16229     }
16230
16231     creatingBook = FALSE;
16232     FlushBook();
16233 }
16234
16235 void
16236 BookEvent ()
16237 {
16238     if (appData.noChessProgram) return;
16239     switch (gameMode) {
16240       case MachinePlaysWhite:
16241         if (WhiteOnMove(forwardMostMove)) {
16242             DisplayError(_("Wait until your turn."), 0);
16243             return;
16244         }
16245         break;
16246       case BeginningOfGame:
16247       case MachinePlaysBlack:
16248         if (!WhiteOnMove(forwardMostMove)) {
16249             DisplayError(_("Wait until your turn."), 0);
16250             return;
16251         }
16252         break;
16253       case EditPosition:
16254         EditPositionDone(TRUE);
16255         break;
16256       case TwoMachinesPlay:
16257         return;
16258       default:
16259         break;
16260     }
16261     SendToProgram("bk\n", &first);
16262     bookOutput[0] = NULLCHAR;
16263     bookRequested = TRUE;
16264 }
16265
16266 void
16267 AboutGameEvent ()
16268 {
16269     char *tags = PGNTags(&gameInfo);
16270     TagsPopUp(tags, CmailMsg());
16271     free(tags);
16272 }
16273
16274 /* end button procedures */
16275
16276 void
16277 PrintPosition (FILE *fp, int move)
16278 {
16279     int i, j;
16280
16281     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16282         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16283             char c = PieceToChar(boards[move][i][j]);
16284             fputc(c == 'x' ? '.' : c, fp);
16285             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16286         }
16287     }
16288     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16289       fprintf(fp, "white to play\n");
16290     else
16291       fprintf(fp, "black to play\n");
16292 }
16293
16294 void
16295 PrintOpponents (FILE *fp)
16296 {
16297     if (gameInfo.white != NULL) {
16298         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16299     } else {
16300         fprintf(fp, "\n");
16301     }
16302 }
16303
16304 /* Find last component of program's own name, using some heuristics */
16305 void
16306 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16307 {
16308     char *p, *q, c;
16309     int local = (strcmp(host, "localhost") == 0);
16310     while (!local && (p = strchr(prog, ';')) != NULL) {
16311         p++;
16312         while (*p == ' ') p++;
16313         prog = p;
16314     }
16315     if (*prog == '"' || *prog == '\'') {
16316         q = strchr(prog + 1, *prog);
16317     } else {
16318         q = strchr(prog, ' ');
16319     }
16320     if (q == NULL) q = prog + strlen(prog);
16321     p = q;
16322     while (p >= prog && *p != '/' && *p != '\\') p--;
16323     p++;
16324     if(p == prog && *p == '"') p++;
16325     c = *q; *q = 0;
16326     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16327     memcpy(buf, p, q - p);
16328     buf[q - p] = NULLCHAR;
16329     if (!local) {
16330         strcat(buf, "@");
16331         strcat(buf, host);
16332     }
16333 }
16334
16335 char *
16336 TimeControlTagValue ()
16337 {
16338     char buf[MSG_SIZ];
16339     if (!appData.clockMode) {
16340       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16341     } else if (movesPerSession > 0) {
16342       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16343     } else if (timeIncrement == 0) {
16344       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16345     } else {
16346       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16347     }
16348     return StrSave(buf);
16349 }
16350
16351 void
16352 SetGameInfo ()
16353 {
16354     /* This routine is used only for certain modes */
16355     VariantClass v = gameInfo.variant;
16356     ChessMove r = GameUnfinished;
16357     char *p = NULL;
16358
16359     if(keepInfo) return;
16360
16361     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16362         r = gameInfo.result;
16363         p = gameInfo.resultDetails;
16364         gameInfo.resultDetails = NULL;
16365     }
16366     ClearGameInfo(&gameInfo);
16367     gameInfo.variant = v;
16368
16369     switch (gameMode) {
16370       case MachinePlaysWhite:
16371         gameInfo.event = StrSave( appData.pgnEventHeader );
16372         gameInfo.site = StrSave(HostName());
16373         gameInfo.date = PGNDate();
16374         gameInfo.round = StrSave("-");
16375         gameInfo.white = StrSave(first.tidy);
16376         gameInfo.black = StrSave(UserName());
16377         gameInfo.timeControl = TimeControlTagValue();
16378         break;
16379
16380       case MachinePlaysBlack:
16381         gameInfo.event = StrSave( appData.pgnEventHeader );
16382         gameInfo.site = StrSave(HostName());
16383         gameInfo.date = PGNDate();
16384         gameInfo.round = StrSave("-");
16385         gameInfo.white = StrSave(UserName());
16386         gameInfo.black = StrSave(first.tidy);
16387         gameInfo.timeControl = TimeControlTagValue();
16388         break;
16389
16390       case TwoMachinesPlay:
16391         gameInfo.event = StrSave( appData.pgnEventHeader );
16392         gameInfo.site = StrSave(HostName());
16393         gameInfo.date = PGNDate();
16394         if (roundNr > 0) {
16395             char buf[MSG_SIZ];
16396             snprintf(buf, MSG_SIZ, "%d", roundNr);
16397             gameInfo.round = StrSave(buf);
16398         } else {
16399             gameInfo.round = StrSave("-");
16400         }
16401         if (first.twoMachinesColor[0] == 'w') {
16402             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16403             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16404         } else {
16405             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16406             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16407         }
16408         gameInfo.timeControl = TimeControlTagValue();
16409         break;
16410
16411       case EditGame:
16412         gameInfo.event = StrSave("Edited game");
16413         gameInfo.site = StrSave(HostName());
16414         gameInfo.date = PGNDate();
16415         gameInfo.round = StrSave("-");
16416         gameInfo.white = StrSave("-");
16417         gameInfo.black = StrSave("-");
16418         gameInfo.result = r;
16419         gameInfo.resultDetails = p;
16420         break;
16421
16422       case EditPosition:
16423         gameInfo.event = StrSave("Edited position");
16424         gameInfo.site = StrSave(HostName());
16425         gameInfo.date = PGNDate();
16426         gameInfo.round = StrSave("-");
16427         gameInfo.white = StrSave("-");
16428         gameInfo.black = StrSave("-");
16429         break;
16430
16431       case IcsPlayingWhite:
16432       case IcsPlayingBlack:
16433       case IcsObserving:
16434       case IcsExamining:
16435         break;
16436
16437       case PlayFromGameFile:
16438         gameInfo.event = StrSave("Game from non-PGN file");
16439         gameInfo.site = StrSave(HostName());
16440         gameInfo.date = PGNDate();
16441         gameInfo.round = StrSave("-");
16442         gameInfo.white = StrSave("?");
16443         gameInfo.black = StrSave("?");
16444         break;
16445
16446       default:
16447         break;
16448     }
16449 }
16450
16451 void
16452 ReplaceComment (int index, char *text)
16453 {
16454     int len;
16455     char *p;
16456     float score;
16457
16458     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16459        pvInfoList[index-1].depth == len &&
16460        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16461        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16462     while (*text == '\n') text++;
16463     len = strlen(text);
16464     while (len > 0 && text[len - 1] == '\n') len--;
16465
16466     if (commentList[index] != NULL)
16467       free(commentList[index]);
16468
16469     if (len == 0) {
16470         commentList[index] = NULL;
16471         return;
16472     }
16473   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16474       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16475       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16476     commentList[index] = (char *) malloc(len + 2);
16477     strncpy(commentList[index], text, len);
16478     commentList[index][len] = '\n';
16479     commentList[index][len + 1] = NULLCHAR;
16480   } else {
16481     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16482     char *p;
16483     commentList[index] = (char *) malloc(len + 7);
16484     safeStrCpy(commentList[index], "{\n", 3);
16485     safeStrCpy(commentList[index]+2, text, len+1);
16486     commentList[index][len+2] = NULLCHAR;
16487     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16488     strcat(commentList[index], "\n}\n");
16489   }
16490 }
16491
16492 void
16493 CrushCRs (char *text)
16494 {
16495   char *p = text;
16496   char *q = text;
16497   char ch;
16498
16499   do {
16500     ch = *p++;
16501     if (ch == '\r') continue;
16502     *q++ = ch;
16503   } while (ch != '\0');
16504 }
16505
16506 void
16507 AppendComment (int index, char *text, Boolean addBraces)
16508 /* addBraces  tells if we should add {} */
16509 {
16510     int oldlen, len;
16511     char *old;
16512
16513 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16514     if(addBraces == 3) addBraces = 0; else // force appending literally
16515     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16516
16517     CrushCRs(text);
16518     while (*text == '\n') text++;
16519     len = strlen(text);
16520     while (len > 0 && text[len - 1] == '\n') len--;
16521     text[len] = NULLCHAR;
16522
16523     if (len == 0) return;
16524
16525     if (commentList[index] != NULL) {
16526       Boolean addClosingBrace = addBraces;
16527         old = commentList[index];
16528         oldlen = strlen(old);
16529         while(commentList[index][oldlen-1] ==  '\n')
16530           commentList[index][--oldlen] = NULLCHAR;
16531         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16532         safeStrCpy(commentList[index], old, oldlen + len + 6);
16533         free(old);
16534         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16535         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16536           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16537           while (*text == '\n') { text++; len--; }
16538           commentList[index][--oldlen] = NULLCHAR;
16539       }
16540         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16541         else          strcat(commentList[index], "\n");
16542         strcat(commentList[index], text);
16543         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16544         else          strcat(commentList[index], "\n");
16545     } else {
16546         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16547         if(addBraces)
16548           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16549         else commentList[index][0] = NULLCHAR;
16550         strcat(commentList[index], text);
16551         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16552         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16553     }
16554 }
16555
16556 static char *
16557 FindStr (char * text, char * sub_text)
16558 {
16559     char * result = strstr( text, sub_text );
16560
16561     if( result != NULL ) {
16562         result += strlen( sub_text );
16563     }
16564
16565     return result;
16566 }
16567
16568 /* [AS] Try to extract PV info from PGN comment */
16569 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16570 char *
16571 GetInfoFromComment (int index, char * text)
16572 {
16573     char * sep = text, *p;
16574
16575     if( text != NULL && index > 0 ) {
16576         int score = 0;
16577         int depth = 0;
16578         int time = -1, sec = 0, deci;
16579         char * s_eval = FindStr( text, "[%eval " );
16580         char * s_emt = FindStr( text, "[%emt " );
16581 #if 0
16582         if( s_eval != NULL || s_emt != NULL ) {
16583 #else
16584         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16585 #endif
16586             /* New style */
16587             char delim;
16588
16589             if( s_eval != NULL ) {
16590                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16591                     return text;
16592                 }
16593
16594                 if( delim != ']' ) {
16595                     return text;
16596                 }
16597             }
16598
16599             if( s_emt != NULL ) {
16600             }
16601                 return text;
16602         }
16603         else {
16604             /* We expect something like: [+|-]nnn.nn/dd */
16605             int score_lo = 0;
16606
16607             if(*text != '{') return text; // [HGM] braces: must be normal comment
16608
16609             sep = strchr( text, '/' );
16610             if( sep == NULL || sep < (text+4) ) {
16611                 return text;
16612             }
16613
16614             p = text;
16615             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16616             if(p[1] == '(') { // comment starts with PV
16617                p = strchr(p, ')'); // locate end of PV
16618                if(p == NULL || sep < p+5) return text;
16619                // at this point we have something like "{(.*) +0.23/6 ..."
16620                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16621                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16622                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16623             }
16624             time = -1; sec = -1; deci = -1;
16625             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16626                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16627                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16628                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16629                 return text;
16630             }
16631
16632             if( score_lo < 0 || score_lo >= 100 ) {
16633                 return text;
16634             }
16635
16636             if(sec >= 0) time = 600*time + 10*sec; else
16637             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16638
16639             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16640
16641             /* [HGM] PV time: now locate end of PV info */
16642             while( *++sep >= '0' && *sep <= '9'); // strip depth
16643             if(time >= 0)
16644             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16645             if(sec >= 0)
16646             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16647             if(deci >= 0)
16648             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16649             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16650         }
16651
16652         if( depth <= 0 ) {
16653             return text;
16654         }
16655
16656         if( time < 0 ) {
16657             time = -1;
16658         }
16659
16660         pvInfoList[index-1].depth = depth;
16661         pvInfoList[index-1].score = score;
16662         pvInfoList[index-1].time  = 10*time; // centi-sec
16663         if(*sep == '}') *sep = 0; else *--sep = '{';
16664         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16665     }
16666     return sep;
16667 }
16668
16669 void
16670 SendToProgram (char *message, ChessProgramState *cps)
16671 {
16672     int count, outCount, error;
16673     char buf[MSG_SIZ];
16674
16675     if (cps->pr == NoProc) return;
16676     Attention(cps);
16677
16678     if (appData.debugMode) {
16679         TimeMark now;
16680         GetTimeMark(&now);
16681         fprintf(debugFP, "%ld >%-6s: %s",
16682                 SubtractTimeMarks(&now, &programStartTime),
16683                 cps->which, message);
16684         if(serverFP)
16685             fprintf(serverFP, "%ld >%-6s: %s",
16686                 SubtractTimeMarks(&now, &programStartTime),
16687                 cps->which, message), fflush(serverFP);
16688     }
16689
16690     count = strlen(message);
16691     outCount = OutputToProcess(cps->pr, message, count, &error);
16692     if (outCount < count && !exiting
16693                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16694       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16695       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16696         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16697             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16698                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16699                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16700                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16701             } else {
16702                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16703                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16704                 gameInfo.result = res;
16705             }
16706             gameInfo.resultDetails = StrSave(buf);
16707         }
16708         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16709         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16710     }
16711 }
16712
16713 void
16714 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16715 {
16716     char *end_str;
16717     char buf[MSG_SIZ];
16718     ChessProgramState *cps = (ChessProgramState *)closure;
16719
16720     if (isr != cps->isr) return; /* Killed intentionally */
16721     if (count <= 0) {
16722         if (count == 0) {
16723             RemoveInputSource(cps->isr);
16724             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16725                     _(cps->which), cps->program);
16726             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
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, 0, 1); else errorExitStatus = 1;
16741         } else {
16742             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16743                     _(cps->which), cps->program);
16744             RemoveInputSource(cps->isr);
16745
16746             /* [AS] Program is misbehaving badly... kill it */
16747             if( count == -2 ) {
16748                 DestroyChildProcess( cps->pr, 9 );
16749                 cps->pr = NoProc;
16750             }
16751
16752             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16753         }
16754         return;
16755     }
16756
16757     if ((end_str = strchr(message, '\r')) != NULL)
16758       *end_str = NULLCHAR;
16759     if ((end_str = strchr(message, '\n')) != NULL)
16760       *end_str = NULLCHAR;
16761
16762     if (appData.debugMode) {
16763         TimeMark now; int print = 1;
16764         char *quote = ""; char c; int i;
16765
16766         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16767                 char start = message[0];
16768                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16769                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16770                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16771                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16772                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16773                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16774                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16775                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16776                    sscanf(message, "hint: %c", &c)!=1 &&
16777                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16778                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16779                     print = (appData.engineComments >= 2);
16780                 }
16781                 message[0] = start; // restore original message
16782         }
16783         if(print) {
16784                 GetTimeMark(&now);
16785                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16786                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16787                         quote,
16788                         message);
16789                 if(serverFP)
16790                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16791                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16792                         quote,
16793                         message), fflush(serverFP);
16794         }
16795     }
16796
16797     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16798     if (appData.icsEngineAnalyze) {
16799         if (strstr(message, "whisper") != NULL ||
16800              strstr(message, "kibitz") != NULL ||
16801             strstr(message, "tellics") != NULL) return;
16802     }
16803
16804     HandleMachineMove(message, cps);
16805 }
16806
16807
16808 void
16809 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16810 {
16811     char buf[MSG_SIZ];
16812     int seconds;
16813
16814     if( timeControl_2 > 0 ) {
16815         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16816             tc = timeControl_2;
16817         }
16818     }
16819     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16820     inc /= cps->timeOdds;
16821     st  /= cps->timeOdds;
16822
16823     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16824
16825     if (st > 0) {
16826       /* Set exact time per move, normally using st command */
16827       if (cps->stKludge) {
16828         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16829         seconds = st % 60;
16830         if (seconds == 0) {
16831           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16832         } else {
16833           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16834         }
16835       } else {
16836         snprintf(buf, MSG_SIZ, "st %d\n", st);
16837       }
16838     } else {
16839       /* Set conventional or incremental time control, using level command */
16840       if (seconds == 0) {
16841         /* Note old gnuchess bug -- minutes:seconds used to not work.
16842            Fixed in later versions, but still avoid :seconds
16843            when seconds is 0. */
16844         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16845       } else {
16846         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16847                  seconds, inc/1000.);
16848       }
16849     }
16850     SendToProgram(buf, cps);
16851
16852     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16853     /* Orthogonally, limit search to given depth */
16854     if (sd > 0) {
16855       if (cps->sdKludge) {
16856         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16857       } else {
16858         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16859       }
16860       SendToProgram(buf, cps);
16861     }
16862
16863     if(cps->nps >= 0) { /* [HGM] nps */
16864         if(cps->supportsNPS == FALSE)
16865           cps->nps = -1; // don't use if engine explicitly says not supported!
16866         else {
16867           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16868           SendToProgram(buf, cps);
16869         }
16870     }
16871 }
16872
16873 ChessProgramState *
16874 WhitePlayer ()
16875 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16876 {
16877     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16878        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16879         return &second;
16880     return &first;
16881 }
16882
16883 void
16884 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16885 {
16886     char message[MSG_SIZ];
16887     long time, otime;
16888
16889     /* Note: this routine must be called when the clocks are stopped
16890        or when they have *just* been set or switched; otherwise
16891        it will be off by the time since the current tick started.
16892     */
16893     if (machineWhite) {
16894         time = whiteTimeRemaining / 10;
16895         otime = blackTimeRemaining / 10;
16896     } else {
16897         time = blackTimeRemaining / 10;
16898         otime = whiteTimeRemaining / 10;
16899     }
16900     /* [HGM] translate opponent's time by time-odds factor */
16901     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16902
16903     if (time <= 0) time = 1;
16904     if (otime <= 0) otime = 1;
16905
16906     snprintf(message, MSG_SIZ, "time %ld\n", time);
16907     SendToProgram(message, cps);
16908
16909     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16910     SendToProgram(message, cps);
16911 }
16912
16913 char *
16914 EngineDefinedVariant (ChessProgramState *cps, int n)
16915 {   // return name of n-th unknown variant that engine supports
16916     static char buf[MSG_SIZ];
16917     char *p, *s = cps->variants;
16918     if(!s) return NULL;
16919     do { // parse string from variants feature
16920       VariantClass v;
16921         p = strchr(s, ',');
16922         if(p) *p = NULLCHAR;
16923       v = StringToVariant(s);
16924       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16925         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16926             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16927                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16928                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16929                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16930             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16931         }
16932         if(p) *p++ = ',';
16933         if(n < 0) return buf;
16934     } while(s = p);
16935     return NULL;
16936 }
16937
16938 int
16939 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16940 {
16941   char buf[MSG_SIZ];
16942   int len = strlen(name);
16943   int val;
16944
16945   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16946     (*p) += len + 1;
16947     sscanf(*p, "%d", &val);
16948     *loc = (val != 0);
16949     while (**p && **p != ' ')
16950       (*p)++;
16951     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16952     SendToProgram(buf, cps);
16953     return TRUE;
16954   }
16955   return FALSE;
16956 }
16957
16958 int
16959 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16960 {
16961   char buf[MSG_SIZ];
16962   int len = strlen(name);
16963   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16964     (*p) += len + 1;
16965     sscanf(*p, "%d", loc);
16966     while (**p && **p != ' ') (*p)++;
16967     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16968     SendToProgram(buf, cps);
16969     return TRUE;
16970   }
16971   return FALSE;
16972 }
16973
16974 int
16975 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16976 {
16977   char buf[MSG_SIZ];
16978   int len = strlen(name);
16979   if (strncmp((*p), name, len) == 0
16980       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16981     (*p) += len + 2;
16982     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16983     sscanf(*p, "%[^\"]", *loc);
16984     while (**p && **p != '\"') (*p)++;
16985     if (**p == '\"') (*p)++;
16986     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16987     SendToProgram(buf, cps);
16988     return TRUE;
16989   }
16990   return FALSE;
16991 }
16992
16993 int
16994 ParseOption (Option *opt, ChessProgramState *cps)
16995 // [HGM] options: process the string that defines an engine option, and determine
16996 // name, type, default value, and allowed value range
16997 {
16998         char *p, *q, buf[MSG_SIZ];
16999         int n, min = (-1)<<31, max = 1<<31, def;
17000
17001         opt->target = &opt->value;   // OK for spin/slider and checkbox
17002         if(p = strstr(opt->name, " -spin ")) {
17003             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17004             if(max < min) max = min; // enforce consistency
17005             if(def < min) def = min;
17006             if(def > max) def = max;
17007             opt->value = def;
17008             opt->min = min;
17009             opt->max = max;
17010             opt->type = Spin;
17011         } else if((p = strstr(opt->name, " -slider "))) {
17012             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17013             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17014             if(max < min) max = min; // enforce consistency
17015             if(def < min) def = min;
17016             if(def > max) def = max;
17017             opt->value = def;
17018             opt->min = min;
17019             opt->max = max;
17020             opt->type = Spin; // Slider;
17021         } else if((p = strstr(opt->name, " -string "))) {
17022             opt->textValue = p+9;
17023             opt->type = TextBox;
17024             opt->target = &opt->textValue;
17025         } else if((p = strstr(opt->name, " -file "))) {
17026             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17027             opt->target = opt->textValue = p+7;
17028             opt->type = FileName; // FileName;
17029             opt->target = &opt->textValue;
17030         } else if((p = strstr(opt->name, " -path "))) {
17031             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17032             opt->target = opt->textValue = p+7;
17033             opt->type = PathName; // PathName;
17034             opt->target = &opt->textValue;
17035         } else if(p = strstr(opt->name, " -check ")) {
17036             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17037             opt->value = (def != 0);
17038             opt->type = CheckBox;
17039         } else if(p = strstr(opt->name, " -combo ")) {
17040             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17041             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17042             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17043             opt->value = n = 0;
17044             while(q = StrStr(q, " /// ")) {
17045                 n++; *q = 0;    // count choices, and null-terminate each of them
17046                 q += 5;
17047                 if(*q == '*') { // remember default, which is marked with * prefix
17048                     q++;
17049                     opt->value = n;
17050                 }
17051                 cps->comboList[cps->comboCnt++] = q;
17052             }
17053             cps->comboList[cps->comboCnt++] = NULL;
17054             opt->max = n + 1;
17055             opt->type = ComboBox;
17056         } else if(p = strstr(opt->name, " -button")) {
17057             opt->type = Button;
17058         } else if(p = strstr(opt->name, " -save")) {
17059             opt->type = SaveButton;
17060         } else return FALSE;
17061         *p = 0; // terminate option name
17062         // now look if the command-line options define a setting for this engine option.
17063         if(cps->optionSettings && cps->optionSettings[0])
17064             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17065         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17066           snprintf(buf, MSG_SIZ, "option %s", p);
17067                 if(p = strstr(buf, ",")) *p = 0;
17068                 if(q = strchr(buf, '=')) switch(opt->type) {
17069                     case ComboBox:
17070                         for(n=0; n<opt->max; n++)
17071                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17072                         break;
17073                     case TextBox:
17074                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17075                         break;
17076                     case Spin:
17077                     case CheckBox:
17078                         opt->value = atoi(q+1);
17079                     default:
17080                         break;
17081                 }
17082                 strcat(buf, "\n");
17083                 SendToProgram(buf, cps);
17084         }
17085         return TRUE;
17086 }
17087
17088 void
17089 FeatureDone (ChessProgramState *cps, int val)
17090 {
17091   DelayedEventCallback cb = GetDelayedEvent();
17092   if ((cb == InitBackEnd3 && cps == &first) ||
17093       (cb == SettingsMenuIfReady && cps == &second) ||
17094       (cb == LoadEngine) ||
17095       (cb == TwoMachinesEventIfReady)) {
17096     CancelDelayedEvent();
17097     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17098   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17099   cps->initDone = val;
17100   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17101 }
17102
17103 /* Parse feature command from engine */
17104 void
17105 ParseFeatures (char *args, ChessProgramState *cps)
17106 {
17107   char *p = args;
17108   char *q = NULL;
17109   int val;
17110   char buf[MSG_SIZ];
17111
17112   for (;;) {
17113     while (*p == ' ') p++;
17114     if (*p == NULLCHAR) return;
17115
17116     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17117     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17118     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17119     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17120     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17121     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17122     if (BoolFeature(&p, "reuse", &val, cps)) {
17123       /* Engine can disable reuse, but can't enable it if user said no */
17124       if (!val) cps->reuse = FALSE;
17125       continue;
17126     }
17127     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17128     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17129       if (gameMode == TwoMachinesPlay) {
17130         DisplayTwoMachinesTitle();
17131       } else {
17132         DisplayTitle("");
17133       }
17134       continue;
17135     }
17136     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17137     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17138     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17139     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17140     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17141     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17142     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17143     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17144     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17145     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17146     if (IntFeature(&p, "done", &val, cps)) {
17147       FeatureDone(cps, val);
17148       continue;
17149     }
17150     /* Added by Tord: */
17151     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17152     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17153     /* End of additions by Tord */
17154
17155     /* [HGM] added features: */
17156     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17157     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17158     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17159     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17160     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17161     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17162     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17163     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17164         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17165         FREE(cps->option[cps->nrOptions].name);
17166         cps->option[cps->nrOptions].name = q; q = NULL;
17167         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17168           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17169             SendToProgram(buf, cps);
17170             continue;
17171         }
17172         if(cps->nrOptions >= MAX_OPTIONS) {
17173             cps->nrOptions--;
17174             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17175             DisplayError(buf, 0);
17176         }
17177         continue;
17178     }
17179     /* End of additions by HGM */
17180
17181     /* unknown feature: complain and skip */
17182     q = p;
17183     while (*q && *q != '=') q++;
17184     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17185     SendToProgram(buf, cps);
17186     p = q;
17187     if (*p == '=') {
17188       p++;
17189       if (*p == '\"') {
17190         p++;
17191         while (*p && *p != '\"') p++;
17192         if (*p == '\"') p++;
17193       } else {
17194         while (*p && *p != ' ') p++;
17195       }
17196     }
17197   }
17198
17199 }
17200
17201 void
17202 PeriodicUpdatesEvent (int newState)
17203 {
17204     if (newState == appData.periodicUpdates)
17205       return;
17206
17207     appData.periodicUpdates=newState;
17208
17209     /* Display type changes, so update it now */
17210 //    DisplayAnalysis();
17211
17212     /* Get the ball rolling again... */
17213     if (newState) {
17214         AnalysisPeriodicEvent(1);
17215         StartAnalysisClock();
17216     }
17217 }
17218
17219 void
17220 PonderNextMoveEvent (int newState)
17221 {
17222     if (newState == appData.ponderNextMove) return;
17223     if (gameMode == EditPosition) EditPositionDone(TRUE);
17224     if (newState) {
17225         SendToProgram("hard\n", &first);
17226         if (gameMode == TwoMachinesPlay) {
17227             SendToProgram("hard\n", &second);
17228         }
17229     } else {
17230         SendToProgram("easy\n", &first);
17231         thinkOutput[0] = NULLCHAR;
17232         if (gameMode == TwoMachinesPlay) {
17233             SendToProgram("easy\n", &second);
17234         }
17235     }
17236     appData.ponderNextMove = newState;
17237 }
17238
17239 void
17240 NewSettingEvent (int option, int *feature, char *command, int value)
17241 {
17242     char buf[MSG_SIZ];
17243
17244     if (gameMode == EditPosition) EditPositionDone(TRUE);
17245     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17246     if(feature == NULL || *feature) SendToProgram(buf, &first);
17247     if (gameMode == TwoMachinesPlay) {
17248         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17249     }
17250 }
17251
17252 void
17253 ShowThinkingEvent ()
17254 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17255 {
17256     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17257     int newState = appData.showThinking
17258         // [HGM] thinking: other features now need thinking output as well
17259         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17260
17261     if (oldState == newState) return;
17262     oldState = newState;
17263     if (gameMode == EditPosition) EditPositionDone(TRUE);
17264     if (oldState) {
17265         SendToProgram("post\n", &first);
17266         if (gameMode == TwoMachinesPlay) {
17267             SendToProgram("post\n", &second);
17268         }
17269     } else {
17270         SendToProgram("nopost\n", &first);
17271         thinkOutput[0] = NULLCHAR;
17272         if (gameMode == TwoMachinesPlay) {
17273             SendToProgram("nopost\n", &second);
17274         }
17275     }
17276 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17277 }
17278
17279 void
17280 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17281 {
17282   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17283   if (pr == NoProc) return;
17284   AskQuestion(title, question, replyPrefix, pr);
17285 }
17286
17287 void
17288 TypeInEvent (char firstChar)
17289 {
17290     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17291         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17292         gameMode == AnalyzeMode || gameMode == EditGame ||
17293         gameMode == EditPosition || gameMode == IcsExamining ||
17294         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17295         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17296                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17297                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17298         gameMode == Training) PopUpMoveDialog(firstChar);
17299 }
17300
17301 void
17302 TypeInDoneEvent (char *move)
17303 {
17304         Board board;
17305         int n, fromX, fromY, toX, toY;
17306         char promoChar;
17307         ChessMove moveType;
17308
17309         // [HGM] FENedit
17310         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17311                 EditPositionPasteFEN(move);
17312                 return;
17313         }
17314         // [HGM] movenum: allow move number to be typed in any mode
17315         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17316           ToNrEvent(2*n-1);
17317           return;
17318         }
17319         // undocumented kludge: allow command-line option to be typed in!
17320         // (potentially fatal, and does not implement the effect of the option.)
17321         // should only be used for options that are values on which future decisions will be made,
17322         // and definitely not on options that would be used during initialization.
17323         if(strstr(move, "!!! -") == move) {
17324             ParseArgsFromString(move+4);
17325             return;
17326         }
17327
17328       if (gameMode != EditGame && currentMove != forwardMostMove &&
17329         gameMode != Training) {
17330         DisplayMoveError(_("Displayed move is not current"));
17331       } else {
17332         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17333           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17334         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17335         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17336           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17337           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17338         } else {
17339           DisplayMoveError(_("Could not parse move"));
17340         }
17341       }
17342 }
17343
17344 void
17345 DisplayMove (int moveNumber)
17346 {
17347     char message[MSG_SIZ];
17348     char res[MSG_SIZ];
17349     char cpThinkOutput[MSG_SIZ];
17350
17351     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17352
17353     if (moveNumber == forwardMostMove - 1 ||
17354         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17355
17356         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17357
17358         if (strchr(cpThinkOutput, '\n')) {
17359             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17360         }
17361     } else {
17362         *cpThinkOutput = NULLCHAR;
17363     }
17364
17365     /* [AS] Hide thinking from human user */
17366     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17367         *cpThinkOutput = NULLCHAR;
17368         if( thinkOutput[0] != NULLCHAR ) {
17369             int i;
17370
17371             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17372                 cpThinkOutput[i] = '.';
17373             }
17374             cpThinkOutput[i] = NULLCHAR;
17375             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17376         }
17377     }
17378
17379     if (moveNumber == forwardMostMove - 1 &&
17380         gameInfo.resultDetails != NULL) {
17381         if (gameInfo.resultDetails[0] == NULLCHAR) {
17382           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17383         } else {
17384           snprintf(res, MSG_SIZ, " {%s} %s",
17385                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17386         }
17387     } else {
17388         res[0] = NULLCHAR;
17389     }
17390
17391     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17392         DisplayMessage(res, cpThinkOutput);
17393     } else {
17394       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17395                 WhiteOnMove(moveNumber) ? " " : ".. ",
17396                 parseList[moveNumber], res);
17397         DisplayMessage(message, cpThinkOutput);
17398     }
17399 }
17400
17401 void
17402 DisplayComment (int moveNumber, char *text)
17403 {
17404     char title[MSG_SIZ];
17405
17406     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17407       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17408     } else {
17409       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17410               WhiteOnMove(moveNumber) ? " " : ".. ",
17411               parseList[moveNumber]);
17412     }
17413     if (text != NULL && (appData.autoDisplayComment || commentUp))
17414         CommentPopUp(title, text);
17415 }
17416
17417 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17418  * might be busy thinking or pondering.  It can be omitted if your
17419  * gnuchess is configured to stop thinking immediately on any user
17420  * input.  However, that gnuchess feature depends on the FIONREAD
17421  * ioctl, which does not work properly on some flavors of Unix.
17422  */
17423 void
17424 Attention (ChessProgramState *cps)
17425 {
17426 #if ATTENTION
17427     if (!cps->useSigint) return;
17428     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17429     switch (gameMode) {
17430       case MachinePlaysWhite:
17431       case MachinePlaysBlack:
17432       case TwoMachinesPlay:
17433       case IcsPlayingWhite:
17434       case IcsPlayingBlack:
17435       case AnalyzeMode:
17436       case AnalyzeFile:
17437         /* Skip if we know it isn't thinking */
17438         if (!cps->maybeThinking) return;
17439         if (appData.debugMode)
17440           fprintf(debugFP, "Interrupting %s\n", cps->which);
17441         InterruptChildProcess(cps->pr);
17442         cps->maybeThinking = FALSE;
17443         break;
17444       default:
17445         break;
17446     }
17447 #endif /*ATTENTION*/
17448 }
17449
17450 int
17451 CheckFlags ()
17452 {
17453     if (whiteTimeRemaining <= 0) {
17454         if (!whiteFlag) {
17455             whiteFlag = TRUE;
17456             if (appData.icsActive) {
17457                 if (appData.autoCallFlag &&
17458                     gameMode == IcsPlayingBlack && !blackFlag) {
17459                   SendToICS(ics_prefix);
17460                   SendToICS("flag\n");
17461                 }
17462             } else {
17463                 if (blackFlag) {
17464                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17465                 } else {
17466                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17467                     if (appData.autoCallFlag) {
17468                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17469                         return TRUE;
17470                     }
17471                 }
17472             }
17473         }
17474     }
17475     if (blackTimeRemaining <= 0) {
17476         if (!blackFlag) {
17477             blackFlag = TRUE;
17478             if (appData.icsActive) {
17479                 if (appData.autoCallFlag &&
17480                     gameMode == IcsPlayingWhite && !whiteFlag) {
17481                   SendToICS(ics_prefix);
17482                   SendToICS("flag\n");
17483                 }
17484             } else {
17485                 if (whiteFlag) {
17486                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17487                 } else {
17488                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17489                     if (appData.autoCallFlag) {
17490                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17491                         return TRUE;
17492                     }
17493                 }
17494             }
17495         }
17496     }
17497     return FALSE;
17498 }
17499
17500 void
17501 CheckTimeControl ()
17502 {
17503     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17504         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17505
17506     /*
17507      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17508      */
17509     if ( !WhiteOnMove(forwardMostMove) ) {
17510         /* White made time control */
17511         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17512         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17513         /* [HGM] time odds: correct new time quota for time odds! */
17514                                             / WhitePlayer()->timeOdds;
17515         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17516     } else {
17517         lastBlack -= blackTimeRemaining;
17518         /* Black made time control */
17519         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17520                                             / WhitePlayer()->other->timeOdds;
17521         lastWhite = whiteTimeRemaining;
17522     }
17523 }
17524
17525 void
17526 DisplayBothClocks ()
17527 {
17528     int wom = gameMode == EditPosition ?
17529       !blackPlaysFirst : WhiteOnMove(currentMove);
17530     DisplayWhiteClock(whiteTimeRemaining, wom);
17531     DisplayBlackClock(blackTimeRemaining, !wom);
17532 }
17533
17534
17535 /* Timekeeping seems to be a portability nightmare.  I think everyone
17536    has ftime(), but I'm really not sure, so I'm including some ifdefs
17537    to use other calls if you don't.  Clocks will be less accurate if
17538    you have neither ftime nor gettimeofday.
17539 */
17540
17541 /* VS 2008 requires the #include outside of the function */
17542 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17543 #include <sys/timeb.h>
17544 #endif
17545
17546 /* Get the current time as a TimeMark */
17547 void
17548 GetTimeMark (TimeMark *tm)
17549 {
17550 #if HAVE_GETTIMEOFDAY
17551
17552     struct timeval timeVal;
17553     struct timezone timeZone;
17554
17555     gettimeofday(&timeVal, &timeZone);
17556     tm->sec = (long) timeVal.tv_sec;
17557     tm->ms = (int) (timeVal.tv_usec / 1000L);
17558
17559 #else /*!HAVE_GETTIMEOFDAY*/
17560 #if HAVE_FTIME
17561
17562 // include <sys/timeb.h> / moved to just above start of function
17563     struct timeb timeB;
17564
17565     ftime(&timeB);
17566     tm->sec = (long) timeB.time;
17567     tm->ms = (int) timeB.millitm;
17568
17569 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17570     tm->sec = (long) time(NULL);
17571     tm->ms = 0;
17572 #endif
17573 #endif
17574 }
17575
17576 /* Return the difference in milliseconds between two
17577    time marks.  We assume the difference will fit in a long!
17578 */
17579 long
17580 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17581 {
17582     return 1000L*(tm2->sec - tm1->sec) +
17583            (long) (tm2->ms - tm1->ms);
17584 }
17585
17586
17587 /*
17588  * Code to manage the game clocks.
17589  *
17590  * In tournament play, black starts the clock and then white makes a move.
17591  * We give the human user a slight advantage if he is playing white---the
17592  * clocks don't run until he makes his first move, so it takes zero time.
17593  * Also, we don't account for network lag, so we could get out of sync
17594  * with GNU Chess's clock -- but then, referees are always right.
17595  */
17596
17597 static TimeMark tickStartTM;
17598 static long intendedTickLength;
17599
17600 long
17601 NextTickLength (long timeRemaining)
17602 {
17603     long nominalTickLength, nextTickLength;
17604
17605     if (timeRemaining > 0L && timeRemaining <= 10000L)
17606       nominalTickLength = 100L;
17607     else
17608       nominalTickLength = 1000L;
17609     nextTickLength = timeRemaining % nominalTickLength;
17610     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17611
17612     return nextTickLength;
17613 }
17614
17615 /* Adjust clock one minute up or down */
17616 void
17617 AdjustClock (Boolean which, int dir)
17618 {
17619     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17620     if(which) blackTimeRemaining += 60000*dir;
17621     else      whiteTimeRemaining += 60000*dir;
17622     DisplayBothClocks();
17623     adjustedClock = TRUE;
17624 }
17625
17626 /* Stop clocks and reset to a fresh time control */
17627 void
17628 ResetClocks ()
17629 {
17630     (void) StopClockTimer();
17631     if (appData.icsActive) {
17632         whiteTimeRemaining = blackTimeRemaining = 0;
17633     } else if (searchTime) {
17634         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17635         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17636     } else { /* [HGM] correct new time quote for time odds */
17637         whiteTC = blackTC = fullTimeControlString;
17638         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17639         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17640     }
17641     if (whiteFlag || blackFlag) {
17642         DisplayTitle("");
17643         whiteFlag = blackFlag = FALSE;
17644     }
17645     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17646     DisplayBothClocks();
17647     adjustedClock = FALSE;
17648 }
17649
17650 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17651
17652 /* Decrement running clock by amount of time that has passed */
17653 void
17654 DecrementClocks ()
17655 {
17656     long timeRemaining;
17657     long lastTickLength, fudge;
17658     TimeMark now;
17659
17660     if (!appData.clockMode) return;
17661     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17662
17663     GetTimeMark(&now);
17664
17665     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17666
17667     /* Fudge if we woke up a little too soon */
17668     fudge = intendedTickLength - lastTickLength;
17669     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17670
17671     if (WhiteOnMove(forwardMostMove)) {
17672         if(whiteNPS >= 0) lastTickLength = 0;
17673         timeRemaining = whiteTimeRemaining -= lastTickLength;
17674         if(timeRemaining < 0 && !appData.icsActive) {
17675             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17676             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17677                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17678                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17679             }
17680         }
17681         DisplayWhiteClock(whiteTimeRemaining - fudge,
17682                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17683     } else {
17684         if(blackNPS >= 0) lastTickLength = 0;
17685         timeRemaining = blackTimeRemaining -= lastTickLength;
17686         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17687             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17688             if(suddenDeath) {
17689                 blackStartMove = forwardMostMove;
17690                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17691             }
17692         }
17693         DisplayBlackClock(blackTimeRemaining - fudge,
17694                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17695     }
17696     if (CheckFlags()) return;
17697
17698     if(twoBoards) { // count down secondary board's clocks as well
17699         activePartnerTime -= lastTickLength;
17700         partnerUp = 1;
17701         if(activePartner == 'W')
17702             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17703         else
17704             DisplayBlackClock(activePartnerTime, TRUE);
17705         partnerUp = 0;
17706     }
17707
17708     tickStartTM = now;
17709     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17710     StartClockTimer(intendedTickLength);
17711
17712     /* if the time remaining has fallen below the alarm threshold, sound the
17713      * alarm. if the alarm has sounded and (due to a takeback or time control
17714      * with increment) the time remaining has increased to a level above the
17715      * threshold, reset the alarm so it can sound again.
17716      */
17717
17718     if (appData.icsActive && appData.icsAlarm) {
17719
17720         /* make sure we are dealing with the user's clock */
17721         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17722                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17723            )) return;
17724
17725         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17726             alarmSounded = FALSE;
17727         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17728             PlayAlarmSound();
17729             alarmSounded = TRUE;
17730         }
17731     }
17732 }
17733
17734
17735 /* A player has just moved, so stop the previously running
17736    clock and (if in clock mode) start the other one.
17737    We redisplay both clocks in case we're in ICS mode, because
17738    ICS gives us an update to both clocks after every move.
17739    Note that this routine is called *after* forwardMostMove
17740    is updated, so the last fractional tick must be subtracted
17741    from the color that is *not* on move now.
17742 */
17743 void
17744 SwitchClocks (int newMoveNr)
17745 {
17746     long lastTickLength;
17747     TimeMark now;
17748     int flagged = FALSE;
17749
17750     GetTimeMark(&now);
17751
17752     if (StopClockTimer() && appData.clockMode) {
17753         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17754         if (!WhiteOnMove(forwardMostMove)) {
17755             if(blackNPS >= 0) lastTickLength = 0;
17756             blackTimeRemaining -= lastTickLength;
17757            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17758 //         if(pvInfoList[forwardMostMove].time == -1)
17759                  pvInfoList[forwardMostMove].time =               // use GUI time
17760                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17761         } else {
17762            if(whiteNPS >= 0) lastTickLength = 0;
17763            whiteTimeRemaining -= lastTickLength;
17764            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17765 //         if(pvInfoList[forwardMostMove].time == -1)
17766                  pvInfoList[forwardMostMove].time =
17767                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17768         }
17769         flagged = CheckFlags();
17770     }
17771     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17772     CheckTimeControl();
17773
17774     if (flagged || !appData.clockMode) return;
17775
17776     switch (gameMode) {
17777       case MachinePlaysBlack:
17778       case MachinePlaysWhite:
17779       case BeginningOfGame:
17780         if (pausing) return;
17781         break;
17782
17783       case EditGame:
17784       case PlayFromGameFile:
17785       case IcsExamining:
17786         return;
17787
17788       default:
17789         break;
17790     }
17791
17792     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17793         if(WhiteOnMove(forwardMostMove))
17794              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17795         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17796     }
17797
17798     tickStartTM = now;
17799     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17800       whiteTimeRemaining : blackTimeRemaining);
17801     StartClockTimer(intendedTickLength);
17802 }
17803
17804
17805 /* Stop both clocks */
17806 void
17807 StopClocks ()
17808 {
17809     long lastTickLength;
17810     TimeMark now;
17811
17812     if (!StopClockTimer()) return;
17813     if (!appData.clockMode) return;
17814
17815     GetTimeMark(&now);
17816
17817     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17818     if (WhiteOnMove(forwardMostMove)) {
17819         if(whiteNPS >= 0) lastTickLength = 0;
17820         whiteTimeRemaining -= lastTickLength;
17821         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17822     } else {
17823         if(blackNPS >= 0) lastTickLength = 0;
17824         blackTimeRemaining -= lastTickLength;
17825         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17826     }
17827     CheckFlags();
17828 }
17829
17830 /* Start clock of player on move.  Time may have been reset, so
17831    if clock is already running, stop and restart it. */
17832 void
17833 StartClocks ()
17834 {
17835     (void) StopClockTimer(); /* in case it was running already */
17836     DisplayBothClocks();
17837     if (CheckFlags()) return;
17838
17839     if (!appData.clockMode) return;
17840     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17841
17842     GetTimeMark(&tickStartTM);
17843     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17844       whiteTimeRemaining : blackTimeRemaining);
17845
17846    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17847     whiteNPS = blackNPS = -1;
17848     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17849        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17850         whiteNPS = first.nps;
17851     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17852        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17853         blackNPS = first.nps;
17854     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17855         whiteNPS = second.nps;
17856     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17857         blackNPS = second.nps;
17858     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17859
17860     StartClockTimer(intendedTickLength);
17861 }
17862
17863 char *
17864 TimeString (long ms)
17865 {
17866     long second, minute, hour, day;
17867     char *sign = "";
17868     static char buf[32];
17869
17870     if (ms > 0 && ms <= 9900) {
17871       /* convert milliseconds to tenths, rounding up */
17872       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17873
17874       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17875       return buf;
17876     }
17877
17878     /* convert milliseconds to seconds, rounding up */
17879     /* use floating point to avoid strangeness of integer division
17880        with negative dividends on many machines */
17881     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17882
17883     if (second < 0) {
17884         sign = "-";
17885         second = -second;
17886     }
17887
17888     day = second / (60 * 60 * 24);
17889     second = second % (60 * 60 * 24);
17890     hour = second / (60 * 60);
17891     second = second % (60 * 60);
17892     minute = second / 60;
17893     second = second % 60;
17894
17895     if (day > 0)
17896       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17897               sign, day, hour, minute, second);
17898     else if (hour > 0)
17899       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17900     else
17901       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17902
17903     return buf;
17904 }
17905
17906
17907 /*
17908  * This is necessary because some C libraries aren't ANSI C compliant yet.
17909  */
17910 char *
17911 StrStr (char *string, char *match)
17912 {
17913     int i, length;
17914
17915     length = strlen(match);
17916
17917     for (i = strlen(string) - length; i >= 0; i--, string++)
17918       if (!strncmp(match, string, length))
17919         return string;
17920
17921     return NULL;
17922 }
17923
17924 char *
17925 StrCaseStr (char *string, char *match)
17926 {
17927     int i, j, length;
17928
17929     length = strlen(match);
17930
17931     for (i = strlen(string) - length; i >= 0; i--, string++) {
17932         for (j = 0; j < length; j++) {
17933             if (ToLower(match[j]) != ToLower(string[j]))
17934               break;
17935         }
17936         if (j == length) return string;
17937     }
17938
17939     return NULL;
17940 }
17941
17942 #ifndef _amigados
17943 int
17944 StrCaseCmp (char *s1, char *s2)
17945 {
17946     char c1, c2;
17947
17948     for (;;) {
17949         c1 = ToLower(*s1++);
17950         c2 = ToLower(*s2++);
17951         if (c1 > c2) return 1;
17952         if (c1 < c2) return -1;
17953         if (c1 == NULLCHAR) return 0;
17954     }
17955 }
17956
17957
17958 int
17959 ToLower (int c)
17960 {
17961     return isupper(c) ? tolower(c) : c;
17962 }
17963
17964
17965 int
17966 ToUpper (int c)
17967 {
17968     return islower(c) ? toupper(c) : c;
17969 }
17970 #endif /* !_amigados    */
17971
17972 char *
17973 StrSave (char *s)
17974 {
17975   char *ret;
17976
17977   if ((ret = (char *) malloc(strlen(s) + 1)))
17978     {
17979       safeStrCpy(ret, s, strlen(s)+1);
17980     }
17981   return ret;
17982 }
17983
17984 char *
17985 StrSavePtr (char *s, char **savePtr)
17986 {
17987     if (*savePtr) {
17988         free(*savePtr);
17989     }
17990     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17991       safeStrCpy(*savePtr, s, strlen(s)+1);
17992     }
17993     return(*savePtr);
17994 }
17995
17996 char *
17997 PGNDate ()
17998 {
17999     time_t clock;
18000     struct tm *tm;
18001     char buf[MSG_SIZ];
18002
18003     clock = time((time_t *)NULL);
18004     tm = localtime(&clock);
18005     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18006             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18007     return StrSave(buf);
18008 }
18009
18010
18011 char *
18012 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18013 {
18014     int i, j, fromX, fromY, toX, toY;
18015     int whiteToPlay, haveRights = nrCastlingRights;
18016     char buf[MSG_SIZ];
18017     char *p, *q;
18018     int emptycount;
18019     ChessSquare piece;
18020
18021     whiteToPlay = (gameMode == EditPosition) ?
18022       !blackPlaysFirst : (move % 2 == 0);
18023     p = buf;
18024
18025     /* Piece placement data */
18026     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18027         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18028         emptycount = 0;
18029         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18030             if (boards[move][i][j] == EmptySquare) {
18031                 emptycount++;
18032             } else { ChessSquare piece = boards[move][i][j];
18033                 if (emptycount > 0) {
18034                     if(emptycount<10) /* [HGM] can be >= 10 */
18035                         *p++ = '0' + emptycount;
18036                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18037                     emptycount = 0;
18038                 }
18039                 if(PieceToChar(piece) == '+') {
18040                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18041                     *p++ = '+';
18042                     piece = (ChessSquare)(CHUDEMOTED(piece));
18043                 }
18044                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18045                 if(*p = PieceSuffix(piece)) p++;
18046                 if(p[-1] == '~') {
18047                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18048                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18049                     *p++ = '~';
18050                 }
18051             }
18052         }
18053         if (emptycount > 0) {
18054             if(emptycount<10) /* [HGM] can be >= 10 */
18055                 *p++ = '0' + emptycount;
18056             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18057             emptycount = 0;
18058         }
18059         *p++ = '/';
18060     }
18061     *(p - 1) = ' ';
18062
18063     /* [HGM] print Crazyhouse or Shogi holdings */
18064     if( gameInfo.holdingsWidth ) {
18065         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18066         q = p;
18067         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18068             piece = boards[move][i][BOARD_WIDTH-1];
18069             if( piece != EmptySquare )
18070               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18071                   *p++ = PieceToChar(piece);
18072         }
18073         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18074             piece = boards[move][BOARD_HEIGHT-i-1][0];
18075             if( piece != EmptySquare )
18076               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18077                   *p++ = PieceToChar(piece);
18078         }
18079
18080         if( q == p ) *p++ = '-';
18081         *p++ = ']';
18082         *p++ = ' ';
18083     }
18084
18085     /* Active color */
18086     *p++ = whiteToPlay ? 'w' : 'b';
18087     *p++ = ' ';
18088
18089   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18090     haveRights = 0; q = p;
18091     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18092       piece = boards[move][0][i];
18093       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18094         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18095       }
18096     }
18097     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18098       piece = boards[move][BOARD_HEIGHT-1][i];
18099       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18100         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18101       }
18102     }
18103     if(p == q) *p++ = '-';
18104     *p++ = ' ';
18105   }
18106
18107   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18108     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18109   } else {
18110   if(haveRights) {
18111      int handW=0, handB=0;
18112      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18113         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18114         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18115      }
18116      q = p;
18117      if(appData.fischerCastling) {
18118         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18119            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18120                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18121         } else {
18122        /* [HGM] write directly from rights */
18123            if(boards[move][CASTLING][2] != NoRights &&
18124               boards[move][CASTLING][0] != NoRights   )
18125                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18126            if(boards[move][CASTLING][2] != NoRights &&
18127               boards[move][CASTLING][1] != NoRights   )
18128                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18129         }
18130         if(handB) {
18131            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18132                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18133         } else {
18134            if(boards[move][CASTLING][5] != NoRights &&
18135               boards[move][CASTLING][3] != NoRights   )
18136                 *p++ = boards[move][CASTLING][3] + AAA;
18137            if(boards[move][CASTLING][5] != NoRights &&
18138               boards[move][CASTLING][4] != NoRights   )
18139                 *p++ = boards[move][CASTLING][4] + AAA;
18140         }
18141      } else {
18142
18143         /* [HGM] write true castling rights */
18144         if( nrCastlingRights == 6 ) {
18145             int q, k=0;
18146             if(boards[move][CASTLING][0] != NoRights &&
18147                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18148             q = (boards[move][CASTLING][1] != NoRights &&
18149                  boards[move][CASTLING][2] != NoRights  );
18150             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18151                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18152                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18153                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18154             }
18155             if(q) *p++ = 'Q';
18156             k = 0;
18157             if(boards[move][CASTLING][3] != NoRights &&
18158                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18159             q = (boards[move][CASTLING][4] != NoRights &&
18160                  boards[move][CASTLING][5] != NoRights  );
18161             if(handB) {
18162                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18163                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18164                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18165             }
18166             if(q) *p++ = 'q';
18167         }
18168      }
18169      if (q == p) *p++ = '-'; /* No castling rights */
18170      *p++ = ' ';
18171   }
18172
18173   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18174      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18175      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18176     /* En passant target square */
18177     if (move > backwardMostMove) {
18178         fromX = moveList[move - 1][0] - AAA;
18179         fromY = moveList[move - 1][1] - ONE;
18180         toX = moveList[move - 1][2] - AAA;
18181         toY = moveList[move - 1][3] - ONE;
18182         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18183             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18184             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18185             fromX == toX) {
18186             /* 2-square pawn move just happened */
18187             *p++ = toX + AAA;
18188             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18189         } else {
18190             *p++ = '-';
18191         }
18192     } else if(move == backwardMostMove) {
18193         // [HGM] perhaps we should always do it like this, and forget the above?
18194         if((signed char)boards[move][EP_STATUS] >= 0) {
18195             *p++ = boards[move][EP_STATUS] + AAA;
18196             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18197         } else {
18198             *p++ = '-';
18199         }
18200     } else {
18201         *p++ = '-';
18202     }
18203     *p++ = ' ';
18204   }
18205   }
18206
18207     if(moveCounts)
18208     {   int i = 0, j=move;
18209
18210         /* [HGM] find reversible plies */
18211         if (appData.debugMode) { int k;
18212             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18213             for(k=backwardMostMove; k<=forwardMostMove; k++)
18214                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18215
18216         }
18217
18218         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18219         if( j == backwardMostMove ) i += initialRulePlies;
18220         sprintf(p, "%d ", i);
18221         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18222
18223         /* Fullmove number */
18224         sprintf(p, "%d", (move / 2) + 1);
18225     } else *--p = NULLCHAR;
18226
18227     return StrSave(buf);
18228 }
18229
18230 Boolean
18231 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18232 {
18233     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18234     char *p, c;
18235     int emptycount, virgin[BOARD_FILES];
18236     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18237
18238     p = fen;
18239
18240     /* Piece placement data */
18241     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18242         j = 0;
18243         for (;;) {
18244             if (*p == '/' || *p == ' ' || *p == '[' ) {
18245                 if(j > w) w = j;
18246                 emptycount = gameInfo.boardWidth - j;
18247                 while (emptycount--)
18248                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18249                 if (*p == '/') p++;
18250                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18251                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18252                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18253                     }
18254                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18255                 }
18256                 break;
18257 #if(BOARD_FILES >= 10)*0
18258             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18259                 p++; emptycount=10;
18260                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18261                 while (emptycount--)
18262                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18263 #endif
18264             } else if (*p == '*') {
18265                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18266             } else if (isdigit(*p)) {
18267                 emptycount = *p++ - '0';
18268                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18269                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18270                 while (emptycount--)
18271                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18272             } else if (*p == '<') {
18273                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18274                 else if (i != 0 || !shuffle) return FALSE;
18275                 p++;
18276             } else if (shuffle && *p == '>') {
18277                 p++; // for now ignore closing shuffle range, and assume rank-end
18278             } else if (*p == '?') {
18279                 if (j >= gameInfo.boardWidth) return FALSE;
18280                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18281                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18282             } else if (*p == '+' || isalpha(*p)) {
18283                 char *q, *s = SUFFIXES;
18284                 if (j >= gameInfo.boardWidth) return FALSE;
18285                 if(*p=='+') {
18286                     char c = *++p;
18287                     if(q = strchr(s, p[1])) p++;
18288                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18289                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18290                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18291                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18292                 } else {
18293                     char c = *p++;
18294                     if(q = strchr(s, *p)) p++;
18295                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18296                 }
18297
18298                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18299                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18300                     piece = (ChessSquare) (PROMOTED(piece));
18301                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18302                     p++;
18303                 }
18304                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18305                 if(piece == king) wKingRank = i;
18306                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18307             } else {
18308                 return FALSE;
18309             }
18310         }
18311     }
18312     while (*p == '/' || *p == ' ') p++;
18313
18314     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18315
18316     /* [HGM] by default clear Crazyhouse holdings, if present */
18317     if(gameInfo.holdingsWidth) {
18318        for(i=0; i<BOARD_HEIGHT; i++) {
18319            board[i][0]             = EmptySquare; /* black holdings */
18320            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18321            board[i][1]             = (ChessSquare) 0; /* black counts */
18322            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18323        }
18324     }
18325
18326     /* [HGM] look for Crazyhouse holdings here */
18327     while(*p==' ') p++;
18328     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18329         int swap=0, wcnt=0, bcnt=0;
18330         if(*p == '[') p++;
18331         if(*p == '<') swap++, p++;
18332         if(*p == '-' ) p++; /* empty holdings */ else {
18333             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18334             /* if we would allow FEN reading to set board size, we would   */
18335             /* have to add holdings and shift the board read so far here   */
18336             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18337                 p++;
18338                 if((int) piece >= (int) BlackPawn ) {
18339                     i = (int)piece - (int)BlackPawn;
18340                     i = PieceToNumber((ChessSquare)i);
18341                     if( i >= gameInfo.holdingsSize ) return FALSE;
18342                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18343                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18344                     bcnt++;
18345                 } else {
18346                     i = (int)piece - (int)WhitePawn;
18347                     i = PieceToNumber((ChessSquare)i);
18348                     if( i >= gameInfo.holdingsSize ) return FALSE;
18349                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18350                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18351                     wcnt++;
18352                 }
18353             }
18354             if(subst) { // substitute back-rank question marks by holdings pieces
18355                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18356                     int k, m, n = bcnt + 1;
18357                     if(board[0][j] == ClearBoard) {
18358                         if(!wcnt) return FALSE;
18359                         n = rand() % wcnt;
18360                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18361                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18362                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18363                             break;
18364                         }
18365                     }
18366                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18367                         if(!bcnt) return FALSE;
18368                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18369                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18370                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18371                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18372                             break;
18373                         }
18374                     }
18375                 }
18376                 subst = 0;
18377             }
18378         }
18379         if(*p == ']') p++;
18380     }
18381
18382     if(subst) return FALSE; // substitution requested, but no holdings
18383
18384     while(*p == ' ') p++;
18385
18386     /* Active color */
18387     c = *p++;
18388     if(appData.colorNickNames) {
18389       if( c == appData.colorNickNames[0] ) c = 'w'; else
18390       if( c == appData.colorNickNames[1] ) c = 'b';
18391     }
18392     switch (c) {
18393       case 'w':
18394         *blackPlaysFirst = FALSE;
18395         break;
18396       case 'b':
18397         *blackPlaysFirst = TRUE;
18398         break;
18399       default:
18400         return FALSE;
18401     }
18402
18403     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18404     /* return the extra info in global variiables             */
18405
18406     while(*p==' ') p++;
18407
18408     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18409         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18410         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18411     }
18412
18413     /* set defaults in case FEN is incomplete */
18414     board[EP_STATUS] = EP_UNKNOWN;
18415     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18416     for(i=0; i<nrCastlingRights; i++ ) {
18417         board[CASTLING][i] =
18418             appData.fischerCastling ? NoRights : initialRights[i];
18419     }   /* assume possible unless obviously impossible */
18420     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18421     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18422     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18423                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18424     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18425     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18426     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18427                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18428     FENrulePlies = 0;
18429
18430     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18431       char *q = p;
18432       int w=0, b=0;
18433       while(isalpha(*p)) {
18434         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18435         if(islower(*p)) b |= 1 << (*p++ - 'a');
18436       }
18437       if(*p == '-') p++;
18438       if(p != q) {
18439         board[TOUCHED_W] = ~w;
18440         board[TOUCHED_B] = ~b;
18441         while(*p == ' ') p++;
18442       }
18443     } else
18444
18445     if(nrCastlingRights) {
18446       int fischer = 0;
18447       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18448       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18449           /* castling indicator present, so default becomes no castlings */
18450           for(i=0; i<nrCastlingRights; i++ ) {
18451                  board[CASTLING][i] = NoRights;
18452           }
18453       }
18454       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18455              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18456              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18457              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18458         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18459
18460         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18461             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18462             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18463         }
18464         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18465             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18466         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18467                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18468         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18469                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18470         switch(c) {
18471           case'K':
18472               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18473               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18474               board[CASTLING][2] = whiteKingFile;
18475               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18476               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18477               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18478               break;
18479           case'Q':
18480               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18481               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18482               board[CASTLING][2] = whiteKingFile;
18483               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18484               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18485               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18486               break;
18487           case'k':
18488               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18489               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18490               board[CASTLING][5] = blackKingFile;
18491               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18492               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18493               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18494               break;
18495           case'q':
18496               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18497               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18498               board[CASTLING][5] = blackKingFile;
18499               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18500               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18501               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18502           case '-':
18503               break;
18504           default: /* FRC castlings */
18505               if(c >= 'a') { /* black rights */
18506                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18507                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18508                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18509                   if(i == BOARD_RGHT) break;
18510                   board[CASTLING][5] = i;
18511                   c -= AAA;
18512                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18513                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18514                   if(c > i)
18515                       board[CASTLING][3] = c;
18516                   else
18517                       board[CASTLING][4] = c;
18518               } else { /* white rights */
18519                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18520                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18521                     if(board[0][i] == WhiteKing) break;
18522                   if(i == BOARD_RGHT) break;
18523                   board[CASTLING][2] = i;
18524                   c -= AAA - 'a' + 'A';
18525                   if(board[0][c] >= WhiteKing) break;
18526                   if(c > i)
18527                       board[CASTLING][0] = c;
18528                   else
18529                       board[CASTLING][1] = c;
18530               }
18531         }
18532       }
18533       for(i=0; i<nrCastlingRights; i++)
18534         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18535       if(gameInfo.variant == VariantSChess)
18536         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18537       if(fischer && shuffle) appData.fischerCastling = TRUE;
18538     if (appData.debugMode) {
18539         fprintf(debugFP, "FEN castling rights:");
18540         for(i=0; i<nrCastlingRights; i++)
18541         fprintf(debugFP, " %d", board[CASTLING][i]);
18542         fprintf(debugFP, "\n");
18543     }
18544
18545       while(*p==' ') p++;
18546     }
18547
18548     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18549
18550     /* read e.p. field in games that know e.p. capture */
18551     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18552        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18553        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18554       if(*p=='-') {
18555         p++; board[EP_STATUS] = EP_NONE;
18556       } else {
18557          char c = *p++ - AAA;
18558
18559          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18560          if(*p >= '0' && *p <='9') p++;
18561          board[EP_STATUS] = c;
18562       }
18563     }
18564
18565
18566     if(sscanf(p, "%d", &i) == 1) {
18567         FENrulePlies = i; /* 50-move ply counter */
18568         /* (The move number is still ignored)    */
18569     }
18570
18571     return TRUE;
18572 }
18573
18574 void
18575 EditPositionPasteFEN (char *fen)
18576 {
18577   if (fen != NULL) {
18578     Board initial_position;
18579
18580     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18581       DisplayError(_("Bad FEN position in clipboard"), 0);
18582       return ;
18583     } else {
18584       int savedBlackPlaysFirst = blackPlaysFirst;
18585       EditPositionEvent();
18586       blackPlaysFirst = savedBlackPlaysFirst;
18587       CopyBoard(boards[0], initial_position);
18588       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18589       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18590       DisplayBothClocks();
18591       DrawPosition(FALSE, boards[currentMove]);
18592     }
18593   }
18594 }
18595
18596 static char cseq[12] = "\\   ";
18597
18598 Boolean
18599 set_cont_sequence (char *new_seq)
18600 {
18601     int len;
18602     Boolean ret;
18603
18604     // handle bad attempts to set the sequence
18605         if (!new_seq)
18606                 return 0; // acceptable error - no debug
18607
18608     len = strlen(new_seq);
18609     ret = (len > 0) && (len < sizeof(cseq));
18610     if (ret)
18611       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18612     else if (appData.debugMode)
18613       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18614     return ret;
18615 }
18616
18617 /*
18618     reformat a source message so words don't cross the width boundary.  internal
18619     newlines are not removed.  returns the wrapped size (no null character unless
18620     included in source message).  If dest is NULL, only calculate the size required
18621     for the dest buffer.  lp argument indicats line position upon entry, and it's
18622     passed back upon exit.
18623 */
18624 int
18625 wrap (char *dest, char *src, int count, int width, int *lp)
18626 {
18627     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18628
18629     cseq_len = strlen(cseq);
18630     old_line = line = *lp;
18631     ansi = len = clen = 0;
18632
18633     for (i=0; i < count; i++)
18634     {
18635         if (src[i] == '\033')
18636             ansi = 1;
18637
18638         // if we hit the width, back up
18639         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18640         {
18641             // store i & len in case the word is too long
18642             old_i = i, old_len = len;
18643
18644             // find the end of the last word
18645             while (i && src[i] != ' ' && src[i] != '\n')
18646             {
18647                 i--;
18648                 len--;
18649             }
18650
18651             // word too long?  restore i & len before splitting it
18652             if ((old_i-i+clen) >= width)
18653             {
18654                 i = old_i;
18655                 len = old_len;
18656             }
18657
18658             // extra space?
18659             if (i && src[i-1] == ' ')
18660                 len--;
18661
18662             if (src[i] != ' ' && src[i] != '\n')
18663             {
18664                 i--;
18665                 if (len)
18666                     len--;
18667             }
18668
18669             // now append the newline and continuation sequence
18670             if (dest)
18671                 dest[len] = '\n';
18672             len++;
18673             if (dest)
18674                 strncpy(dest+len, cseq, cseq_len);
18675             len += cseq_len;
18676             line = cseq_len;
18677             clen = cseq_len;
18678             continue;
18679         }
18680
18681         if (dest)
18682             dest[len] = src[i];
18683         len++;
18684         if (!ansi)
18685             line++;
18686         if (src[i] == '\n')
18687             line = 0;
18688         if (src[i] == 'm')
18689             ansi = 0;
18690     }
18691     if (dest && appData.debugMode)
18692     {
18693         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18694             count, width, line, len, *lp);
18695         show_bytes(debugFP, src, count);
18696         fprintf(debugFP, "\ndest: ");
18697         show_bytes(debugFP, dest, len);
18698         fprintf(debugFP, "\n");
18699     }
18700     *lp = dest ? line : old_line;
18701
18702     return len;
18703 }
18704
18705 // [HGM] vari: routines for shelving variations
18706 Boolean modeRestore = FALSE;
18707
18708 void
18709 PushInner (int firstMove, int lastMove)
18710 {
18711         int i, j, nrMoves = lastMove - firstMove;
18712
18713         // push current tail of game on stack
18714         savedResult[storedGames] = gameInfo.result;
18715         savedDetails[storedGames] = gameInfo.resultDetails;
18716         gameInfo.resultDetails = NULL;
18717         savedFirst[storedGames] = firstMove;
18718         savedLast [storedGames] = lastMove;
18719         savedFramePtr[storedGames] = framePtr;
18720         framePtr -= nrMoves; // reserve space for the boards
18721         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18722             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18723             for(j=0; j<MOVE_LEN; j++)
18724                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18725             for(j=0; j<2*MOVE_LEN; j++)
18726                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18727             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18728             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18729             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18730             pvInfoList[firstMove+i-1].depth = 0;
18731             commentList[framePtr+i] = commentList[firstMove+i];
18732             commentList[firstMove+i] = NULL;
18733         }
18734
18735         storedGames++;
18736         forwardMostMove = firstMove; // truncate game so we can start variation
18737 }
18738
18739 void
18740 PushTail (int firstMove, int lastMove)
18741 {
18742         if(appData.icsActive) { // only in local mode
18743                 forwardMostMove = currentMove; // mimic old ICS behavior
18744                 return;
18745         }
18746         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18747
18748         PushInner(firstMove, lastMove);
18749         if(storedGames == 1) GreyRevert(FALSE);
18750         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18751 }
18752
18753 void
18754 PopInner (Boolean annotate)
18755 {
18756         int i, j, nrMoves;
18757         char buf[8000], moveBuf[20];
18758
18759         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18760         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18761         nrMoves = savedLast[storedGames] - currentMove;
18762         if(annotate) {
18763                 int cnt = 10;
18764                 if(!WhiteOnMove(currentMove))
18765                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18766                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18767                 for(i=currentMove; i<forwardMostMove; i++) {
18768                         if(WhiteOnMove(i))
18769                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18770                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18771                         strcat(buf, moveBuf);
18772                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18773                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18774                 }
18775                 strcat(buf, ")");
18776         }
18777         for(i=1; i<=nrMoves; i++) { // copy last variation back
18778             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18779             for(j=0; j<MOVE_LEN; j++)
18780                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18781             for(j=0; j<2*MOVE_LEN; j++)
18782                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18783             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18784             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18785             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18786             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18787             commentList[currentMove+i] = commentList[framePtr+i];
18788             commentList[framePtr+i] = NULL;
18789         }
18790         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18791         framePtr = savedFramePtr[storedGames];
18792         gameInfo.result = savedResult[storedGames];
18793         if(gameInfo.resultDetails != NULL) {
18794             free(gameInfo.resultDetails);
18795       }
18796         gameInfo.resultDetails = savedDetails[storedGames];
18797         forwardMostMove = currentMove + nrMoves;
18798 }
18799
18800 Boolean
18801 PopTail (Boolean annotate)
18802 {
18803         if(appData.icsActive) return FALSE; // only in local mode
18804         if(!storedGames) return FALSE; // sanity
18805         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18806
18807         PopInner(annotate);
18808         if(currentMove < forwardMostMove) ForwardEvent(); else
18809         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18810
18811         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18812         return TRUE;
18813 }
18814
18815 void
18816 CleanupTail ()
18817 {       // remove all shelved variations
18818         int i;
18819         for(i=0; i<storedGames; i++) {
18820             if(savedDetails[i])
18821                 free(savedDetails[i]);
18822             savedDetails[i] = NULL;
18823         }
18824         for(i=framePtr; i<MAX_MOVES; i++) {
18825                 if(commentList[i]) free(commentList[i]);
18826                 commentList[i] = NULL;
18827         }
18828         framePtr = MAX_MOVES-1;
18829         storedGames = 0;
18830 }
18831
18832 void
18833 LoadVariation (int index, char *text)
18834 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18835         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18836         int level = 0, move;
18837
18838         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18839         // first find outermost bracketing variation
18840         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18841             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18842                 if(*p == '{') wait = '}'; else
18843                 if(*p == '[') wait = ']'; else
18844                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18845                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18846             }
18847             if(*p == wait) wait = NULLCHAR; // closing ]} found
18848             p++;
18849         }
18850         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18851         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18852         end[1] = NULLCHAR; // clip off comment beyond variation
18853         ToNrEvent(currentMove-1);
18854         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18855         // kludge: use ParsePV() to append variation to game
18856         move = currentMove;
18857         ParsePV(start, TRUE, TRUE);
18858         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18859         ClearPremoveHighlights();
18860         CommentPopDown();
18861         ToNrEvent(currentMove+1);
18862 }
18863
18864 void
18865 LoadTheme ()
18866 {
18867     char *p, *q, buf[MSG_SIZ];
18868     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18869         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18870         ParseArgsFromString(buf);
18871         ActivateTheme(TRUE); // also redo colors
18872         return;
18873     }
18874     p = nickName;
18875     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18876     {
18877         int len;
18878         q = appData.themeNames;
18879         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18880       if(appData.useBitmaps) {
18881         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18882                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18883                 appData.liteBackTextureMode,
18884                 appData.darkBackTextureMode );
18885       } else {
18886         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18887                 Col2Text(2),   // lightSquareColor
18888                 Col2Text(3) ); // darkSquareColor
18889       }
18890       if(appData.useBorder) {
18891         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18892                 appData.border);
18893       } else {
18894         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18895       }
18896       if(appData.useFont) {
18897         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18898                 appData.renderPiecesWithFont,
18899                 appData.fontToPieceTable,
18900                 Col2Text(9),    // appData.fontBackColorWhite
18901                 Col2Text(10) ); // appData.fontForeColorBlack
18902       } else {
18903         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18904                 appData.pieceDirectory);
18905         if(!appData.pieceDirectory[0])
18906           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18907                 Col2Text(0),   // whitePieceColor
18908                 Col2Text(1) ); // blackPieceColor
18909       }
18910       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18911                 Col2Text(4),   // highlightSquareColor
18912                 Col2Text(5) ); // premoveHighlightColor
18913         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18914         if(insert != q) insert[-1] = NULLCHAR;
18915         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18916         if(q)   free(q);
18917     }
18918     ActivateTheme(FALSE);
18919 }