Leave xterm at start of new line after quitting XBoard
[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 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 StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298
299 /* States for ics_getting_history */
300 #define H_FALSE 0
301 #define H_REQUESTED 1
302 #define H_GOT_REQ_HEADER 2
303 #define H_GOT_UNREQ_HEADER 3
304 #define H_GETTING_MOVES 4
305 #define H_GOT_UNWANTED_HEADER 5
306
307 /* whosays values for GameEnds */
308 #define GE_ICS 0
309 #define GE_ENGINE 1
310 #define GE_PLAYER 2
311 #define GE_FILE 3
312 #define GE_XBOARD 4
313 #define GE_ENGINE1 5
314 #define GE_ENGINE2 6
315
316 /* Maximum number of games in a cmail message */
317 #define CMAIL_MAX_GAMES 20
318
319 /* Different types of move when calling RegisterMove */
320 #define CMAIL_MOVE   0
321 #define CMAIL_RESIGN 1
322 #define CMAIL_DRAW   2
323 #define CMAIL_ACCEPT 3
324
325 /* Different types of result to remember for each game */
326 #define CMAIL_NOT_RESULT 0
327 #define CMAIL_OLD_RESULT 1
328 #define CMAIL_NEW_RESULT 2
329
330 /* Telnet protocol constants */
331 #define TN_WILL 0373
332 #define TN_WONT 0374
333 #define TN_DO   0375
334 #define TN_DONT 0376
335 #define TN_IAC  0377
336 #define TN_ECHO 0001
337 #define TN_SGA  0003
338 #define TN_PORT 23
339
340 char*
341 safeStrCpy (char *dst, const char *src, size_t count)
342 { // [HGM] made safe
343   int i;
344   assert( dst != NULL );
345   assert( src != NULL );
346   assert( count > 0 );
347
348   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
349   if(  i == count && dst[count-1] != NULLCHAR)
350     {
351       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
352       if(appData.debugMode)
353         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
354     }
355
356   return dst;
357 }
358
359 /* Some compiler can't cast u64 to double
360  * This function do the job for us:
361
362  * We use the highest bit for cast, this only
363  * works if the highest bit is not
364  * in use (This should not happen)
365  *
366  * We used this for all compiler
367  */
368 double
369 u64ToDouble (u64 value)
370 {
371   double r;
372   u64 tmp = value & u64Const(0x7fffffffffffffff);
373   r = (double)(s64)tmp;
374   if (value & u64Const(0x8000000000000000))
375        r +=  9.2233720368547758080e18; /* 2^63 */
376  return r;
377 }
378
379 /* Fake up flags for now, as we aren't keeping track of castling
380    availability yet. [HGM] Change of logic: the flag now only
381    indicates the type of castlings allowed by the rule of the game.
382    The actual rights themselves are maintained in the array
383    castlingRights, as part of the game history, and are not probed
384    by this function.
385  */
386 int
387 PosFlags (index)
388 {
389   int flags = F_ALL_CASTLE_OK;
390   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
391   switch (gameInfo.variant) {
392   case VariantSuicide:
393     flags &= ~F_ALL_CASTLE_OK;
394   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
395     flags |= F_IGNORE_CHECK;
396   case VariantLosers:
397     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
398     break;
399   case VariantAtomic:
400     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
401     break;
402   case VariantKriegspiel:
403     flags |= F_KRIEGSPIEL_CAPTURE;
404     break;
405   case VariantCapaRandom:
406   case VariantFischeRandom:
407     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
408   case VariantNoCastle:
409   case VariantShatranj:
410   case VariantCourier:
411   case VariantMakruk:
412   case VariantASEAN:
413   case VariantGrand:
414     flags &= ~F_ALL_CASTLE_OK;
415     break;
416   default:
417     break;
418   }
419   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
420   return flags;
421 }
422
423 FILE *gameFileFP, *debugFP, *serverFP;
424 char *currentDebugFile; // [HGM] debug split: to remember name
425
426 /*
427     [AS] Note: sometimes, the sscanf() function is used to parse the input
428     into a fixed-size buffer. Because of this, we must be prepared to
429     receive strings as long as the size of the input buffer, which is currently
430     set to 4K for Windows and 8K for the rest.
431     So, we must either allocate sufficiently large buffers here, or
432     reduce the size of the input buffer in the input reading part.
433 */
434
435 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
436 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
437 char thinkOutput1[MSG_SIZ*10];
438
439 ChessProgramState first, second, pairing;
440
441 /* premove variables */
442 int premoveToX = 0;
443 int premoveToY = 0;
444 int premoveFromX = 0;
445 int premoveFromY = 0;
446 int premovePromoChar = 0;
447 int gotPremove = 0;
448 Boolean alarmSounded;
449 /* end premove variables */
450
451 char *ics_prefix = "$";
452 enum ICS_TYPE ics_type = ICS_GENERIC;
453
454 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
455 int pauseExamForwardMostMove = 0;
456 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
457 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
458 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
459 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
460 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
461 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
462 int whiteFlag = FALSE, blackFlag = FALSE;
463 int userOfferedDraw = FALSE;
464 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
465 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
466 int cmailMoveType[CMAIL_MAX_GAMES];
467 long ics_clock_paused = 0;
468 ProcRef icsPR = NoProc, cmailPR = NoProc;
469 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
470 GameMode gameMode = BeginningOfGame;
471 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
472 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
473 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
474 int hiddenThinkOutputState = 0; /* [AS] */
475 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
476 int adjudicateLossPlies = 6;
477 char white_holding[64], black_holding[64];
478 TimeMark lastNodeCountTime;
479 long lastNodeCount=0;
480 int shiftKey, controlKey; // [HGM] set by mouse handler
481
482 int have_sent_ICS_logon = 0;
483 int movesPerSession;
484 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
485 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
486 Boolean adjustedClock;
487 long timeControl_2; /* [AS] Allow separate time controls */
488 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
489 long timeRemaining[2][MAX_MOVES];
490 int matchGame = 0, nextGame = 0, roundNr = 0;
491 Boolean waitingForGame = FALSE, startingEngine = FALSE;
492 TimeMark programStartTime, pauseStart;
493 char ics_handle[MSG_SIZ];
494 int have_set_title = 0;
495
496 /* animateTraining preserves the state of appData.animate
497  * when Training mode is activated. This allows the
498  * response to be animated when appData.animate == TRUE and
499  * appData.animateDragging == TRUE.
500  */
501 Boolean animateTraining;
502
503 GameInfo gameInfo;
504
505 AppData appData;
506
507 Board boards[MAX_MOVES];
508 /* [HGM] Following 7 needed for accurate legality tests: */
509 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
510 signed char  initialRights[BOARD_FILES];
511 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
512 int   initialRulePlies, FENrulePlies;
513 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
514 int loadFlag = 0;
515 Boolean shuffleOpenings;
516 int mute; // mute all sounds
517
518 // [HGM] vari: next 12 to save and restore variations
519 #define MAX_VARIATIONS 10
520 int framePtr = MAX_MOVES-1; // points to free stack entry
521 int storedGames = 0;
522 int savedFirst[MAX_VARIATIONS];
523 int savedLast[MAX_VARIATIONS];
524 int savedFramePtr[MAX_VARIATIONS];
525 char *savedDetails[MAX_VARIATIONS];
526 ChessMove savedResult[MAX_VARIATIONS];
527
528 void PushTail P((int firstMove, int lastMove));
529 Boolean PopTail P((Boolean annotate));
530 void PushInner P((int firstMove, int lastMove));
531 void PopInner P((Boolean annotate));
532 void CleanupTail P((void));
533
534 ChessSquare  FIDEArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
538         BlackKing, BlackBishop, BlackKnight, BlackRook }
539 };
540
541 ChessSquare twoKingsArray[2][BOARD_FILES] = {
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
545         BlackKing, BlackKing, BlackKnight, BlackRook }
546 };
547
548 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
549     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
550         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
551     { BlackRook, BlackMan, BlackBishop, BlackQueen,
552         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
553 };
554
555 ChessSquare SpartanArray[2][BOARD_FILES] = {
556     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
557         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
558     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
559         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
560 };
561
562 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
563     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
564         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
565     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
566         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
567 };
568
569 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
570     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
571         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
572     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
573         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
574 };
575
576 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
577     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
578         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
579     { BlackRook, BlackKnight, BlackMan, BlackFerz,
580         BlackKing, BlackMan, BlackKnight, BlackRook }
581 };
582
583 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
584     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
585         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
586     { BlackRook, BlackKnight, BlackMan, BlackFerz,
587         BlackKing, BlackMan, BlackKnight, BlackRook }
588 };
589
590 ChessSquare  lionArray[2][BOARD_FILES] = {
591     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
592         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
593     { BlackRook, BlackLion, BlackBishop, BlackQueen,
594         BlackKing, BlackBishop, BlackKnight, BlackRook }
595 };
596
597
598 #if (BOARD_FILES>=10)
599 ChessSquare ShogiArray[2][BOARD_FILES] = {
600     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
601         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
602     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
603         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
604 };
605
606 ChessSquare XiangqiArray[2][BOARD_FILES] = {
607     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
608         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
609     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
610         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
611 };
612
613 ChessSquare CapablancaArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
615         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
617         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
618 };
619
620 ChessSquare GreatArray[2][BOARD_FILES] = {
621     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
622         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
623     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
624         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
625 };
626
627 ChessSquare JanusArray[2][BOARD_FILES] = {
628     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
629         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
630     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
631         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
632 };
633
634 ChessSquare GrandArray[2][BOARD_FILES] = {
635     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
636         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
637     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
638         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
639 };
640
641 ChessSquare ChuChessArray[2][BOARD_FILES] = {
642     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
643         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
644     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
645         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
646 };
647
648 #ifdef GOTHIC
649 ChessSquare GothicArray[2][BOARD_FILES] = {
650     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
651         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
652     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
653         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
654 };
655 #else // !GOTHIC
656 #define GothicArray CapablancaArray
657 #endif // !GOTHIC
658
659 #ifdef FALCON
660 ChessSquare FalconArray[2][BOARD_FILES] = {
661     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
662         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
663     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
664         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
665 };
666 #else // !FALCON
667 #define FalconArray CapablancaArray
668 #endif // !FALCON
669
670 #else // !(BOARD_FILES>=10)
671 #define XiangqiPosition FIDEArray
672 #define CapablancaArray FIDEArray
673 #define GothicArray FIDEArray
674 #define GreatArray FIDEArray
675 #endif // !(BOARD_FILES>=10)
676
677 #if (BOARD_FILES>=12)
678 ChessSquare CourierArray[2][BOARD_FILES] = {
679     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
680         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
681     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
682         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
683 };
684 ChessSquare ChuArray[6][BOARD_FILES] = {
685     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
686       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
687     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
688       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
689     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
690       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
691     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
692       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
693     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
694       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
695     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
696       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
697 };
698 #else // !(BOARD_FILES>=12)
699 #define CourierArray CapablancaArray
700 #define ChuArray CapablancaArray
701 #endif // !(BOARD_FILES>=12)
702
703
704 Board initialPosition;
705
706
707 /* Convert str to a rating. Checks for special cases of "----",
708
709    "++++", etc. Also strips ()'s */
710 int
711 string_to_rating (char *str)
712 {
713   while(*str && !isdigit(*str)) ++str;
714   if (!*str)
715     return 0;   /* One of the special "no rating" cases */
716   else
717     return atoi(str);
718 }
719
720 void
721 ClearProgramStats ()
722 {
723     /* Init programStats */
724     programStats.movelist[0] = 0;
725     programStats.depth = 0;
726     programStats.nr_moves = 0;
727     programStats.moves_left = 0;
728     programStats.nodes = 0;
729     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
730     programStats.score = 0;
731     programStats.got_only_move = 0;
732     programStats.got_fail = 0;
733     programStats.line_is_book = 0;
734 }
735
736 void
737 CommonEngineInit ()
738 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
739     if (appData.firstPlaysBlack) {
740         first.twoMachinesColor = "black\n";
741         second.twoMachinesColor = "white\n";
742     } else {
743         first.twoMachinesColor = "white\n";
744         second.twoMachinesColor = "black\n";
745     }
746
747     first.other = &second;
748     second.other = &first;
749
750     { float norm = 1;
751         if(appData.timeOddsMode) {
752             norm = appData.timeOdds[0];
753             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
754         }
755         first.timeOdds  = appData.timeOdds[0]/norm;
756         second.timeOdds = appData.timeOdds[1]/norm;
757     }
758
759     if(programVersion) free(programVersion);
760     if (appData.noChessProgram) {
761         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
762         sprintf(programVersion, "%s", PACKAGE_STRING);
763     } else {
764       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
765       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
766       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
767     }
768 }
769
770 void
771 UnloadEngine (ChessProgramState *cps)
772 {
773         /* Kill off first chess program */
774         if (cps->isr != NULL)
775           RemoveInputSource(cps->isr);
776         cps->isr = NULL;
777
778         if (cps->pr != NoProc) {
779             ExitAnalyzeMode();
780             DoSleep( appData.delayBeforeQuit );
781             SendToProgram("quit\n", cps);
782             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
783         }
784         cps->pr = NoProc;
785         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
786 }
787
788 void
789 ClearOptions (ChessProgramState *cps)
790 {
791     int i;
792     cps->nrOptions = cps->comboCnt = 0;
793     for(i=0; i<MAX_OPTIONS; i++) {
794         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
795         cps->option[i].textValue = 0;
796     }
797 }
798
799 char *engineNames[] = {
800   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
801      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
802 N_("first"),
803   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
804      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
805 N_("second")
806 };
807
808 void
809 InitEngine (ChessProgramState *cps, int n)
810 {   // [HGM] all engine initialiation put in a function that does one engine
811
812     ClearOptions(cps);
813
814     cps->which = engineNames[n];
815     cps->maybeThinking = FALSE;
816     cps->pr = NoProc;
817     cps->isr = NULL;
818     cps->sendTime = 2;
819     cps->sendDrawOffers = 1;
820
821     cps->program = appData.chessProgram[n];
822     cps->host = appData.host[n];
823     cps->dir = appData.directory[n];
824     cps->initString = appData.engInitString[n];
825     cps->computerString = appData.computerString[n];
826     cps->useSigint  = TRUE;
827     cps->useSigterm = TRUE;
828     cps->reuse = appData.reuse[n];
829     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
830     cps->useSetboard = FALSE;
831     cps->useSAN = FALSE;
832     cps->usePing = FALSE;
833     cps->lastPing = 0;
834     cps->lastPong = 0;
835     cps->usePlayother = FALSE;
836     cps->useColors = TRUE;
837     cps->useUsermove = FALSE;
838     cps->sendICS = FALSE;
839     cps->sendName = appData.icsActive;
840     cps->sdKludge = FALSE;
841     cps->stKludge = FALSE;
842     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
843     TidyProgramName(cps->program, cps->host, cps->tidy);
844     cps->matchWins = 0;
845     ASSIGN(cps->variants, appData.variant);
846     cps->analysisSupport = 2; /* detect */
847     cps->analyzing = FALSE;
848     cps->initDone = FALSE;
849     cps->reload = FALSE;
850
851     /* New features added by Tord: */
852     cps->useFEN960 = FALSE;
853     cps->useOOCastle = TRUE;
854     /* End of new features added by Tord. */
855     cps->fenOverride  = appData.fenOverride[n];
856
857     /* [HGM] time odds: set factor for each machine */
858     cps->timeOdds  = appData.timeOdds[n];
859
860     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
861     cps->accumulateTC = appData.accumulateTC[n];
862     cps->maxNrOfSessions = 1;
863
864     /* [HGM] debug */
865     cps->debug = FALSE;
866
867     cps->drawDepth = appData.drawDepth[n];
868     cps->supportsNPS = UNKNOWN;
869     cps->memSize = FALSE;
870     cps->maxCores = FALSE;
871     ASSIGN(cps->egtFormats, "");
872
873     /* [HGM] options */
874     cps->optionSettings  = appData.engOptions[n];
875
876     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
877     cps->isUCI = appData.isUCI[n]; /* [AS] */
878     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
879     cps->highlight = 0;
880
881     if (appData.protocolVersion[n] > PROTOVER
882         || appData.protocolVersion[n] < 1)
883       {
884         char buf[MSG_SIZ];
885         int len;
886
887         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
888                        appData.protocolVersion[n]);
889         if( (len >= MSG_SIZ) && appData.debugMode )
890           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
891
892         DisplayFatalError(buf, 0, 2);
893       }
894     else
895       {
896         cps->protocolVersion = appData.protocolVersion[n];
897       }
898
899     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
900     ParseFeatures(appData.featureDefaults, cps);
901 }
902
903 ChessProgramState *savCps;
904
905 GameMode oldMode;
906
907 void
908 LoadEngine ()
909 {
910     int i;
911     if(WaitForEngine(savCps, LoadEngine)) return;
912     CommonEngineInit(); // recalculate time odds
913     if(gameInfo.variant != StringToVariant(appData.variant)) {
914         // we changed variant when loading the engine; this forces us to reset
915         Reset(TRUE, savCps != &first);
916         oldMode = BeginningOfGame; // to prevent restoring old mode
917     }
918     InitChessProgram(savCps, FALSE);
919     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
920     DisplayMessage("", "");
921     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
922     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
923     ThawUI();
924     SetGNUMode();
925     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
926 }
927
928 void
929 ReplaceEngine (ChessProgramState *cps, int n)
930 {
931     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
932     keepInfo = 1;
933     if(oldMode != BeginningOfGame) EditGameEvent();
934     keepInfo = 0;
935     UnloadEngine(cps);
936     appData.noChessProgram = FALSE;
937     appData.clockMode = TRUE;
938     InitEngine(cps, n);
939     UpdateLogos(TRUE);
940     if(n) return; // only startup first engine immediately; second can wait
941     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
942     LoadEngine();
943 }
944
945 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
946 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
947
948 static char resetOptions[] =
949         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
950         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
951         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
952         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
953
954 void
955 FloatToFront(char **list, char *engineLine)
956 {
957     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
958     int i=0;
959     if(appData.recentEngines <= 0) return;
960     TidyProgramName(engineLine, "localhost", tidy+1);
961     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
962     strncpy(buf+1, *list, MSG_SIZ-50);
963     if(p = strstr(buf, tidy)) { // tidy name appears in list
964         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
965         while(*p++ = *++q); // squeeze out
966     }
967     strcat(tidy, buf+1); // put list behind tidy name
968     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
969     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
970     ASSIGN(*list, tidy+1);
971 }
972
973 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
974
975 void
976 Load (ChessProgramState *cps, int i)
977 {
978     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
979     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
980         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
981         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
982         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
983         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
984         appData.firstProtocolVersion = PROTOVER;
985         ParseArgsFromString(buf);
986         SwapEngines(i);
987         ReplaceEngine(cps, i);
988         FloatToFront(&appData.recentEngineList, engineLine);
989         return;
990     }
991     p = engineName;
992     while(q = strchr(p, SLASH)) p = q+1;
993     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
994     if(engineDir[0] != NULLCHAR) {
995         ASSIGN(appData.directory[i], engineDir); p = engineName;
996     } else if(p != engineName) { // derive directory from engine path, when not given
997         p[-1] = 0;
998         ASSIGN(appData.directory[i], engineName);
999         p[-1] = SLASH;
1000         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1001     } else { ASSIGN(appData.directory[i], "."); }
1002     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1003     if(params[0]) {
1004         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1005         snprintf(command, MSG_SIZ, "%s %s", p, params);
1006         p = command;
1007     }
1008     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1009     ASSIGN(appData.chessProgram[i], p);
1010     appData.isUCI[i] = isUCI;
1011     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1012     appData.hasOwnBookUCI[i] = hasBook;
1013     if(!nickName[0]) useNick = FALSE;
1014     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1015     if(addToList) {
1016         int len;
1017         char quote;
1018         q = firstChessProgramNames;
1019         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1020         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1021         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1022                         quote, p, quote, appData.directory[i],
1023                         useNick ? " -fn \"" : "",
1024                         useNick ? nickName : "",
1025                         useNick ? "\"" : "",
1026                         v1 ? " -firstProtocolVersion 1" : "",
1027                         hasBook ? "" : " -fNoOwnBookUCI",
1028                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1029                         storeVariant ? " -variant " : "",
1030                         storeVariant ? VariantName(gameInfo.variant) : "");
1031         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1032         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1033         if(insert != q) insert[-1] = NULLCHAR;
1034         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1035         if(q)   free(q);
1036         FloatToFront(&appData.recentEngineList, buf);
1037     }
1038     ReplaceEngine(cps, i);
1039 }
1040
1041 void
1042 InitTimeControls ()
1043 {
1044     int matched, min, sec;
1045     /*
1046      * Parse timeControl resource
1047      */
1048     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1049                           appData.movesPerSession)) {
1050         char buf[MSG_SIZ];
1051         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1052         DisplayFatalError(buf, 0, 2);
1053     }
1054
1055     /*
1056      * Parse searchTime resource
1057      */
1058     if (*appData.searchTime != NULLCHAR) {
1059         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1060         if (matched == 1) {
1061             searchTime = min * 60;
1062         } else if (matched == 2) {
1063             searchTime = min * 60 + sec;
1064         } else {
1065             char buf[MSG_SIZ];
1066             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1067             DisplayFatalError(buf, 0, 2);
1068         }
1069     }
1070 }
1071
1072 void
1073 InitBackEnd1 ()
1074 {
1075
1076     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1077     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1078
1079     GetTimeMark(&programStartTime);
1080     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1081     appData.seedBase = random() + (random()<<15);
1082     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1083
1084     ClearProgramStats();
1085     programStats.ok_to_send = 1;
1086     programStats.seen_stat = 0;
1087
1088     /*
1089      * Initialize game list
1090      */
1091     ListNew(&gameList);
1092
1093
1094     /*
1095      * Internet chess server status
1096      */
1097     if (appData.icsActive) {
1098         appData.matchMode = FALSE;
1099         appData.matchGames = 0;
1100 #if ZIPPY
1101         appData.noChessProgram = !appData.zippyPlay;
1102 #else
1103         appData.zippyPlay = FALSE;
1104         appData.zippyTalk = FALSE;
1105         appData.noChessProgram = TRUE;
1106 #endif
1107         if (*appData.icsHelper != NULLCHAR) {
1108             appData.useTelnet = TRUE;
1109             appData.telnetProgram = appData.icsHelper;
1110         }
1111     } else {
1112         appData.zippyTalk = appData.zippyPlay = FALSE;
1113     }
1114
1115     /* [AS] Initialize pv info list [HGM] and game state */
1116     {
1117         int i, j;
1118
1119         for( i=0; i<=framePtr; i++ ) {
1120             pvInfoList[i].depth = -1;
1121             boards[i][EP_STATUS] = EP_NONE;
1122             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1123         }
1124     }
1125
1126     InitTimeControls();
1127
1128     /* [AS] Adjudication threshold */
1129     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1130
1131     InitEngine(&first, 0);
1132     InitEngine(&second, 1);
1133     CommonEngineInit();
1134
1135     pairing.which = "pairing"; // pairing engine
1136     pairing.pr = NoProc;
1137     pairing.isr = NULL;
1138     pairing.program = appData.pairingEngine;
1139     pairing.host = "localhost";
1140     pairing.dir = ".";
1141
1142     if (appData.icsActive) {
1143         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1144     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1145         appData.clockMode = FALSE;
1146         first.sendTime = second.sendTime = 0;
1147     }
1148
1149 #if ZIPPY
1150     /* Override some settings from environment variables, for backward
1151        compatibility.  Unfortunately it's not feasible to have the env
1152        vars just set defaults, at least in xboard.  Ugh.
1153     */
1154     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1155       ZippyInit();
1156     }
1157 #endif
1158
1159     if (!appData.icsActive) {
1160       char buf[MSG_SIZ];
1161       int len;
1162
1163       /* Check for variants that are supported only in ICS mode,
1164          or not at all.  Some that are accepted here nevertheless
1165          have bugs; see comments below.
1166       */
1167       VariantClass variant = StringToVariant(appData.variant);
1168       switch (variant) {
1169       case VariantBughouse:     /* need four players and two boards */
1170       case VariantKriegspiel:   /* need to hide pieces and move details */
1171         /* case VariantFischeRandom: (Fabien: moved below) */
1172         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1173         if( (len >= MSG_SIZ) && appData.debugMode )
1174           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1175
1176         DisplayFatalError(buf, 0, 2);
1177         return;
1178
1179       case VariantUnknown:
1180       case VariantLoadable:
1181       case Variant29:
1182       case Variant30:
1183       case Variant31:
1184       case Variant32:
1185       case Variant33:
1186       case Variant34:
1187       case Variant35:
1188       case Variant36:
1189       default:
1190         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1191         if( (len >= MSG_SIZ) && appData.debugMode )
1192           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1193
1194         DisplayFatalError(buf, 0, 2);
1195         return;
1196
1197       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1198       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1199       case VariantGothic:     /* [HGM] should work */
1200       case VariantCapablanca: /* [HGM] should work */
1201       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1202       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1203       case VariantChu:        /* [HGM] experimental */
1204       case VariantKnightmate: /* [HGM] should work */
1205       case VariantCylinder:   /* [HGM] untested */
1206       case VariantFalcon:     /* [HGM] untested */
1207       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1208                                  offboard interposition not understood */
1209       case VariantNormal:     /* definitely works! */
1210       case VariantWildCastle: /* pieces not automatically shuffled */
1211       case VariantNoCastle:   /* pieces not automatically shuffled */
1212       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1213       case VariantLosers:     /* should work except for win condition,
1214                                  and doesn't know captures are mandatory */
1215       case VariantSuicide:    /* should work except for win condition,
1216                                  and doesn't know captures are mandatory */
1217       case VariantGiveaway:   /* should work except for win condition,
1218                                  and doesn't know captures are mandatory */
1219       case VariantTwoKings:   /* should work */
1220       case VariantAtomic:     /* should work except for win condition */
1221       case Variant3Check:     /* should work except for win condition */
1222       case VariantShatranj:   /* should work except for all win conditions */
1223       case VariantMakruk:     /* should work except for draw countdown */
1224       case VariantASEAN :     /* should work except for draw countdown */
1225       case VariantBerolina:   /* might work if TestLegality is off */
1226       case VariantCapaRandom: /* should work */
1227       case VariantJanus:      /* should work */
1228       case VariantSuper:      /* experimental */
1229       case VariantGreat:      /* experimental, requires legality testing to be off */
1230       case VariantSChess:     /* S-Chess, should work */
1231       case VariantGrand:      /* should work */
1232       case VariantSpartan:    /* should work */
1233       case VariantLion:       /* should work */
1234       case VariantChuChess:   /* should work */
1235         break;
1236       }
1237     }
1238
1239 }
1240
1241 int
1242 NextIntegerFromString (char ** str, long * value)
1243 {
1244     int result = -1;
1245     char * s = *str;
1246
1247     while( *s == ' ' || *s == '\t' ) {
1248         s++;
1249     }
1250
1251     *value = 0;
1252
1253     if( *s >= '0' && *s <= '9' ) {
1254         while( *s >= '0' && *s <= '9' ) {
1255             *value = *value * 10 + (*s - '0');
1256             s++;
1257         }
1258
1259         result = 0;
1260     }
1261
1262     *str = s;
1263
1264     return result;
1265 }
1266
1267 int
1268 NextTimeControlFromString (char ** str, long * value)
1269 {
1270     long temp;
1271     int result = NextIntegerFromString( str, &temp );
1272
1273     if( result == 0 ) {
1274         *value = temp * 60; /* Minutes */
1275         if( **str == ':' ) {
1276             (*str)++;
1277             result = NextIntegerFromString( str, &temp );
1278             *value += temp; /* Seconds */
1279         }
1280     }
1281
1282     return result;
1283 }
1284
1285 int
1286 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1287 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1288     int result = -1, type = 0; long temp, temp2;
1289
1290     if(**str != ':') return -1; // old params remain in force!
1291     (*str)++;
1292     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1293     if( NextIntegerFromString( str, &temp ) ) return -1;
1294     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1295
1296     if(**str != '/') {
1297         /* time only: incremental or sudden-death time control */
1298         if(**str == '+') { /* increment follows; read it */
1299             (*str)++;
1300             if(**str == '!') type = *(*str)++; // Bronstein TC
1301             if(result = NextIntegerFromString( str, &temp2)) return -1;
1302             *inc = temp2 * 1000;
1303             if(**str == '.') { // read fraction of increment
1304                 char *start = ++(*str);
1305                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1306                 temp2 *= 1000;
1307                 while(start++ < *str) temp2 /= 10;
1308                 *inc += temp2;
1309             }
1310         } else *inc = 0;
1311         *moves = 0; *tc = temp * 1000; *incType = type;
1312         return 0;
1313     }
1314
1315     (*str)++; /* classical time control */
1316     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1317
1318     if(result == 0) {
1319         *moves = temp;
1320         *tc    = temp2 * 1000;
1321         *inc   = 0;
1322         *incType = type;
1323     }
1324     return result;
1325 }
1326
1327 int
1328 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1329 {   /* [HGM] get time to add from the multi-session time-control string */
1330     int incType, moves=1; /* kludge to force reading of first session */
1331     long time, increment;
1332     char *s = tcString;
1333
1334     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1335     do {
1336         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1337         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1338         if(movenr == -1) return time;    /* last move before new session     */
1339         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1340         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1341         if(!moves) return increment;     /* current session is incremental   */
1342         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1343     } while(movenr >= -1);               /* try again for next session       */
1344
1345     return 0; // no new time quota on this move
1346 }
1347
1348 int
1349 ParseTimeControl (char *tc, float ti, int mps)
1350 {
1351   long tc1;
1352   long tc2;
1353   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1354   int min, sec=0;
1355
1356   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1357   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1358       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1359   if(ti > 0) {
1360
1361     if(mps)
1362       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1363     else
1364       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1365   } else {
1366     if(mps)
1367       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1368     else
1369       snprintf(buf, MSG_SIZ, ":%s", mytc);
1370   }
1371   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1372
1373   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1374     return FALSE;
1375   }
1376
1377   if( *tc == '/' ) {
1378     /* Parse second time control */
1379     tc++;
1380
1381     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1382       return FALSE;
1383     }
1384
1385     if( tc2 == 0 ) {
1386       return FALSE;
1387     }
1388
1389     timeControl_2 = tc2 * 1000;
1390   }
1391   else {
1392     timeControl_2 = 0;
1393   }
1394
1395   if( tc1 == 0 ) {
1396     return FALSE;
1397   }
1398
1399   timeControl = tc1 * 1000;
1400
1401   if (ti >= 0) {
1402     timeIncrement = ti * 1000;  /* convert to ms */
1403     movesPerSession = 0;
1404   } else {
1405     timeIncrement = 0;
1406     movesPerSession = mps;
1407   }
1408   return TRUE;
1409 }
1410
1411 void
1412 InitBackEnd2 ()
1413 {
1414     if (appData.debugMode) {
1415 #    ifdef __GIT_VERSION
1416       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1417 #    else
1418       fprintf(debugFP, "Version: %s\n", programVersion);
1419 #    endif
1420     }
1421     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1422
1423     set_cont_sequence(appData.wrapContSeq);
1424     if (appData.matchGames > 0) {
1425         appData.matchMode = TRUE;
1426     } else if (appData.matchMode) {
1427         appData.matchGames = 1;
1428     }
1429     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1430         appData.matchGames = appData.sameColorGames;
1431     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1432         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1433         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1434     }
1435     Reset(TRUE, FALSE);
1436     if (appData.noChessProgram || first.protocolVersion == 1) {
1437       InitBackEnd3();
1438     } else {
1439       /* kludge: allow timeout for initial "feature" commands */
1440       FreezeUI();
1441       DisplayMessage("", _("Starting chess program"));
1442       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1443     }
1444 }
1445
1446 int
1447 CalculateIndex (int index, int gameNr)
1448 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1449     int res;
1450     if(index > 0) return index; // fixed nmber
1451     if(index == 0) return 1;
1452     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1453     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1454     return res;
1455 }
1456
1457 int
1458 LoadGameOrPosition (int gameNr)
1459 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1460     if (*appData.loadGameFile != NULLCHAR) {
1461         if (!LoadGameFromFile(appData.loadGameFile,
1462                 CalculateIndex(appData.loadGameIndex, gameNr),
1463                               appData.loadGameFile, FALSE)) {
1464             DisplayFatalError(_("Bad game file"), 0, 1);
1465             return 0;
1466         }
1467     } else if (*appData.loadPositionFile != NULLCHAR) {
1468         if (!LoadPositionFromFile(appData.loadPositionFile,
1469                 CalculateIndex(appData.loadPositionIndex, gameNr),
1470                                   appData.loadPositionFile)) {
1471             DisplayFatalError(_("Bad position file"), 0, 1);
1472             return 0;
1473         }
1474     }
1475     return 1;
1476 }
1477
1478 void
1479 ReserveGame (int gameNr, char resChar)
1480 {
1481     FILE *tf = fopen(appData.tourneyFile, "r+");
1482     char *p, *q, c, buf[MSG_SIZ];
1483     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1484     safeStrCpy(buf, lastMsg, MSG_SIZ);
1485     DisplayMessage(_("Pick new game"), "");
1486     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1487     ParseArgsFromFile(tf);
1488     p = q = appData.results;
1489     if(appData.debugMode) {
1490       char *r = appData.participants;
1491       fprintf(debugFP, "results = '%s'\n", p);
1492       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1493       fprintf(debugFP, "\n");
1494     }
1495     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1496     nextGame = q - p;
1497     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1498     safeStrCpy(q, p, strlen(p) + 2);
1499     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1500     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1501     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1502         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1503         q[nextGame] = '*';
1504     }
1505     fseek(tf, -(strlen(p)+4), SEEK_END);
1506     c = fgetc(tf);
1507     if(c != '"') // depending on DOS or Unix line endings we can be one off
1508          fseek(tf, -(strlen(p)+2), SEEK_END);
1509     else fseek(tf, -(strlen(p)+3), SEEK_END);
1510     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1511     DisplayMessage(buf, "");
1512     free(p); appData.results = q;
1513     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1514        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1515       int round = appData.defaultMatchGames * appData.tourneyType;
1516       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1517          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1518         UnloadEngine(&first);  // next game belongs to other pairing;
1519         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1520     }
1521     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1522 }
1523
1524 void
1525 MatchEvent (int mode)
1526 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1527         int dummy;
1528         if(matchMode) { // already in match mode: switch it off
1529             abortMatch = TRUE;
1530             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1531             return;
1532         }
1533 //      if(gameMode != BeginningOfGame) {
1534 //          DisplayError(_("You can only start a match from the initial position."), 0);
1535 //          return;
1536 //      }
1537         abortMatch = FALSE;
1538         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1539         /* Set up machine vs. machine match */
1540         nextGame = 0;
1541         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1542         if(appData.tourneyFile[0]) {
1543             ReserveGame(-1, 0);
1544             if(nextGame > appData.matchGames) {
1545                 char buf[MSG_SIZ];
1546                 if(strchr(appData.results, '*') == NULL) {
1547                     FILE *f;
1548                     appData.tourneyCycles++;
1549                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1550                         fclose(f);
1551                         NextTourneyGame(-1, &dummy);
1552                         ReserveGame(-1, 0);
1553                         if(nextGame <= appData.matchGames) {
1554                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1555                             matchMode = mode;
1556                             ScheduleDelayedEvent(NextMatchGame, 10000);
1557                             return;
1558                         }
1559                     }
1560                 }
1561                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1562                 DisplayError(buf, 0);
1563                 appData.tourneyFile[0] = 0;
1564                 return;
1565             }
1566         } else
1567         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1568             DisplayFatalError(_("Can't have a match with no chess programs"),
1569                               0, 2);
1570             return;
1571         }
1572         matchMode = mode;
1573         matchGame = roundNr = 1;
1574         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1575         NextMatchGame();
1576 }
1577
1578 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1579
1580 void
1581 InitBackEnd3 P((void))
1582 {
1583     GameMode initialMode;
1584     char buf[MSG_SIZ];
1585     int err, len;
1586
1587     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1588        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1589         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1590        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1591        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1592         char c, *q = first.variants, *p = strchr(q, ',');
1593         if(p) *p = NULLCHAR;
1594         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1595             int w, h, s;
1596             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1597                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1598             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1599             Reset(TRUE, FALSE);         // and re-initialize
1600         }
1601         if(p) *p = ',';
1602     }
1603
1604     InitChessProgram(&first, startedFromSetupPosition);
1605
1606     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1607         free(programVersion);
1608         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1609         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1610         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1611     }
1612
1613     if (appData.icsActive) {
1614 #ifdef WIN32
1615         /* [DM] Make a console window if needed [HGM] merged ifs */
1616         ConsoleCreate();
1617 #endif
1618         err = establish();
1619         if (err != 0)
1620           {
1621             if (*appData.icsCommPort != NULLCHAR)
1622               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1623                              appData.icsCommPort);
1624             else
1625               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1626                         appData.icsHost, appData.icsPort);
1627
1628             if( (len >= MSG_SIZ) && appData.debugMode )
1629               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1630
1631             DisplayFatalError(buf, err, 1);
1632             return;
1633         }
1634         SetICSMode();
1635         telnetISR =
1636           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1637         fromUserISR =
1638           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1639         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1640             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1641     } else if (appData.noChessProgram) {
1642         SetNCPMode();
1643     } else {
1644         SetGNUMode();
1645     }
1646
1647     if (*appData.cmailGameName != NULLCHAR) {
1648         SetCmailMode();
1649         OpenLoopback(&cmailPR);
1650         cmailISR =
1651           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1652     }
1653
1654     ThawUI();
1655     DisplayMessage("", "");
1656     if (StrCaseCmp(appData.initialMode, "") == 0) {
1657       initialMode = BeginningOfGame;
1658       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1659         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1660         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1661         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1662         ModeHighlight();
1663       }
1664     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1665       initialMode = TwoMachinesPlay;
1666     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1667       initialMode = AnalyzeFile;
1668     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1669       initialMode = AnalyzeMode;
1670     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1671       initialMode = MachinePlaysWhite;
1672     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1673       initialMode = MachinePlaysBlack;
1674     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1675       initialMode = EditGame;
1676     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1677       initialMode = EditPosition;
1678     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1679       initialMode = Training;
1680     } else {
1681       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1682       if( (len >= MSG_SIZ) && appData.debugMode )
1683         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1684
1685       DisplayFatalError(buf, 0, 2);
1686       return;
1687     }
1688
1689     if (appData.matchMode) {
1690         if(appData.tourneyFile[0]) { // start tourney from command line
1691             FILE *f;
1692             if(f = fopen(appData.tourneyFile, "r")) {
1693                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1694                 fclose(f);
1695                 appData.clockMode = TRUE;
1696                 SetGNUMode();
1697             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1698         }
1699         MatchEvent(TRUE);
1700     } else if (*appData.cmailGameName != NULLCHAR) {
1701         /* Set up cmail mode */
1702         ReloadCmailMsgEvent(TRUE);
1703     } else {
1704         /* Set up other modes */
1705         if (initialMode == AnalyzeFile) {
1706           if (*appData.loadGameFile == NULLCHAR) {
1707             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1708             return;
1709           }
1710         }
1711         if (*appData.loadGameFile != NULLCHAR) {
1712             (void) LoadGameFromFile(appData.loadGameFile,
1713                                     appData.loadGameIndex,
1714                                     appData.loadGameFile, TRUE);
1715         } else if (*appData.loadPositionFile != NULLCHAR) {
1716             (void) LoadPositionFromFile(appData.loadPositionFile,
1717                                         appData.loadPositionIndex,
1718                                         appData.loadPositionFile);
1719             /* [HGM] try to make self-starting even after FEN load */
1720             /* to allow automatic setup of fairy variants with wtm */
1721             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1722                 gameMode = BeginningOfGame;
1723                 setboardSpoiledMachineBlack = 1;
1724             }
1725             /* [HGM] loadPos: make that every new game uses the setup */
1726             /* from file as long as we do not switch variant          */
1727             if(!blackPlaysFirst) {
1728                 startedFromPositionFile = TRUE;
1729                 CopyBoard(filePosition, boards[0]);
1730             }
1731         }
1732         if (initialMode == AnalyzeMode) {
1733           if (appData.noChessProgram) {
1734             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1735             return;
1736           }
1737           if (appData.icsActive) {
1738             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1739             return;
1740           }
1741           AnalyzeModeEvent();
1742         } else if (initialMode == AnalyzeFile) {
1743           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1744           ShowThinkingEvent();
1745           AnalyzeFileEvent();
1746           AnalysisPeriodicEvent(1);
1747         } else if (initialMode == MachinePlaysWhite) {
1748           if (appData.noChessProgram) {
1749             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1750                               0, 2);
1751             return;
1752           }
1753           if (appData.icsActive) {
1754             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1755                               0, 2);
1756             return;
1757           }
1758           MachineWhiteEvent();
1759         } else if (initialMode == MachinePlaysBlack) {
1760           if (appData.noChessProgram) {
1761             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1762                               0, 2);
1763             return;
1764           }
1765           if (appData.icsActive) {
1766             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1767                               0, 2);
1768             return;
1769           }
1770           MachineBlackEvent();
1771         } else if (initialMode == TwoMachinesPlay) {
1772           if (appData.noChessProgram) {
1773             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1774                               0, 2);
1775             return;
1776           }
1777           if (appData.icsActive) {
1778             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1779                               0, 2);
1780             return;
1781           }
1782           TwoMachinesEvent();
1783         } else if (initialMode == EditGame) {
1784           EditGameEvent();
1785         } else if (initialMode == EditPosition) {
1786           EditPositionEvent();
1787         } else if (initialMode == Training) {
1788           if (*appData.loadGameFile == NULLCHAR) {
1789             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1790             return;
1791           }
1792           TrainingEvent();
1793         }
1794     }
1795 }
1796
1797 void
1798 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1799 {
1800     DisplayBook(current+1);
1801
1802     MoveHistorySet( movelist, first, last, current, pvInfoList );
1803
1804     EvalGraphSet( first, last, current, pvInfoList );
1805
1806     MakeEngineOutputTitle();
1807 }
1808
1809 /*
1810  * Establish will establish a contact to a remote host.port.
1811  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1812  *  used to talk to the host.
1813  * Returns 0 if okay, error code if not.
1814  */
1815 int
1816 establish ()
1817 {
1818     char buf[MSG_SIZ];
1819
1820     if (*appData.icsCommPort != NULLCHAR) {
1821         /* Talk to the host through a serial comm port */
1822         return OpenCommPort(appData.icsCommPort, &icsPR);
1823
1824     } else if (*appData.gateway != NULLCHAR) {
1825         if (*appData.remoteShell == NULLCHAR) {
1826             /* Use the rcmd protocol to run telnet program on a gateway host */
1827             snprintf(buf, sizeof(buf), "%s %s %s",
1828                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1829             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1830
1831         } else {
1832             /* Use the rsh program to run telnet program on a gateway host */
1833             if (*appData.remoteUser == NULLCHAR) {
1834                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1835                         appData.gateway, appData.telnetProgram,
1836                         appData.icsHost, appData.icsPort);
1837             } else {
1838                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1839                         appData.remoteShell, appData.gateway,
1840                         appData.remoteUser, appData.telnetProgram,
1841                         appData.icsHost, appData.icsPort);
1842             }
1843             return StartChildProcess(buf, "", &icsPR);
1844
1845         }
1846     } else if (appData.useTelnet) {
1847         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1848
1849     } else {
1850         /* TCP socket interface differs somewhat between
1851            Unix and NT; handle details in the front end.
1852            */
1853         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1854     }
1855 }
1856
1857 void
1858 EscapeExpand (char *p, char *q)
1859 {       // [HGM] initstring: routine to shape up string arguments
1860         while(*p++ = *q++) if(p[-1] == '\\')
1861             switch(*q++) {
1862                 case 'n': p[-1] = '\n'; break;
1863                 case 'r': p[-1] = '\r'; break;
1864                 case 't': p[-1] = '\t'; break;
1865                 case '\\': p[-1] = '\\'; break;
1866                 case 0: *p = 0; return;
1867                 default: p[-1] = q[-1]; break;
1868             }
1869 }
1870
1871 void
1872 show_bytes (FILE *fp, char *buf, int count)
1873 {
1874     while (count--) {
1875         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1876             fprintf(fp, "\\%03o", *buf & 0xff);
1877         } else {
1878             putc(*buf, fp);
1879         }
1880         buf++;
1881     }
1882     fflush(fp);
1883 }
1884
1885 /* Returns an errno value */
1886 int
1887 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1888 {
1889     char buf[8192], *p, *q, *buflim;
1890     int left, newcount, outcount;
1891
1892     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1893         *appData.gateway != NULLCHAR) {
1894         if (appData.debugMode) {
1895             fprintf(debugFP, ">ICS: ");
1896             show_bytes(debugFP, message, count);
1897             fprintf(debugFP, "\n");
1898         }
1899         return OutputToProcess(pr, message, count, outError);
1900     }
1901
1902     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1903     p = message;
1904     q = buf;
1905     left = count;
1906     newcount = 0;
1907     while (left) {
1908         if (q >= buflim) {
1909             if (appData.debugMode) {
1910                 fprintf(debugFP, ">ICS: ");
1911                 show_bytes(debugFP, buf, newcount);
1912                 fprintf(debugFP, "\n");
1913             }
1914             outcount = OutputToProcess(pr, buf, newcount, outError);
1915             if (outcount < newcount) return -1; /* to be sure */
1916             q = buf;
1917             newcount = 0;
1918         }
1919         if (*p == '\n') {
1920             *q++ = '\r';
1921             newcount++;
1922         } else if (((unsigned char) *p) == TN_IAC) {
1923             *q++ = (char) TN_IAC;
1924             newcount ++;
1925         }
1926         *q++ = *p++;
1927         newcount++;
1928         left--;
1929     }
1930     if (appData.debugMode) {
1931         fprintf(debugFP, ">ICS: ");
1932         show_bytes(debugFP, buf, newcount);
1933         fprintf(debugFP, "\n");
1934     }
1935     outcount = OutputToProcess(pr, buf, newcount, outError);
1936     if (outcount < newcount) return -1; /* to be sure */
1937     return count;
1938 }
1939
1940 void
1941 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1942 {
1943     int outError, outCount;
1944     static int gotEof = 0;
1945     static FILE *ini;
1946
1947     /* Pass data read from player on to ICS */
1948     if (count > 0) {
1949         gotEof = 0;
1950         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1951         if (outCount < count) {
1952             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1953         }
1954         if(have_sent_ICS_logon == 2) {
1955           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1956             fprintf(ini, "%s", message);
1957             have_sent_ICS_logon = 3;
1958           } else
1959             have_sent_ICS_logon = 1;
1960         } else if(have_sent_ICS_logon == 3) {
1961             fprintf(ini, "%s", message);
1962             fclose(ini);
1963           have_sent_ICS_logon = 1;
1964         }
1965     } else if (count < 0) {
1966         RemoveInputSource(isr);
1967         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1968     } else if (gotEof++ > 0) {
1969         RemoveInputSource(isr);
1970         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1971     }
1972 }
1973
1974 void
1975 KeepAlive ()
1976 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1977     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1978     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1979     SendToICS("date\n");
1980     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1981 }
1982
1983 /* added routine for printf style output to ics */
1984 void
1985 ics_printf (char *format, ...)
1986 {
1987     char buffer[MSG_SIZ];
1988     va_list args;
1989
1990     va_start(args, format);
1991     vsnprintf(buffer, sizeof(buffer), format, args);
1992     buffer[sizeof(buffer)-1] = '\0';
1993     SendToICS(buffer);
1994     va_end(args);
1995 }
1996
1997 void
1998 SendToICS (char *s)
1999 {
2000     int count, outCount, outError;
2001
2002     if (icsPR == NoProc) return;
2003
2004     count = strlen(s);
2005     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2006     if (outCount < count) {
2007         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2008     }
2009 }
2010
2011 /* This is used for sending logon scripts to the ICS. Sending
2012    without a delay causes problems when using timestamp on ICC
2013    (at least on my machine). */
2014 void
2015 SendToICSDelayed (char *s, long msdelay)
2016 {
2017     int count, outCount, outError;
2018
2019     if (icsPR == NoProc) return;
2020
2021     count = strlen(s);
2022     if (appData.debugMode) {
2023         fprintf(debugFP, ">ICS: ");
2024         show_bytes(debugFP, s, count);
2025         fprintf(debugFP, "\n");
2026     }
2027     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2028                                       msdelay);
2029     if (outCount < count) {
2030         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2031     }
2032 }
2033
2034
2035 /* Remove all highlighting escape sequences in s
2036    Also deletes any suffix starting with '('
2037    */
2038 char *
2039 StripHighlightAndTitle (char *s)
2040 {
2041     static char retbuf[MSG_SIZ];
2042     char *p = retbuf;
2043
2044     while (*s != NULLCHAR) {
2045         while (*s == '\033') {
2046             while (*s != NULLCHAR && !isalpha(*s)) s++;
2047             if (*s != NULLCHAR) s++;
2048         }
2049         while (*s != NULLCHAR && *s != '\033') {
2050             if (*s == '(' || *s == '[') {
2051                 *p = NULLCHAR;
2052                 return retbuf;
2053             }
2054             *p++ = *s++;
2055         }
2056     }
2057     *p = NULLCHAR;
2058     return retbuf;
2059 }
2060
2061 /* Remove all highlighting escape sequences in s */
2062 char *
2063 StripHighlight (char *s)
2064 {
2065     static char retbuf[MSG_SIZ];
2066     char *p = retbuf;
2067
2068     while (*s != NULLCHAR) {
2069         while (*s == '\033') {
2070             while (*s != NULLCHAR && !isalpha(*s)) s++;
2071             if (*s != NULLCHAR) s++;
2072         }
2073         while (*s != NULLCHAR && *s != '\033') {
2074             *p++ = *s++;
2075         }
2076     }
2077     *p = NULLCHAR;
2078     return retbuf;
2079 }
2080
2081 char engineVariant[MSG_SIZ];
2082 char *variantNames[] = VARIANT_NAMES;
2083 char *
2084 VariantName (VariantClass v)
2085 {
2086     if(v == VariantUnknown || *engineVariant) return engineVariant;
2087     return variantNames[v];
2088 }
2089
2090
2091 /* Identify a variant from the strings the chess servers use or the
2092    PGN Variant tag names we use. */
2093 VariantClass
2094 StringToVariant (char *e)
2095 {
2096     char *p;
2097     int wnum = -1;
2098     VariantClass v = VariantNormal;
2099     int i, found = FALSE;
2100     char buf[MSG_SIZ];
2101     int len;
2102
2103     if (!e) return v;
2104
2105     /* [HGM] skip over optional board-size prefixes */
2106     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2107         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2108         while( *e++ != '_');
2109     }
2110
2111     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2112         v = VariantNormal;
2113         found = TRUE;
2114     } else
2115     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2116       if (p = StrCaseStr(e, variantNames[i])) {
2117         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2118         v = (VariantClass) i;
2119         found = TRUE;
2120         break;
2121       }
2122     }
2123
2124     if (!found) {
2125       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2126           || StrCaseStr(e, "wild/fr")
2127           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2128         v = VariantFischeRandom;
2129       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2130                  (i = 1, p = StrCaseStr(e, "w"))) {
2131         p += i;
2132         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2133         if (isdigit(*p)) {
2134           wnum = atoi(p);
2135         } else {
2136           wnum = -1;
2137         }
2138         switch (wnum) {
2139         case 0: /* FICS only, actually */
2140         case 1:
2141           /* Castling legal even if K starts on d-file */
2142           v = VariantWildCastle;
2143           break;
2144         case 2:
2145         case 3:
2146         case 4:
2147           /* Castling illegal even if K & R happen to start in
2148              normal positions. */
2149           v = VariantNoCastle;
2150           break;
2151         case 5:
2152         case 7:
2153         case 8:
2154         case 10:
2155         case 11:
2156         case 12:
2157         case 13:
2158         case 14:
2159         case 15:
2160         case 18:
2161         case 19:
2162           /* Castling legal iff K & R start in normal positions */
2163           v = VariantNormal;
2164           break;
2165         case 6:
2166         case 20:
2167         case 21:
2168           /* Special wilds for position setup; unclear what to do here */
2169           v = VariantLoadable;
2170           break;
2171         case 9:
2172           /* Bizarre ICC game */
2173           v = VariantTwoKings;
2174           break;
2175         case 16:
2176           v = VariantKriegspiel;
2177           break;
2178         case 17:
2179           v = VariantLosers;
2180           break;
2181         case 22:
2182           v = VariantFischeRandom;
2183           break;
2184         case 23:
2185           v = VariantCrazyhouse;
2186           break;
2187         case 24:
2188           v = VariantBughouse;
2189           break;
2190         case 25:
2191           v = Variant3Check;
2192           break;
2193         case 26:
2194           /* Not quite the same as FICS suicide! */
2195           v = VariantGiveaway;
2196           break;
2197         case 27:
2198           v = VariantAtomic;
2199           break;
2200         case 28:
2201           v = VariantShatranj;
2202           break;
2203
2204         /* Temporary names for future ICC types.  The name *will* change in
2205            the next xboard/WinBoard release after ICC defines it. */
2206         case 29:
2207           v = Variant29;
2208           break;
2209         case 30:
2210           v = Variant30;
2211           break;
2212         case 31:
2213           v = Variant31;
2214           break;
2215         case 32:
2216           v = Variant32;
2217           break;
2218         case 33:
2219           v = Variant33;
2220           break;
2221         case 34:
2222           v = Variant34;
2223           break;
2224         case 35:
2225           v = Variant35;
2226           break;
2227         case 36:
2228           v = Variant36;
2229           break;
2230         case 37:
2231           v = VariantShogi;
2232           break;
2233         case 38:
2234           v = VariantXiangqi;
2235           break;
2236         case 39:
2237           v = VariantCourier;
2238           break;
2239         case 40:
2240           v = VariantGothic;
2241           break;
2242         case 41:
2243           v = VariantCapablanca;
2244           break;
2245         case 42:
2246           v = VariantKnightmate;
2247           break;
2248         case 43:
2249           v = VariantFairy;
2250           break;
2251         case 44:
2252           v = VariantCylinder;
2253           break;
2254         case 45:
2255           v = VariantFalcon;
2256           break;
2257         case 46:
2258           v = VariantCapaRandom;
2259           break;
2260         case 47:
2261           v = VariantBerolina;
2262           break;
2263         case 48:
2264           v = VariantJanus;
2265           break;
2266         case 49:
2267           v = VariantSuper;
2268           break;
2269         case 50:
2270           v = VariantGreat;
2271           break;
2272         case -1:
2273           /* Found "wild" or "w" in the string but no number;
2274              must assume it's normal chess. */
2275           v = VariantNormal;
2276           break;
2277         default:
2278           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2279           if( (len >= MSG_SIZ) && appData.debugMode )
2280             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2281
2282           DisplayError(buf, 0);
2283           v = VariantUnknown;
2284           break;
2285         }
2286       }
2287     }
2288     if (appData.debugMode) {
2289       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2290               e, wnum, VariantName(v));
2291     }
2292     return v;
2293 }
2294
2295 static int leftover_start = 0, leftover_len = 0;
2296 char star_match[STAR_MATCH_N][MSG_SIZ];
2297
2298 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2299    advance *index beyond it, and set leftover_start to the new value of
2300    *index; else return FALSE.  If pattern contains the character '*', it
2301    matches any sequence of characters not containing '\r', '\n', or the
2302    character following the '*' (if any), and the matched sequence(s) are
2303    copied into star_match.
2304    */
2305 int
2306 looking_at ( char *buf, int *index, char *pattern)
2307 {
2308     char *bufp = &buf[*index], *patternp = pattern;
2309     int star_count = 0;
2310     char *matchp = star_match[0];
2311
2312     for (;;) {
2313         if (*patternp == NULLCHAR) {
2314             *index = leftover_start = bufp - buf;
2315             *matchp = NULLCHAR;
2316             return TRUE;
2317         }
2318         if (*bufp == NULLCHAR) return FALSE;
2319         if (*patternp == '*') {
2320             if (*bufp == *(patternp + 1)) {
2321                 *matchp = NULLCHAR;
2322                 matchp = star_match[++star_count];
2323                 patternp += 2;
2324                 bufp++;
2325                 continue;
2326             } else if (*bufp == '\n' || *bufp == '\r') {
2327                 patternp++;
2328                 if (*patternp == NULLCHAR)
2329                   continue;
2330                 else
2331                   return FALSE;
2332             } else {
2333                 *matchp++ = *bufp++;
2334                 continue;
2335             }
2336         }
2337         if (*patternp != *bufp) return FALSE;
2338         patternp++;
2339         bufp++;
2340     }
2341 }
2342
2343 void
2344 SendToPlayer (char *data, int length)
2345 {
2346     int error, outCount;
2347     outCount = OutputToProcess(NoProc, data, length, &error);
2348     if (outCount < length) {
2349         DisplayFatalError(_("Error writing to display"), error, 1);
2350     }
2351 }
2352
2353 void
2354 PackHolding (char packed[], char *holding)
2355 {
2356     char *p = holding;
2357     char *q = packed;
2358     int runlength = 0;
2359     int curr = 9999;
2360     do {
2361         if (*p == curr) {
2362             runlength++;
2363         } else {
2364             switch (runlength) {
2365               case 0:
2366                 break;
2367               case 1:
2368                 *q++ = curr;
2369                 break;
2370               case 2:
2371                 *q++ = curr;
2372                 *q++ = curr;
2373                 break;
2374               default:
2375                 sprintf(q, "%d", runlength);
2376                 while (*q) q++;
2377                 *q++ = curr;
2378                 break;
2379             }
2380             runlength = 1;
2381             curr = *p;
2382         }
2383     } while (*p++);
2384     *q = NULLCHAR;
2385 }
2386
2387 /* Telnet protocol requests from the front end */
2388 void
2389 TelnetRequest (unsigned char ddww, unsigned char option)
2390 {
2391     unsigned char msg[3];
2392     int outCount, outError;
2393
2394     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2395
2396     if (appData.debugMode) {
2397         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2398         switch (ddww) {
2399           case TN_DO:
2400             ddwwStr = "DO";
2401             break;
2402           case TN_DONT:
2403             ddwwStr = "DONT";
2404             break;
2405           case TN_WILL:
2406             ddwwStr = "WILL";
2407             break;
2408           case TN_WONT:
2409             ddwwStr = "WONT";
2410             break;
2411           default:
2412             ddwwStr = buf1;
2413             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2414             break;
2415         }
2416         switch (option) {
2417           case TN_ECHO:
2418             optionStr = "ECHO";
2419             break;
2420           default:
2421             optionStr = buf2;
2422             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2423             break;
2424         }
2425         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2426     }
2427     msg[0] = TN_IAC;
2428     msg[1] = ddww;
2429     msg[2] = option;
2430     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2431     if (outCount < 3) {
2432         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2433     }
2434 }
2435
2436 void
2437 DoEcho ()
2438 {
2439     if (!appData.icsActive) return;
2440     TelnetRequest(TN_DO, TN_ECHO);
2441 }
2442
2443 void
2444 DontEcho ()
2445 {
2446     if (!appData.icsActive) return;
2447     TelnetRequest(TN_DONT, TN_ECHO);
2448 }
2449
2450 void
2451 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2452 {
2453     /* put the holdings sent to us by the server on the board holdings area */
2454     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2455     char p;
2456     ChessSquare piece;
2457
2458     if(gameInfo.holdingsWidth < 2)  return;
2459     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2460         return; // prevent overwriting by pre-board holdings
2461
2462     if( (int)lowestPiece >= BlackPawn ) {
2463         holdingsColumn = 0;
2464         countsColumn = 1;
2465         holdingsStartRow = BOARD_HEIGHT-1;
2466         direction = -1;
2467     } else {
2468         holdingsColumn = BOARD_WIDTH-1;
2469         countsColumn = BOARD_WIDTH-2;
2470         holdingsStartRow = 0;
2471         direction = 1;
2472     }
2473
2474     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2475         board[i][holdingsColumn] = EmptySquare;
2476         board[i][countsColumn]   = (ChessSquare) 0;
2477     }
2478     while( (p=*holdings++) != NULLCHAR ) {
2479         piece = CharToPiece( ToUpper(p) );
2480         if(piece == EmptySquare) continue;
2481         /*j = (int) piece - (int) WhitePawn;*/
2482         j = PieceToNumber(piece);
2483         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2484         if(j < 0) continue;               /* should not happen */
2485         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2486         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2487         board[holdingsStartRow+j*direction][countsColumn]++;
2488     }
2489 }
2490
2491
2492 void
2493 VariantSwitch (Board board, VariantClass newVariant)
2494 {
2495    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2496    static Board oldBoard;
2497
2498    startedFromPositionFile = FALSE;
2499    if(gameInfo.variant == newVariant) return;
2500
2501    /* [HGM] This routine is called each time an assignment is made to
2502     * gameInfo.variant during a game, to make sure the board sizes
2503     * are set to match the new variant. If that means adding or deleting
2504     * holdings, we shift the playing board accordingly
2505     * This kludge is needed because in ICS observe mode, we get boards
2506     * of an ongoing game without knowing the variant, and learn about the
2507     * latter only later. This can be because of the move list we requested,
2508     * in which case the game history is refilled from the beginning anyway,
2509     * but also when receiving holdings of a crazyhouse game. In the latter
2510     * case we want to add those holdings to the already received position.
2511     */
2512
2513
2514    if (appData.debugMode) {
2515      fprintf(debugFP, "Switch board from %s to %s\n",
2516              VariantName(gameInfo.variant), VariantName(newVariant));
2517      setbuf(debugFP, NULL);
2518    }
2519    shuffleOpenings = 0;       /* [HGM] shuffle */
2520    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2521    switch(newVariant)
2522      {
2523      case VariantShogi:
2524        newWidth = 9;  newHeight = 9;
2525        gameInfo.holdingsSize = 7;
2526      case VariantBughouse:
2527      case VariantCrazyhouse:
2528        newHoldingsWidth = 2; break;
2529      case VariantGreat:
2530        newWidth = 10;
2531      case VariantSuper:
2532        newHoldingsWidth = 2;
2533        gameInfo.holdingsSize = 8;
2534        break;
2535      case VariantGothic:
2536      case VariantCapablanca:
2537      case VariantCapaRandom:
2538        newWidth = 10;
2539      default:
2540        newHoldingsWidth = gameInfo.holdingsSize = 0;
2541      };
2542
2543    if(newWidth  != gameInfo.boardWidth  ||
2544       newHeight != gameInfo.boardHeight ||
2545       newHoldingsWidth != gameInfo.holdingsWidth ) {
2546
2547      /* shift position to new playing area, if needed */
2548      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2549        for(i=0; i<BOARD_HEIGHT; i++)
2550          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2551            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2552              board[i][j];
2553        for(i=0; i<newHeight; i++) {
2554          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2555          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2556        }
2557      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2558        for(i=0; i<BOARD_HEIGHT; i++)
2559          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2560            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2561              board[i][j];
2562      }
2563      board[HOLDINGS_SET] = 0;
2564      gameInfo.boardWidth  = newWidth;
2565      gameInfo.boardHeight = newHeight;
2566      gameInfo.holdingsWidth = newHoldingsWidth;
2567      gameInfo.variant = newVariant;
2568      InitDrawingSizes(-2, 0);
2569    } else gameInfo.variant = newVariant;
2570    CopyBoard(oldBoard, board);   // remember correctly formatted board
2571      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2572    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2573 }
2574
2575 static int loggedOn = FALSE;
2576
2577 /*-- Game start info cache: --*/
2578 int gs_gamenum;
2579 char gs_kind[MSG_SIZ];
2580 static char player1Name[128] = "";
2581 static char player2Name[128] = "";
2582 static char cont_seq[] = "\n\\   ";
2583 static int player1Rating = -1;
2584 static int player2Rating = -1;
2585 /*----------------------------*/
2586
2587 ColorClass curColor = ColorNormal;
2588 int suppressKibitz = 0;
2589
2590 // [HGM] seekgraph
2591 Boolean soughtPending = FALSE;
2592 Boolean seekGraphUp;
2593 #define MAX_SEEK_ADS 200
2594 #define SQUARE 0x80
2595 char *seekAdList[MAX_SEEK_ADS];
2596 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2597 float tcList[MAX_SEEK_ADS];
2598 char colorList[MAX_SEEK_ADS];
2599 int nrOfSeekAds = 0;
2600 int minRating = 1010, maxRating = 2800;
2601 int hMargin = 10, vMargin = 20, h, w;
2602 extern int squareSize, lineGap;
2603
2604 void
2605 PlotSeekAd (int i)
2606 {
2607         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2608         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2609         if(r < minRating+100 && r >=0 ) r = minRating+100;
2610         if(r > maxRating) r = maxRating;
2611         if(tc < 1.f) tc = 1.f;
2612         if(tc > 95.f) tc = 95.f;
2613         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2614         y = ((double)r - minRating)/(maxRating - minRating)
2615             * (h-vMargin-squareSize/8-1) + vMargin;
2616         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2617         if(strstr(seekAdList[i], " u ")) color = 1;
2618         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2619            !strstr(seekAdList[i], "bullet") &&
2620            !strstr(seekAdList[i], "blitz") &&
2621            !strstr(seekAdList[i], "standard") ) color = 2;
2622         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2623         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2624 }
2625
2626 void
2627 PlotSingleSeekAd (int i)
2628 {
2629         PlotSeekAd(i);
2630 }
2631
2632 void
2633 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2634 {
2635         char buf[MSG_SIZ], *ext = "";
2636         VariantClass v = StringToVariant(type);
2637         if(strstr(type, "wild")) {
2638             ext = type + 4; // append wild number
2639             if(v == VariantFischeRandom) type = "chess960"; else
2640             if(v == VariantLoadable) type = "setup"; else
2641             type = VariantName(v);
2642         }
2643         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2644         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2645             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2646             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2647             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2648             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2649             seekNrList[nrOfSeekAds] = nr;
2650             zList[nrOfSeekAds] = 0;
2651             seekAdList[nrOfSeekAds++] = StrSave(buf);
2652             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2653         }
2654 }
2655
2656 void
2657 EraseSeekDot (int i)
2658 {
2659     int x = xList[i], y = yList[i], d=squareSize/4, k;
2660     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2661     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2662     // now replot every dot that overlapped
2663     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2664         int xx = xList[k], yy = yList[k];
2665         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2666             DrawSeekDot(xx, yy, colorList[k]);
2667     }
2668 }
2669
2670 void
2671 RemoveSeekAd (int nr)
2672 {
2673         int i;
2674         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2675             EraseSeekDot(i);
2676             if(seekAdList[i]) free(seekAdList[i]);
2677             seekAdList[i] = seekAdList[--nrOfSeekAds];
2678             seekNrList[i] = seekNrList[nrOfSeekAds];
2679             ratingList[i] = ratingList[nrOfSeekAds];
2680             colorList[i]  = colorList[nrOfSeekAds];
2681             tcList[i] = tcList[nrOfSeekAds];
2682             xList[i]  = xList[nrOfSeekAds];
2683             yList[i]  = yList[nrOfSeekAds];
2684             zList[i]  = zList[nrOfSeekAds];
2685             seekAdList[nrOfSeekAds] = NULL;
2686             break;
2687         }
2688 }
2689
2690 Boolean
2691 MatchSoughtLine (char *line)
2692 {
2693     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2694     int nr, base, inc, u=0; char dummy;
2695
2696     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2697        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2698        (u=1) &&
2699        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2700         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2701         // match: compact and save the line
2702         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2703         return TRUE;
2704     }
2705     return FALSE;
2706 }
2707
2708 int
2709 DrawSeekGraph ()
2710 {
2711     int i;
2712     if(!seekGraphUp) return FALSE;
2713     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2714     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2715
2716     DrawSeekBackground(0, 0, w, h);
2717     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2718     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2719     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2720         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2721         yy = h-1-yy;
2722         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2723         if(i%500 == 0) {
2724             char buf[MSG_SIZ];
2725             snprintf(buf, MSG_SIZ, "%d", i);
2726             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2727         }
2728     }
2729     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2730     for(i=1; i<100; i+=(i<10?1:5)) {
2731         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2732         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2733         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2734             char buf[MSG_SIZ];
2735             snprintf(buf, MSG_SIZ, "%d", i);
2736             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2737         }
2738     }
2739     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2740     return TRUE;
2741 }
2742
2743 int
2744 SeekGraphClick (ClickType click, int x, int y, int moving)
2745 {
2746     static int lastDown = 0, displayed = 0, lastSecond;
2747     if(y < 0) return FALSE;
2748     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2749         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2750         if(!seekGraphUp) return FALSE;
2751         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2752         DrawPosition(TRUE, NULL);
2753         return TRUE;
2754     }
2755     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2756         if(click == Release || moving) return FALSE;
2757         nrOfSeekAds = 0;
2758         soughtPending = TRUE;
2759         SendToICS(ics_prefix);
2760         SendToICS("sought\n"); // should this be "sought all"?
2761     } else { // issue challenge based on clicked ad
2762         int dist = 10000; int i, closest = 0, second = 0;
2763         for(i=0; i<nrOfSeekAds; i++) {
2764             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2765             if(d < dist) { dist = d; closest = i; }
2766             second += (d - zList[i] < 120); // count in-range ads
2767             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2768         }
2769         if(dist < 120) {
2770             char buf[MSG_SIZ];
2771             second = (second > 1);
2772             if(displayed != closest || second != lastSecond) {
2773                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2774                 lastSecond = second; displayed = closest;
2775             }
2776             if(click == Press) {
2777                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2778                 lastDown = closest;
2779                 return TRUE;
2780             } // on press 'hit', only show info
2781             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2782             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2783             SendToICS(ics_prefix);
2784             SendToICS(buf);
2785             return TRUE; // let incoming board of started game pop down the graph
2786         } else if(click == Release) { // release 'miss' is ignored
2787             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2788             if(moving == 2) { // right up-click
2789                 nrOfSeekAds = 0; // refresh graph
2790                 soughtPending = TRUE;
2791                 SendToICS(ics_prefix);
2792                 SendToICS("sought\n"); // should this be "sought all"?
2793             }
2794             return TRUE;
2795         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2796         // press miss or release hit 'pop down' seek graph
2797         seekGraphUp = FALSE;
2798         DrawPosition(TRUE, NULL);
2799     }
2800     return TRUE;
2801 }
2802
2803 void
2804 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2805 {
2806 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2807 #define STARTED_NONE 0
2808 #define STARTED_MOVES 1
2809 #define STARTED_BOARD 2
2810 #define STARTED_OBSERVE 3
2811 #define STARTED_HOLDINGS 4
2812 #define STARTED_CHATTER 5
2813 #define STARTED_COMMENT 6
2814 #define STARTED_MOVES_NOHIDE 7
2815
2816     static int started = STARTED_NONE;
2817     static char parse[20000];
2818     static int parse_pos = 0;
2819     static char buf[BUF_SIZE + 1];
2820     static int firstTime = TRUE, intfSet = FALSE;
2821     static ColorClass prevColor = ColorNormal;
2822     static int savingComment = FALSE;
2823     static int cmatch = 0; // continuation sequence match
2824     char *bp;
2825     char str[MSG_SIZ];
2826     int i, oldi;
2827     int buf_len;
2828     int next_out;
2829     int tkind;
2830     int backup;    /* [DM] For zippy color lines */
2831     char *p;
2832     char talker[MSG_SIZ]; // [HGM] chat
2833     int channel, collective=0;
2834
2835     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2836
2837     if (appData.debugMode) {
2838       if (!error) {
2839         fprintf(debugFP, "<ICS: ");
2840         show_bytes(debugFP, data, count);
2841         fprintf(debugFP, "\n");
2842       }
2843     }
2844
2845     if (appData.debugMode) { int f = forwardMostMove;
2846         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2847                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2848                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2849     }
2850     if (count > 0) {
2851         /* If last read ended with a partial line that we couldn't parse,
2852            prepend it to the new read and try again. */
2853         if (leftover_len > 0) {
2854             for (i=0; i<leftover_len; i++)
2855               buf[i] = buf[leftover_start + i];
2856         }
2857
2858     /* copy new characters into the buffer */
2859     bp = buf + leftover_len;
2860     buf_len=leftover_len;
2861     for (i=0; i<count; i++)
2862     {
2863         // ignore these
2864         if (data[i] == '\r')
2865             continue;
2866
2867         // join lines split by ICS?
2868         if (!appData.noJoin)
2869         {
2870             /*
2871                 Joining just consists of finding matches against the
2872                 continuation sequence, and discarding that sequence
2873                 if found instead of copying it.  So, until a match
2874                 fails, there's nothing to do since it might be the
2875                 complete sequence, and thus, something we don't want
2876                 copied.
2877             */
2878             if (data[i] == cont_seq[cmatch])
2879             {
2880                 cmatch++;
2881                 if (cmatch == strlen(cont_seq))
2882                 {
2883                     cmatch = 0; // complete match.  just reset the counter
2884
2885                     /*
2886                         it's possible for the ICS to not include the space
2887                         at the end of the last word, making our [correct]
2888                         join operation fuse two separate words.  the server
2889                         does this when the space occurs at the width setting.
2890                     */
2891                     if (!buf_len || buf[buf_len-1] != ' ')
2892                     {
2893                         *bp++ = ' ';
2894                         buf_len++;
2895                     }
2896                 }
2897                 continue;
2898             }
2899             else if (cmatch)
2900             {
2901                 /*
2902                     match failed, so we have to copy what matched before
2903                     falling through and copying this character.  In reality,
2904                     this will only ever be just the newline character, but
2905                     it doesn't hurt to be precise.
2906                 */
2907                 strncpy(bp, cont_seq, cmatch);
2908                 bp += cmatch;
2909                 buf_len += cmatch;
2910                 cmatch = 0;
2911             }
2912         }
2913
2914         // copy this char
2915         *bp++ = data[i];
2916         buf_len++;
2917     }
2918
2919         buf[buf_len] = NULLCHAR;
2920 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2921         next_out = 0;
2922         leftover_start = 0;
2923
2924         i = 0;
2925         while (i < buf_len) {
2926             /* Deal with part of the TELNET option negotiation
2927                protocol.  We refuse to do anything beyond the
2928                defaults, except that we allow the WILL ECHO option,
2929                which ICS uses to turn off password echoing when we are
2930                directly connected to it.  We reject this option
2931                if localLineEditing mode is on (always on in xboard)
2932                and we are talking to port 23, which might be a real
2933                telnet server that will try to keep WILL ECHO on permanently.
2934              */
2935             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2936                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2937                 unsigned char option;
2938                 oldi = i;
2939                 switch ((unsigned char) buf[++i]) {
2940                   case TN_WILL:
2941                     if (appData.debugMode)
2942                       fprintf(debugFP, "\n<WILL ");
2943                     switch (option = (unsigned char) buf[++i]) {
2944                       case TN_ECHO:
2945                         if (appData.debugMode)
2946                           fprintf(debugFP, "ECHO ");
2947                         /* Reply only if this is a change, according
2948                            to the protocol rules. */
2949                         if (remoteEchoOption) break;
2950                         if (appData.localLineEditing &&
2951                             atoi(appData.icsPort) == TN_PORT) {
2952                             TelnetRequest(TN_DONT, TN_ECHO);
2953                         } else {
2954                             EchoOff();
2955                             TelnetRequest(TN_DO, TN_ECHO);
2956                             remoteEchoOption = TRUE;
2957                         }
2958                         break;
2959                       default:
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "%d ", option);
2962                         /* Whatever this is, we don't want it. */
2963                         TelnetRequest(TN_DONT, option);
2964                         break;
2965                     }
2966                     break;
2967                   case TN_WONT:
2968                     if (appData.debugMode)
2969                       fprintf(debugFP, "\n<WONT ");
2970                     switch (option = (unsigned char) buf[++i]) {
2971                       case TN_ECHO:
2972                         if (appData.debugMode)
2973                           fprintf(debugFP, "ECHO ");
2974                         /* Reply only if this is a change, according
2975                            to the protocol rules. */
2976                         if (!remoteEchoOption) break;
2977                         EchoOn();
2978                         TelnetRequest(TN_DONT, TN_ECHO);
2979                         remoteEchoOption = FALSE;
2980                         break;
2981                       default:
2982                         if (appData.debugMode)
2983                           fprintf(debugFP, "%d ", (unsigned char) option);
2984                         /* Whatever this is, it must already be turned
2985                            off, because we never agree to turn on
2986                            anything non-default, so according to the
2987                            protocol rules, we don't reply. */
2988                         break;
2989                     }
2990                     break;
2991                   case TN_DO:
2992                     if (appData.debugMode)
2993                       fprintf(debugFP, "\n<DO ");
2994                     switch (option = (unsigned char) buf[++i]) {
2995                       default:
2996                         /* Whatever this is, we refuse to do it. */
2997                         if (appData.debugMode)
2998                           fprintf(debugFP, "%d ", option);
2999                         TelnetRequest(TN_WONT, option);
3000                         break;
3001                     }
3002                     break;
3003                   case TN_DONT:
3004                     if (appData.debugMode)
3005                       fprintf(debugFP, "\n<DONT ");
3006                     switch (option = (unsigned char) buf[++i]) {
3007                       default:
3008                         if (appData.debugMode)
3009                           fprintf(debugFP, "%d ", option);
3010                         /* Whatever this is, we are already not doing
3011                            it, because we never agree to do anything
3012                            non-default, so according to the protocol
3013                            rules, we don't reply. */
3014                         break;
3015                     }
3016                     break;
3017                   case TN_IAC:
3018                     if (appData.debugMode)
3019                       fprintf(debugFP, "\n<IAC ");
3020                     /* Doubled IAC; pass it through */
3021                     i--;
3022                     break;
3023                   default:
3024                     if (appData.debugMode)
3025                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3026                     /* Drop all other telnet commands on the floor */
3027                     break;
3028                 }
3029                 if (oldi > next_out)
3030                   SendToPlayer(&buf[next_out], oldi - next_out);
3031                 if (++i > next_out)
3032                   next_out = i;
3033                 continue;
3034             }
3035
3036             /* OK, this at least will *usually* work */
3037             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3038                 loggedOn = TRUE;
3039             }
3040
3041             if (loggedOn && !intfSet) {
3042                 if (ics_type == ICS_ICC) {
3043                   snprintf(str, MSG_SIZ,
3044                           "/set-quietly interface %s\n/set-quietly style 12\n",
3045                           programVersion);
3046                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3047                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3048                 } else if (ics_type == ICS_CHESSNET) {
3049                   snprintf(str, MSG_SIZ, "/style 12\n");
3050                 } else {
3051                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3052                   strcat(str, programVersion);
3053                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3054                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3055                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3056 #ifdef WIN32
3057                   strcat(str, "$iset nohighlight 1\n");
3058 #endif
3059                   strcat(str, "$iset lock 1\n$style 12\n");
3060                 }
3061                 SendToICS(str);
3062                 NotifyFrontendLogin();
3063                 intfSet = TRUE;
3064             }
3065
3066             if (started == STARTED_COMMENT) {
3067                 /* Accumulate characters in comment */
3068                 parse[parse_pos++] = buf[i];
3069                 if (buf[i] == '\n') {
3070                     parse[parse_pos] = NULLCHAR;
3071                     if(chattingPartner>=0) {
3072                         char mess[MSG_SIZ];
3073                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3074                         OutputChatMessage(chattingPartner, mess);
3075                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3076                             int p;
3077                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3078                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3079                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3080                                 OutputChatMessage(p, mess);
3081                                 break;
3082                             }
3083                         }
3084                         chattingPartner = -1;
3085                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3086                         collective = 0;
3087                     } else
3088                     if(!suppressKibitz) // [HGM] kibitz
3089                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3090                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3091                         int nrDigit = 0, nrAlph = 0, j;
3092                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3093                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3094                         parse[parse_pos] = NULLCHAR;
3095                         // try to be smart: if it does not look like search info, it should go to
3096                         // ICS interaction window after all, not to engine-output window.
3097                         for(j=0; j<parse_pos; j++) { // count letters and digits
3098                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3099                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3100                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3101                         }
3102                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3103                             int depth=0; float score;
3104                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3105                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3106                                 pvInfoList[forwardMostMove-1].depth = depth;
3107                                 pvInfoList[forwardMostMove-1].score = 100*score;
3108                             }
3109                             OutputKibitz(suppressKibitz, parse);
3110                         } else {
3111                             char tmp[MSG_SIZ];
3112                             if(gameMode == IcsObserving) // restore original ICS messages
3113                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3114                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3115                             else
3116                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3117                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3118                             SendToPlayer(tmp, strlen(tmp));
3119                         }
3120                         next_out = i+1; // [HGM] suppress printing in ICS window
3121                     }
3122                     started = STARTED_NONE;
3123                 } else {
3124                     /* Don't match patterns against characters in comment */
3125                     i++;
3126                     continue;
3127                 }
3128             }
3129             if (started == STARTED_CHATTER) {
3130                 if (buf[i] != '\n') {
3131                     /* Don't match patterns against characters in chatter */
3132                     i++;
3133                     continue;
3134                 }
3135                 started = STARTED_NONE;
3136                 if(suppressKibitz) next_out = i+1;
3137             }
3138
3139             /* Kludge to deal with rcmd protocol */
3140             if (firstTime && looking_at(buf, &i, "\001*")) {
3141                 DisplayFatalError(&buf[1], 0, 1);
3142                 continue;
3143             } else {
3144                 firstTime = FALSE;
3145             }
3146
3147             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3148                 ics_type = ICS_ICC;
3149                 ics_prefix = "/";
3150                 if (appData.debugMode)
3151                   fprintf(debugFP, "ics_type %d\n", ics_type);
3152                 continue;
3153             }
3154             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3155                 ics_type = ICS_FICS;
3156                 ics_prefix = "$";
3157                 if (appData.debugMode)
3158                   fprintf(debugFP, "ics_type %d\n", ics_type);
3159                 continue;
3160             }
3161             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3162                 ics_type = ICS_CHESSNET;
3163                 ics_prefix = "/";
3164                 if (appData.debugMode)
3165                   fprintf(debugFP, "ics_type %d\n", ics_type);
3166                 continue;
3167             }
3168
3169             if (!loggedOn &&
3170                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3171                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3172                  looking_at(buf, &i, "will be \"*\""))) {
3173               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3174               continue;
3175             }
3176
3177             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3178               char buf[MSG_SIZ];
3179               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3180               DisplayIcsInteractionTitle(buf);
3181               have_set_title = TRUE;
3182             }
3183
3184             /* skip finger notes */
3185             if (started == STARTED_NONE &&
3186                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3187                  (buf[i] == '1' && buf[i+1] == '0')) &&
3188                 buf[i+2] == ':' && buf[i+3] == ' ') {
3189               started = STARTED_CHATTER;
3190               i += 3;
3191               continue;
3192             }
3193
3194             oldi = i;
3195             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3196             if(appData.seekGraph) {
3197                 if(soughtPending && MatchSoughtLine(buf+i)) {
3198                     i = strstr(buf+i, "rated") - buf;
3199                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3200                     next_out = leftover_start = i;
3201                     started = STARTED_CHATTER;
3202                     suppressKibitz = TRUE;
3203                     continue;
3204                 }
3205                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3206                         && looking_at(buf, &i, "* ads displayed")) {
3207                     soughtPending = FALSE;
3208                     seekGraphUp = TRUE;
3209                     DrawSeekGraph();
3210                     continue;
3211                 }
3212                 if(appData.autoRefresh) {
3213                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3214                         int s = (ics_type == ICS_ICC); // ICC format differs
3215                         if(seekGraphUp)
3216                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3217                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3218                         looking_at(buf, &i, "*% "); // eat prompt
3219                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3220                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                         next_out = i; // suppress
3222                         continue;
3223                     }
3224                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3225                         char *p = star_match[0];
3226                         while(*p) {
3227                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3228                             while(*p && *p++ != ' '); // next
3229                         }
3230                         looking_at(buf, &i, "*% "); // eat prompt
3231                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3232                         next_out = i;
3233                         continue;
3234                     }
3235                 }
3236             }
3237
3238             /* skip formula vars */
3239             if (started == STARTED_NONE &&
3240                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3241               started = STARTED_CHATTER;
3242               i += 3;
3243               continue;
3244             }
3245
3246             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3247             if (appData.autoKibitz && started == STARTED_NONE &&
3248                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3249                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3250                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3251                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3252                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3253                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3254                         suppressKibitz = TRUE;
3255                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3256                         next_out = i;
3257                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3258                                 && (gameMode == IcsPlayingWhite)) ||
3259                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3260                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3261                             started = STARTED_CHATTER; // own kibitz we simply discard
3262                         else {
3263                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3264                             parse_pos = 0; parse[0] = NULLCHAR;
3265                             savingComment = TRUE;
3266                             suppressKibitz = gameMode != IcsObserving ? 2 :
3267                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3268                         }
3269                         continue;
3270                 } else
3271                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3272                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3273                          && atoi(star_match[0])) {
3274                     // suppress the acknowledgements of our own autoKibitz
3275                     char *p;
3276                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3277                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3278                     SendToPlayer(star_match[0], strlen(star_match[0]));
3279                     if(looking_at(buf, &i, "*% ")) // eat prompt
3280                         suppressKibitz = FALSE;
3281                     next_out = i;
3282                     continue;
3283                 }
3284             } // [HGM] kibitz: end of patch
3285
3286             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3287
3288             // [HGM] chat: intercept tells by users for which we have an open chat window
3289             channel = -1;
3290             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3291                                            looking_at(buf, &i, "* whispers:") ||
3292                                            looking_at(buf, &i, "* kibitzes:") ||
3293                                            looking_at(buf, &i, "* shouts:") ||
3294                                            looking_at(buf, &i, "* c-shouts:") ||
3295                                            looking_at(buf, &i, "--> * ") ||
3296                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3297                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3298                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3299                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3300                 int p;
3301                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3302                 chattingPartner = -1; collective = 0;
3303
3304                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3305                 for(p=0; p<MAX_CHAT; p++) {
3306                     collective = 1;
3307                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3308                     talker[0] = '['; strcat(talker, "] ");
3309                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3310                     chattingPartner = p; break;
3311                     }
3312                 } else
3313                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3314                 for(p=0; p<MAX_CHAT; p++) {
3315                     collective = 1;
3316                     if(!strcmp("kibitzes", chatPartner[p])) {
3317                         talker[0] = '['; strcat(talker, "] ");
3318                         chattingPartner = p; break;
3319                     }
3320                 } else
3321                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3322                 for(p=0; p<MAX_CHAT; p++) {
3323                     collective = 1;
3324                     if(!strcmp("whispers", chatPartner[p])) {
3325                         talker[0] = '['; strcat(talker, "] ");
3326                         chattingPartner = p; break;
3327                     }
3328                 } else
3329                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3330                   if(buf[i-8] == '-' && buf[i-3] == 't')
3331                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3332                     collective = 1;
3333                     if(!strcmp("c-shouts", chatPartner[p])) {
3334                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3335                         chattingPartner = p; break;
3336                     }
3337                   }
3338                   if(chattingPartner < 0)
3339                   for(p=0; p<MAX_CHAT; p++) {
3340                     collective = 1;
3341                     if(!strcmp("shouts", chatPartner[p])) {
3342                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3343                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3344                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3345                         chattingPartner = p; break;
3346                     }
3347                   }
3348                 }
3349                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3350                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3351                     talker[0] = 0;
3352                     Colorize(ColorTell, FALSE);
3353                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3354                     collective |= 2;
3355                     chattingPartner = p; break;
3356                 }
3357                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3358                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3359                     started = STARTED_COMMENT;
3360                     parse_pos = 0; parse[0] = NULLCHAR;
3361                     savingComment = 3 + chattingPartner; // counts as TRUE
3362                     if(collective == 3) i = oldi; else {
3363                         suppressKibitz = TRUE;
3364                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3365                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3366                         continue;
3367                     }
3368                 }
3369             } // [HGM] chat: end of patch
3370
3371           backup = i;
3372             if (appData.zippyTalk || appData.zippyPlay) {
3373                 /* [DM] Backup address for color zippy lines */
3374 #if ZIPPY
3375                if (loggedOn == TRUE)
3376                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3377                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3378 #endif
3379             } // [DM] 'else { ' deleted
3380                 if (
3381                     /* Regular tells and says */
3382                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3383                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3384                     looking_at(buf, &i, "* says: ") ||
3385                     /* Don't color "message" or "messages" output */
3386                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3387                     looking_at(buf, &i, "*. * at *:*: ") ||
3388                     looking_at(buf, &i, "--* (*:*): ") ||
3389                     /* Message notifications (same color as tells) */
3390                     looking_at(buf, &i, "* has left a message ") ||
3391                     looking_at(buf, &i, "* just sent you a message:\n") ||
3392                     /* Whispers and kibitzes */
3393                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3394                     looking_at(buf, &i, "* kibitzes: ") ||
3395                     /* Channel tells */
3396                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3397
3398                   if (tkind == 1 && strchr(star_match[0], ':')) {
3399                       /* Avoid "tells you:" spoofs in channels */
3400                      tkind = 3;
3401                   }
3402                   if (star_match[0][0] == NULLCHAR ||
3403                       strchr(star_match[0], ' ') ||
3404                       (tkind == 3 && strchr(star_match[1], ' '))) {
3405                     /* Reject bogus matches */
3406                     i = oldi;
3407                   } else {
3408                     if (appData.colorize) {
3409                       if (oldi > next_out) {
3410                         SendToPlayer(&buf[next_out], oldi - next_out);
3411                         next_out = oldi;
3412                       }
3413                       switch (tkind) {
3414                       case 1:
3415                         Colorize(ColorTell, FALSE);
3416                         curColor = ColorTell;
3417                         break;
3418                       case 2:
3419                         Colorize(ColorKibitz, FALSE);
3420                         curColor = ColorKibitz;
3421                         break;
3422                       case 3:
3423                         p = strrchr(star_match[1], '(');
3424                         if (p == NULL) {
3425                           p = star_match[1];
3426                         } else {
3427                           p++;
3428                         }
3429                         if (atoi(p) == 1) {
3430                           Colorize(ColorChannel1, FALSE);
3431                           curColor = ColorChannel1;
3432                         } else {
3433                           Colorize(ColorChannel, FALSE);
3434                           curColor = ColorChannel;
3435                         }
3436                         break;
3437                       case 5:
3438                         curColor = ColorNormal;
3439                         break;
3440                       }
3441                     }
3442                     if (started == STARTED_NONE && appData.autoComment &&
3443                         (gameMode == IcsObserving ||
3444                          gameMode == IcsPlayingWhite ||
3445                          gameMode == IcsPlayingBlack)) {
3446                       parse_pos = i - oldi;
3447                       memcpy(parse, &buf[oldi], parse_pos);
3448                       parse[parse_pos] = NULLCHAR;
3449                       started = STARTED_COMMENT;
3450                       savingComment = TRUE;
3451                     } else if(collective != 3) {
3452                       started = STARTED_CHATTER;
3453                       savingComment = FALSE;
3454                     }
3455                     loggedOn = TRUE;
3456                     continue;
3457                   }
3458                 }
3459
3460                 if (looking_at(buf, &i, "* s-shouts: ") ||
3461                     looking_at(buf, &i, "* c-shouts: ")) {
3462                     if (appData.colorize) {
3463                         if (oldi > next_out) {
3464                             SendToPlayer(&buf[next_out], oldi - next_out);
3465                             next_out = oldi;
3466                         }
3467                         Colorize(ColorSShout, FALSE);
3468                         curColor = ColorSShout;
3469                     }
3470                     loggedOn = TRUE;
3471                     started = STARTED_CHATTER;
3472                     continue;
3473                 }
3474
3475                 if (looking_at(buf, &i, "--->")) {
3476                     loggedOn = TRUE;
3477                     continue;
3478                 }
3479
3480                 if (looking_at(buf, &i, "* shouts: ") ||
3481                     looking_at(buf, &i, "--> ")) {
3482                     if (appData.colorize) {
3483                         if (oldi > next_out) {
3484                             SendToPlayer(&buf[next_out], oldi - next_out);
3485                             next_out = oldi;
3486                         }
3487                         Colorize(ColorShout, FALSE);
3488                         curColor = ColorShout;
3489                     }
3490                     loggedOn = TRUE;
3491                     started = STARTED_CHATTER;
3492                     continue;
3493                 }
3494
3495                 if (looking_at( buf, &i, "Challenge:")) {
3496                     if (appData.colorize) {
3497                         if (oldi > next_out) {
3498                             SendToPlayer(&buf[next_out], oldi - next_out);
3499                             next_out = oldi;
3500                         }
3501                         Colorize(ColorChallenge, FALSE);
3502                         curColor = ColorChallenge;
3503                     }
3504                     loggedOn = TRUE;
3505                     continue;
3506                 }
3507
3508                 if (looking_at(buf, &i, "* offers you") ||
3509                     looking_at(buf, &i, "* offers to be") ||
3510                     looking_at(buf, &i, "* would like to") ||
3511                     looking_at(buf, &i, "* requests to") ||
3512                     looking_at(buf, &i, "Your opponent offers") ||
3513                     looking_at(buf, &i, "Your opponent requests")) {
3514
3515                     if (appData.colorize) {
3516                         if (oldi > next_out) {
3517                             SendToPlayer(&buf[next_out], oldi - next_out);
3518                             next_out = oldi;
3519                         }
3520                         Colorize(ColorRequest, FALSE);
3521                         curColor = ColorRequest;
3522                     }
3523                     continue;
3524                 }
3525
3526                 if (looking_at(buf, &i, "* (*) seeking")) {
3527                     if (appData.colorize) {
3528                         if (oldi > next_out) {
3529                             SendToPlayer(&buf[next_out], oldi - next_out);
3530                             next_out = oldi;
3531                         }
3532                         Colorize(ColorSeek, FALSE);
3533                         curColor = ColorSeek;
3534                     }
3535                     continue;
3536             }
3537
3538           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3539
3540             if (looking_at(buf, &i, "\\   ")) {
3541                 if (prevColor != ColorNormal) {
3542                     if (oldi > next_out) {
3543                         SendToPlayer(&buf[next_out], oldi - next_out);
3544                         next_out = oldi;
3545                     }
3546                     Colorize(prevColor, TRUE);
3547                     curColor = prevColor;
3548                 }
3549                 if (savingComment) {
3550                     parse_pos = i - oldi;
3551                     memcpy(parse, &buf[oldi], parse_pos);
3552                     parse[parse_pos] = NULLCHAR;
3553                     started = STARTED_COMMENT;
3554                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3555                         chattingPartner = savingComment - 3; // kludge to remember the box
3556                 } else {
3557                     started = STARTED_CHATTER;
3558                 }
3559                 continue;
3560             }
3561
3562             if (looking_at(buf, &i, "Black Strength :") ||
3563                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3564                 looking_at(buf, &i, "<10>") ||
3565                 looking_at(buf, &i, "#@#")) {
3566                 /* Wrong board style */
3567                 loggedOn = TRUE;
3568                 SendToICS(ics_prefix);
3569                 SendToICS("set style 12\n");
3570                 SendToICS(ics_prefix);
3571                 SendToICS("refresh\n");
3572                 continue;
3573             }
3574
3575             if (looking_at(buf, &i, "login:")) {
3576               if (!have_sent_ICS_logon) {
3577                 if(ICSInitScript())
3578                   have_sent_ICS_logon = 1;
3579                 else // no init script was found
3580                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3581               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3582                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3583               }
3584                 continue;
3585             }
3586
3587             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3588                 (looking_at(buf, &i, "\n<12> ") ||
3589                  looking_at(buf, &i, "<12> "))) {
3590                 loggedOn = TRUE;
3591                 if (oldi > next_out) {
3592                     SendToPlayer(&buf[next_out], oldi - next_out);
3593                 }
3594                 next_out = i;
3595                 started = STARTED_BOARD;
3596                 parse_pos = 0;
3597                 continue;
3598             }
3599
3600             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3601                 looking_at(buf, &i, "<b1> ")) {
3602                 if (oldi > next_out) {
3603                     SendToPlayer(&buf[next_out], oldi - next_out);
3604                 }
3605                 next_out = i;
3606                 started = STARTED_HOLDINGS;
3607                 parse_pos = 0;
3608                 continue;
3609             }
3610
3611             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3612                 loggedOn = TRUE;
3613                 /* Header for a move list -- first line */
3614
3615                 switch (ics_getting_history) {
3616                   case H_FALSE:
3617                     switch (gameMode) {
3618                       case IcsIdle:
3619                       case BeginningOfGame:
3620                         /* User typed "moves" or "oldmoves" while we
3621                            were idle.  Pretend we asked for these
3622                            moves and soak them up so user can step
3623                            through them and/or save them.
3624                            */
3625                         Reset(FALSE, TRUE);
3626                         gameMode = IcsObserving;
3627                         ModeHighlight();
3628                         ics_gamenum = -1;
3629                         ics_getting_history = H_GOT_UNREQ_HEADER;
3630                         break;
3631                       case EditGame: /*?*/
3632                       case EditPosition: /*?*/
3633                         /* Should above feature work in these modes too? */
3634                         /* For now it doesn't */
3635                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3636                         break;
3637                       default:
3638                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3639                         break;
3640                     }
3641                     break;
3642                   case H_REQUESTED:
3643                     /* Is this the right one? */
3644                     if (gameInfo.white && gameInfo.black &&
3645                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3646                         strcmp(gameInfo.black, star_match[2]) == 0) {
3647                         /* All is well */
3648                         ics_getting_history = H_GOT_REQ_HEADER;
3649                     }
3650                     break;
3651                   case H_GOT_REQ_HEADER:
3652                   case H_GOT_UNREQ_HEADER:
3653                   case H_GOT_UNWANTED_HEADER:
3654                   case H_GETTING_MOVES:
3655                     /* Should not happen */
3656                     DisplayError(_("Error gathering move list: two headers"), 0);
3657                     ics_getting_history = H_FALSE;
3658                     break;
3659                 }
3660
3661                 /* Save player ratings into gameInfo if needed */
3662                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3663                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3664                     (gameInfo.whiteRating == -1 ||
3665                      gameInfo.blackRating == -1)) {
3666
3667                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3668                     gameInfo.blackRating = string_to_rating(star_match[3]);
3669                     if (appData.debugMode)
3670                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3671                               gameInfo.whiteRating, gameInfo.blackRating);
3672                 }
3673                 continue;
3674             }
3675
3676             if (looking_at(buf, &i,
3677               "* * match, initial time: * minute*, increment: * second")) {
3678                 /* Header for a move list -- second line */
3679                 /* Initial board will follow if this is a wild game */
3680                 if (gameInfo.event != NULL) free(gameInfo.event);
3681                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3682                 gameInfo.event = StrSave(str);
3683                 /* [HGM] we switched variant. Translate boards if needed. */
3684                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3685                 continue;
3686             }
3687
3688             if (looking_at(buf, &i, "Move  ")) {
3689                 /* Beginning of a move list */
3690                 switch (ics_getting_history) {
3691                   case H_FALSE:
3692                     /* Normally should not happen */
3693                     /* Maybe user hit reset while we were parsing */
3694                     break;
3695                   case H_REQUESTED:
3696                     /* Happens if we are ignoring a move list that is not
3697                      * the one we just requested.  Common if the user
3698                      * tries to observe two games without turning off
3699                      * getMoveList */
3700                     break;
3701                   case H_GETTING_MOVES:
3702                     /* Should not happen */
3703                     DisplayError(_("Error gathering move list: nested"), 0);
3704                     ics_getting_history = H_FALSE;
3705                     break;
3706                   case H_GOT_REQ_HEADER:
3707                     ics_getting_history = H_GETTING_MOVES;
3708                     started = STARTED_MOVES;
3709                     parse_pos = 0;
3710                     if (oldi > next_out) {
3711                         SendToPlayer(&buf[next_out], oldi - next_out);
3712                     }
3713                     break;
3714                   case H_GOT_UNREQ_HEADER:
3715                     ics_getting_history = H_GETTING_MOVES;
3716                     started = STARTED_MOVES_NOHIDE;
3717                     parse_pos = 0;
3718                     break;
3719                   case H_GOT_UNWANTED_HEADER:
3720                     ics_getting_history = H_FALSE;
3721                     break;
3722                 }
3723                 continue;
3724             }
3725
3726             if (looking_at(buf, &i, "% ") ||
3727                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3728                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3729                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3730                     soughtPending = FALSE;
3731                     seekGraphUp = TRUE;
3732                     DrawSeekGraph();
3733                 }
3734                 if(suppressKibitz) next_out = i;
3735                 savingComment = FALSE;
3736                 suppressKibitz = 0;
3737                 switch (started) {
3738                   case STARTED_MOVES:
3739                   case STARTED_MOVES_NOHIDE:
3740                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3741                     parse[parse_pos + i - oldi] = NULLCHAR;
3742                     ParseGameHistory(parse);
3743 #if ZIPPY
3744                     if (appData.zippyPlay && first.initDone) {
3745                         FeedMovesToProgram(&first, forwardMostMove);
3746                         if (gameMode == IcsPlayingWhite) {
3747                             if (WhiteOnMove(forwardMostMove)) {
3748                                 if (first.sendTime) {
3749                                   if (first.useColors) {
3750                                     SendToProgram("black\n", &first);
3751                                   }
3752                                   SendTimeRemaining(&first, TRUE);
3753                                 }
3754                                 if (first.useColors) {
3755                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3756                                 }
3757                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3758                                 first.maybeThinking = TRUE;
3759                             } else {
3760                                 if (first.usePlayother) {
3761                                   if (first.sendTime) {
3762                                     SendTimeRemaining(&first, TRUE);
3763                                   }
3764                                   SendToProgram("playother\n", &first);
3765                                   firstMove = FALSE;
3766                                 } else {
3767                                   firstMove = TRUE;
3768                                 }
3769                             }
3770                         } else if (gameMode == IcsPlayingBlack) {
3771                             if (!WhiteOnMove(forwardMostMove)) {
3772                                 if (first.sendTime) {
3773                                   if (first.useColors) {
3774                                     SendToProgram("white\n", &first);
3775                                   }
3776                                   SendTimeRemaining(&first, FALSE);
3777                                 }
3778                                 if (first.useColors) {
3779                                   SendToProgram("black\n", &first);
3780                                 }
3781                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3782                                 first.maybeThinking = TRUE;
3783                             } else {
3784                                 if (first.usePlayother) {
3785                                   if (first.sendTime) {
3786                                     SendTimeRemaining(&first, FALSE);
3787                                   }
3788                                   SendToProgram("playother\n", &first);
3789                                   firstMove = FALSE;
3790                                 } else {
3791                                   firstMove = TRUE;
3792                                 }
3793                             }
3794                         }
3795                     }
3796 #endif
3797                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3798                         /* Moves came from oldmoves or moves command
3799                            while we weren't doing anything else.
3800                            */
3801                         currentMove = forwardMostMove;
3802                         ClearHighlights();/*!!could figure this out*/
3803                         flipView = appData.flipView;
3804                         DrawPosition(TRUE, boards[currentMove]);
3805                         DisplayBothClocks();
3806                         snprintf(str, MSG_SIZ, "%s %s %s",
3807                                 gameInfo.white, _("vs."),  gameInfo.black);
3808                         DisplayTitle(str);
3809                         gameMode = IcsIdle;
3810                     } else {
3811                         /* Moves were history of an active game */
3812                         if (gameInfo.resultDetails != NULL) {
3813                             free(gameInfo.resultDetails);
3814                             gameInfo.resultDetails = NULL;
3815                         }
3816                     }
3817                     HistorySet(parseList, backwardMostMove,
3818                                forwardMostMove, currentMove-1);
3819                     DisplayMove(currentMove - 1);
3820                     if (started == STARTED_MOVES) next_out = i;
3821                     started = STARTED_NONE;
3822                     ics_getting_history = H_FALSE;
3823                     break;
3824
3825                   case STARTED_OBSERVE:
3826                     started = STARTED_NONE;
3827                     SendToICS(ics_prefix);
3828                     SendToICS("refresh\n");
3829                     break;
3830
3831                   default:
3832                     break;
3833                 }
3834                 if(bookHit) { // [HGM] book: simulate book reply
3835                     static char bookMove[MSG_SIZ]; // a bit generous?
3836
3837                     programStats.nodes = programStats.depth = programStats.time =
3838                     programStats.score = programStats.got_only_move = 0;
3839                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3840
3841                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3842                     strcat(bookMove, bookHit);
3843                     HandleMachineMove(bookMove, &first);
3844                 }
3845                 continue;
3846             }
3847
3848             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3849                  started == STARTED_HOLDINGS ||
3850                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3851                 /* Accumulate characters in move list or board */
3852                 parse[parse_pos++] = buf[i];
3853             }
3854
3855             /* Start of game messages.  Mostly we detect start of game
3856                when the first board image arrives.  On some versions
3857                of the ICS, though, we need to do a "refresh" after starting
3858                to observe in order to get the current board right away. */
3859             if (looking_at(buf, &i, "Adding game * to observation list")) {
3860                 started = STARTED_OBSERVE;
3861                 continue;
3862             }
3863
3864             /* Handle auto-observe */
3865             if (appData.autoObserve &&
3866                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3867                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3868                 char *player;
3869                 /* Choose the player that was highlighted, if any. */
3870                 if (star_match[0][0] == '\033' ||
3871                     star_match[1][0] != '\033') {
3872                     player = star_match[0];
3873                 } else {
3874                     player = star_match[2];
3875                 }
3876                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3877                         ics_prefix, StripHighlightAndTitle(player));
3878                 SendToICS(str);
3879
3880                 /* Save ratings from notify string */
3881                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3882                 player1Rating = string_to_rating(star_match[1]);
3883                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3884                 player2Rating = string_to_rating(star_match[3]);
3885
3886                 if (appData.debugMode)
3887                   fprintf(debugFP,
3888                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3889                           player1Name, player1Rating,
3890                           player2Name, player2Rating);
3891
3892                 continue;
3893             }
3894
3895             /* Deal with automatic examine mode after a game,
3896                and with IcsObserving -> IcsExamining transition */
3897             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3898                 looking_at(buf, &i, "has made you an examiner of game *")) {
3899
3900                 int gamenum = atoi(star_match[0]);
3901                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3902                     gamenum == ics_gamenum) {
3903                     /* We were already playing or observing this game;
3904                        no need to refetch history */
3905                     gameMode = IcsExamining;
3906                     if (pausing) {
3907                         pauseExamForwardMostMove = forwardMostMove;
3908                     } else if (currentMove < forwardMostMove) {
3909                         ForwardInner(forwardMostMove);
3910                     }
3911                 } else {
3912                     /* I don't think this case really can happen */
3913                     SendToICS(ics_prefix);
3914                     SendToICS("refresh\n");
3915                 }
3916                 continue;
3917             }
3918
3919             /* Error messages */
3920 //          if (ics_user_moved) {
3921             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3922                 if (looking_at(buf, &i, "Illegal move") ||
3923                     looking_at(buf, &i, "Not a legal move") ||
3924                     looking_at(buf, &i, "Your king is in check") ||
3925                     looking_at(buf, &i, "It isn't your turn") ||
3926                     looking_at(buf, &i, "It is not your move")) {
3927                     /* Illegal move */
3928                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3929                         currentMove = forwardMostMove-1;
3930                         DisplayMove(currentMove - 1); /* before DMError */
3931                         DrawPosition(FALSE, boards[currentMove]);
3932                         SwitchClocks(forwardMostMove-1); // [HGM] race
3933                         DisplayBothClocks();
3934                     }
3935                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3936                     ics_user_moved = 0;
3937                     continue;
3938                 }
3939             }
3940
3941             if (looking_at(buf, &i, "still have time") ||
3942                 looking_at(buf, &i, "not out of time") ||
3943                 looking_at(buf, &i, "either player is out of time") ||
3944                 looking_at(buf, &i, "has timeseal; checking")) {
3945                 /* We must have called his flag a little too soon */
3946                 whiteFlag = blackFlag = FALSE;
3947                 continue;
3948             }
3949
3950             if (looking_at(buf, &i, "added * seconds to") ||
3951                 looking_at(buf, &i, "seconds were added to")) {
3952                 /* Update the clocks */
3953                 SendToICS(ics_prefix);
3954                 SendToICS("refresh\n");
3955                 continue;
3956             }
3957
3958             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3959                 ics_clock_paused = TRUE;
3960                 StopClocks();
3961                 continue;
3962             }
3963
3964             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3965                 ics_clock_paused = FALSE;
3966                 StartClocks();
3967                 continue;
3968             }
3969
3970             /* Grab player ratings from the Creating: message.
3971                Note we have to check for the special case when
3972                the ICS inserts things like [white] or [black]. */
3973             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3974                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3975                 /* star_matches:
3976                    0    player 1 name (not necessarily white)
3977                    1    player 1 rating
3978                    2    empty, white, or black (IGNORED)
3979                    3    player 2 name (not necessarily black)
3980                    4    player 2 rating
3981
3982                    The names/ratings are sorted out when the game
3983                    actually starts (below).
3984                 */
3985                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3986                 player1Rating = string_to_rating(star_match[1]);
3987                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3988                 player2Rating = string_to_rating(star_match[4]);
3989
3990                 if (appData.debugMode)
3991                   fprintf(debugFP,
3992                           "Ratings from 'Creating:' %s %d, %s %d\n",
3993                           player1Name, player1Rating,
3994                           player2Name, player2Rating);
3995
3996                 continue;
3997             }
3998
3999             /* Improved generic start/end-of-game messages */
4000             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4001                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4002                 /* If tkind == 0: */
4003                 /* star_match[0] is the game number */
4004                 /*           [1] is the white player's name */
4005                 /*           [2] is the black player's name */
4006                 /* For end-of-game: */
4007                 /*           [3] is the reason for the game end */
4008                 /*           [4] is a PGN end game-token, preceded by " " */
4009                 /* For start-of-game: */
4010                 /*           [3] begins with "Creating" or "Continuing" */
4011                 /*           [4] is " *" or empty (don't care). */
4012                 int gamenum = atoi(star_match[0]);
4013                 char *whitename, *blackname, *why, *endtoken;
4014                 ChessMove endtype = EndOfFile;
4015
4016                 if (tkind == 0) {
4017                   whitename = star_match[1];
4018                   blackname = star_match[2];
4019                   why = star_match[3];
4020                   endtoken = star_match[4];
4021                 } else {
4022                   whitename = star_match[1];
4023                   blackname = star_match[3];
4024                   why = star_match[5];
4025                   endtoken = star_match[6];
4026                 }
4027
4028                 /* Game start messages */
4029                 if (strncmp(why, "Creating ", 9) == 0 ||
4030                     strncmp(why, "Continuing ", 11) == 0) {
4031                     gs_gamenum = gamenum;
4032                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4033                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4034                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4035 #if ZIPPY
4036                     if (appData.zippyPlay) {
4037                         ZippyGameStart(whitename, blackname);
4038                     }
4039 #endif /*ZIPPY*/
4040                     partnerBoardValid = FALSE; // [HGM] bughouse
4041                     continue;
4042                 }
4043
4044                 /* Game end messages */
4045                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4046                     ics_gamenum != gamenum) {
4047                     continue;
4048                 }
4049                 while (endtoken[0] == ' ') endtoken++;
4050                 switch (endtoken[0]) {
4051                   case '*':
4052                   default:
4053                     endtype = GameUnfinished;
4054                     break;
4055                   case '0':
4056                     endtype = BlackWins;
4057                     break;
4058                   case '1':
4059                     if (endtoken[1] == '/')
4060                       endtype = GameIsDrawn;
4061                     else
4062                       endtype = WhiteWins;
4063                     break;
4064                 }
4065                 GameEnds(endtype, why, GE_ICS);
4066 #if ZIPPY
4067                 if (appData.zippyPlay && first.initDone) {
4068                     ZippyGameEnd(endtype, why);
4069                     if (first.pr == NoProc) {
4070                       /* Start the next process early so that we'll
4071                          be ready for the next challenge */
4072                       StartChessProgram(&first);
4073                     }
4074                     /* Send "new" early, in case this command takes
4075                        a long time to finish, so that we'll be ready
4076                        for the next challenge. */
4077                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4078                     Reset(TRUE, TRUE);
4079                 }
4080 #endif /*ZIPPY*/
4081                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4082                 continue;
4083             }
4084
4085             if (looking_at(buf, &i, "Removing game * from observation") ||
4086                 looking_at(buf, &i, "no longer observing game *") ||
4087                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4088                 if (gameMode == IcsObserving &&
4089                     atoi(star_match[0]) == ics_gamenum)
4090                   {
4091                       /* icsEngineAnalyze */
4092                       if (appData.icsEngineAnalyze) {
4093                             ExitAnalyzeMode();
4094                             ModeHighlight();
4095                       }
4096                       StopClocks();
4097                       gameMode = IcsIdle;
4098                       ics_gamenum = -1;
4099                       ics_user_moved = FALSE;
4100                   }
4101                 continue;
4102             }
4103
4104             if (looking_at(buf, &i, "no longer examining game *")) {
4105                 if (gameMode == IcsExamining &&
4106                     atoi(star_match[0]) == ics_gamenum)
4107                   {
4108                       gameMode = IcsIdle;
4109                       ics_gamenum = -1;
4110                       ics_user_moved = FALSE;
4111                   }
4112                 continue;
4113             }
4114
4115             /* Advance leftover_start past any newlines we find,
4116                so only partial lines can get reparsed */
4117             if (looking_at(buf, &i, "\n")) {
4118                 prevColor = curColor;
4119                 if (curColor != ColorNormal) {
4120                     if (oldi > next_out) {
4121                         SendToPlayer(&buf[next_out], oldi - next_out);
4122                         next_out = oldi;
4123                     }
4124                     Colorize(ColorNormal, FALSE);
4125                     curColor = ColorNormal;
4126                 }
4127                 if (started == STARTED_BOARD) {
4128                     started = STARTED_NONE;
4129                     parse[parse_pos] = NULLCHAR;
4130                     ParseBoard12(parse);
4131                     ics_user_moved = 0;
4132
4133                     /* Send premove here */
4134                     if (appData.premove) {
4135                       char str[MSG_SIZ];
4136                       if (currentMove == 0 &&
4137                           gameMode == IcsPlayingWhite &&
4138                           appData.premoveWhite) {
4139                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4140                         if (appData.debugMode)
4141                           fprintf(debugFP, "Sending premove:\n");
4142                         SendToICS(str);
4143                       } else if (currentMove == 1 &&
4144                                  gameMode == IcsPlayingBlack &&
4145                                  appData.premoveBlack) {
4146                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4147                         if (appData.debugMode)
4148                           fprintf(debugFP, "Sending premove:\n");
4149                         SendToICS(str);
4150                       } else if (gotPremove) {
4151                         gotPremove = 0;
4152                         ClearPremoveHighlights();
4153                         if (appData.debugMode)
4154                           fprintf(debugFP, "Sending premove:\n");
4155                           UserMoveEvent(premoveFromX, premoveFromY,
4156                                         premoveToX, premoveToY,
4157                                         premovePromoChar);
4158                       }
4159                     }
4160
4161                     /* Usually suppress following prompt */
4162                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4163                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4164                         if (looking_at(buf, &i, "*% ")) {
4165                             savingComment = FALSE;
4166                             suppressKibitz = 0;
4167                         }
4168                     }
4169                     next_out = i;
4170                 } else if (started == STARTED_HOLDINGS) {
4171                     int gamenum;
4172                     char new_piece[MSG_SIZ];
4173                     started = STARTED_NONE;
4174                     parse[parse_pos] = NULLCHAR;
4175                     if (appData.debugMode)
4176                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4177                                                         parse, currentMove);
4178                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4179                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4180                         if (gameInfo.variant == VariantNormal) {
4181                           /* [HGM] We seem to switch variant during a game!
4182                            * Presumably no holdings were displayed, so we have
4183                            * to move the position two files to the right to
4184                            * create room for them!
4185                            */
4186                           VariantClass newVariant;
4187                           switch(gameInfo.boardWidth) { // base guess on board width
4188                                 case 9:  newVariant = VariantShogi; break;
4189                                 case 10: newVariant = VariantGreat; break;
4190                                 default: newVariant = VariantCrazyhouse; break;
4191                           }
4192                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4193                           /* Get a move list just to see the header, which
4194                              will tell us whether this is really bug or zh */
4195                           if (ics_getting_history == H_FALSE) {
4196                             ics_getting_history = H_REQUESTED;
4197                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4198                             SendToICS(str);
4199                           }
4200                         }
4201                         new_piece[0] = NULLCHAR;
4202                         sscanf(parse, "game %d white [%s black [%s <- %s",
4203                                &gamenum, white_holding, black_holding,
4204                                new_piece);
4205                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4206                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4207                         /* [HGM] copy holdings to board holdings area */
4208                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4209                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4210                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4211 #if ZIPPY
4212                         if (appData.zippyPlay && first.initDone) {
4213                             ZippyHoldings(white_holding, black_holding,
4214                                           new_piece);
4215                         }
4216 #endif /*ZIPPY*/
4217                         if (tinyLayout || smallLayout) {
4218                             char wh[16], bh[16];
4219                             PackHolding(wh, white_holding);
4220                             PackHolding(bh, black_holding);
4221                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4222                                     gameInfo.white, gameInfo.black);
4223                         } else {
4224                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4225                                     gameInfo.white, white_holding, _("vs."),
4226                                     gameInfo.black, black_holding);
4227                         }
4228                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4229                         DrawPosition(FALSE, boards[currentMove]);
4230                         DisplayTitle(str);
4231                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4232                         sscanf(parse, "game %d white [%s black [%s <- %s",
4233                                &gamenum, white_holding, black_holding,
4234                                new_piece);
4235                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4236                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4237                         /* [HGM] copy holdings to partner-board holdings area */
4238                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4239                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4240                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4241                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4242                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4243                       }
4244                     }
4245                     /* Suppress following prompt */
4246                     if (looking_at(buf, &i, "*% ")) {
4247                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4248                         savingComment = FALSE;
4249                         suppressKibitz = 0;
4250                     }
4251                     next_out = i;
4252                 }
4253                 continue;
4254             }
4255
4256             i++;                /* skip unparsed character and loop back */
4257         }
4258
4259         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4260 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4261 //          SendToPlayer(&buf[next_out], i - next_out);
4262             started != STARTED_HOLDINGS && leftover_start > next_out) {
4263             SendToPlayer(&buf[next_out], leftover_start - next_out);
4264             next_out = i;
4265         }
4266
4267         leftover_len = buf_len - leftover_start;
4268         /* if buffer ends with something we couldn't parse,
4269            reparse it after appending the next read */
4270
4271     } else if (count == 0) {
4272         RemoveInputSource(isr);
4273         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4274     } else {
4275         DisplayFatalError(_("Error reading from ICS"), error, 1);
4276     }
4277 }
4278
4279
4280 /* Board style 12 looks like this:
4281
4282    <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
4283
4284  * The "<12> " is stripped before it gets to this routine.  The two
4285  * trailing 0's (flip state and clock ticking) are later addition, and
4286  * some chess servers may not have them, or may have only the first.
4287  * Additional trailing fields may be added in the future.
4288  */
4289
4290 #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"
4291
4292 #define RELATION_OBSERVING_PLAYED    0
4293 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4294 #define RELATION_PLAYING_MYMOVE      1
4295 #define RELATION_PLAYING_NOTMYMOVE  -1
4296 #define RELATION_EXAMINING           2
4297 #define RELATION_ISOLATED_BOARD     -3
4298 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4299
4300 void
4301 ParseBoard12 (char *string)
4302 {
4303 #if ZIPPY
4304     int i, takeback;
4305     char *bookHit = NULL; // [HGM] book
4306 #endif
4307     GameMode newGameMode;
4308     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4309     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4310     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4311     char to_play, board_chars[200];
4312     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4313     char black[32], white[32];
4314     Board board;
4315     int prevMove = currentMove;
4316     int ticking = 2;
4317     ChessMove moveType;
4318     int fromX, fromY, toX, toY;
4319     char promoChar;
4320     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4321     Boolean weird = FALSE, reqFlag = FALSE;
4322
4323     fromX = fromY = toX = toY = -1;
4324
4325     newGame = FALSE;
4326
4327     if (appData.debugMode)
4328       fprintf(debugFP, "Parsing board: %s\n", string);
4329
4330     move_str[0] = NULLCHAR;
4331     elapsed_time[0] = NULLCHAR;
4332     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4333         int  i = 0, j;
4334         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4335             if(string[i] == ' ') { ranks++; files = 0; }
4336             else files++;
4337             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4338             i++;
4339         }
4340         for(j = 0; j <i; j++) board_chars[j] = string[j];
4341         board_chars[i] = '\0';
4342         string += i + 1;
4343     }
4344     n = sscanf(string, PATTERN, &to_play, &double_push,
4345                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4346                &gamenum, white, black, &relation, &basetime, &increment,
4347                &white_stren, &black_stren, &white_time, &black_time,
4348                &moveNum, str, elapsed_time, move_str, &ics_flip,
4349                &ticking);
4350
4351     if (n < 21) {
4352         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4353         DisplayError(str, 0);
4354         return;
4355     }
4356
4357     /* Convert the move number to internal form */
4358     moveNum = (moveNum - 1) * 2;
4359     if (to_play == 'B') moveNum++;
4360     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4361       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4362                         0, 1);
4363       return;
4364     }
4365
4366     switch (relation) {
4367       case RELATION_OBSERVING_PLAYED:
4368       case RELATION_OBSERVING_STATIC:
4369         if (gamenum == -1) {
4370             /* Old ICC buglet */
4371             relation = RELATION_OBSERVING_STATIC;
4372         }
4373         newGameMode = IcsObserving;
4374         break;
4375       case RELATION_PLAYING_MYMOVE:
4376       case RELATION_PLAYING_NOTMYMOVE:
4377         newGameMode =
4378           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4379             IcsPlayingWhite : IcsPlayingBlack;
4380         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4381         break;
4382       case RELATION_EXAMINING:
4383         newGameMode = IcsExamining;
4384         break;
4385       case RELATION_ISOLATED_BOARD:
4386       default:
4387         /* Just display this board.  If user was doing something else,
4388            we will forget about it until the next board comes. */
4389         newGameMode = IcsIdle;
4390         break;
4391       case RELATION_STARTING_POSITION:
4392         newGameMode = gameMode;
4393         break;
4394     }
4395
4396     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4397         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4398          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4399       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4400       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4401       static int lastBgGame = -1;
4402       char *toSqr;
4403       for (k = 0; k < ranks; k++) {
4404         for (j = 0; j < files; j++)
4405           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4406         if(gameInfo.holdingsWidth > 1) {
4407              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4408              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4409         }
4410       }
4411       CopyBoard(partnerBoard, board);
4412       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4413         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4414         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4415       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4416       if(toSqr = strchr(str, '-')) {
4417         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4418         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4419       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4420       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4421       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4422       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4423       if(twoBoards) {
4424           DisplayWhiteClock(white_time*fac, to_play == 'W');
4425           DisplayBlackClock(black_time*fac, to_play != 'W');
4426           activePartner = to_play;
4427           if(gamenum != lastBgGame) {
4428               char buf[MSG_SIZ];
4429               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4430               DisplayTitle(buf);
4431           }
4432           lastBgGame = gamenum;
4433           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4434                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4435       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4436                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4437       if(!twoBoards) DisplayMessage(partnerStatus, "");
4438         partnerBoardValid = TRUE;
4439       return;
4440     }
4441
4442     if(appData.dualBoard && appData.bgObserve) {
4443         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4444             SendToICS(ics_prefix), SendToICS("pobserve\n");
4445         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4446             char buf[MSG_SIZ];
4447             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4448             SendToICS(buf);
4449         }
4450     }
4451
4452     /* Modify behavior for initial board display on move listing
4453        of wild games.
4454        */
4455     switch (ics_getting_history) {
4456       case H_FALSE:
4457       case H_REQUESTED:
4458         break;
4459       case H_GOT_REQ_HEADER:
4460       case H_GOT_UNREQ_HEADER:
4461         /* This is the initial position of the current game */
4462         gamenum = ics_gamenum;
4463         moveNum = 0;            /* old ICS bug workaround */
4464         if (to_play == 'B') {
4465           startedFromSetupPosition = TRUE;
4466           blackPlaysFirst = TRUE;
4467           moveNum = 1;
4468           if (forwardMostMove == 0) forwardMostMove = 1;
4469           if (backwardMostMove == 0) backwardMostMove = 1;
4470           if (currentMove == 0) currentMove = 1;
4471         }
4472         newGameMode = gameMode;
4473         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4474         break;
4475       case H_GOT_UNWANTED_HEADER:
4476         /* This is an initial board that we don't want */
4477         return;
4478       case H_GETTING_MOVES:
4479         /* Should not happen */
4480         DisplayError(_("Error gathering move list: extra board"), 0);
4481         ics_getting_history = H_FALSE;
4482         return;
4483     }
4484
4485    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4486                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4487                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4488      /* [HGM] We seem to have switched variant unexpectedly
4489       * Try to guess new variant from board size
4490       */
4491           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4492           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4493           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4494           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4495           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4496           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4497           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4498           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4499           /* Get a move list just to see the header, which
4500              will tell us whether this is really bug or zh */
4501           if (ics_getting_history == H_FALSE) {
4502             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4503             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4504             SendToICS(str);
4505           }
4506     }
4507
4508     /* Take action if this is the first board of a new game, or of a
4509        different game than is currently being displayed.  */
4510     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4511         relation == RELATION_ISOLATED_BOARD) {
4512
4513         /* Forget the old game and get the history (if any) of the new one */
4514         if (gameMode != BeginningOfGame) {
4515           Reset(TRUE, TRUE);
4516         }
4517         newGame = TRUE;
4518         if (appData.autoRaiseBoard) BoardToTop();
4519         prevMove = -3;
4520         if (gamenum == -1) {
4521             newGameMode = IcsIdle;
4522         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4523                    appData.getMoveList && !reqFlag) {
4524             /* Need to get game history */
4525             ics_getting_history = H_REQUESTED;
4526             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4527             SendToICS(str);
4528         }
4529
4530         /* Initially flip the board to have black on the bottom if playing
4531            black or if the ICS flip flag is set, but let the user change
4532            it with the Flip View button. */
4533         flipView = appData.autoFlipView ?
4534           (newGameMode == IcsPlayingBlack) || ics_flip :
4535           appData.flipView;
4536
4537         /* Done with values from previous mode; copy in new ones */
4538         gameMode = newGameMode;
4539         ModeHighlight();
4540         ics_gamenum = gamenum;
4541         if (gamenum == gs_gamenum) {
4542             int klen = strlen(gs_kind);
4543             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4544             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4545             gameInfo.event = StrSave(str);
4546         } else {
4547             gameInfo.event = StrSave("ICS game");
4548         }
4549         gameInfo.site = StrSave(appData.icsHost);
4550         gameInfo.date = PGNDate();
4551         gameInfo.round = StrSave("-");
4552         gameInfo.white = StrSave(white);
4553         gameInfo.black = StrSave(black);
4554         timeControl = basetime * 60 * 1000;
4555         timeControl_2 = 0;
4556         timeIncrement = increment * 1000;
4557         movesPerSession = 0;
4558         gameInfo.timeControl = TimeControlTagValue();
4559         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4560   if (appData.debugMode) {
4561     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4562     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4563     setbuf(debugFP, NULL);
4564   }
4565
4566         gameInfo.outOfBook = NULL;
4567
4568         /* Do we have the ratings? */
4569         if (strcmp(player1Name, white) == 0 &&
4570             strcmp(player2Name, black) == 0) {
4571             if (appData.debugMode)
4572               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4573                       player1Rating, player2Rating);
4574             gameInfo.whiteRating = player1Rating;
4575             gameInfo.blackRating = player2Rating;
4576         } else if (strcmp(player2Name, white) == 0 &&
4577                    strcmp(player1Name, black) == 0) {
4578             if (appData.debugMode)
4579               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4580                       player2Rating, player1Rating);
4581             gameInfo.whiteRating = player2Rating;
4582             gameInfo.blackRating = player1Rating;
4583         }
4584         player1Name[0] = player2Name[0] = NULLCHAR;
4585
4586         /* Silence shouts if requested */
4587         if (appData.quietPlay &&
4588             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4589             SendToICS(ics_prefix);
4590             SendToICS("set shout 0\n");
4591         }
4592     }
4593
4594     /* Deal with midgame name changes */
4595     if (!newGame) {
4596         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4597             if (gameInfo.white) free(gameInfo.white);
4598             gameInfo.white = StrSave(white);
4599         }
4600         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4601             if (gameInfo.black) free(gameInfo.black);
4602             gameInfo.black = StrSave(black);
4603         }
4604     }
4605
4606     /* Throw away game result if anything actually changes in examine mode */
4607     if (gameMode == IcsExamining && !newGame) {
4608         gameInfo.result = GameUnfinished;
4609         if (gameInfo.resultDetails != NULL) {
4610             free(gameInfo.resultDetails);
4611             gameInfo.resultDetails = NULL;
4612         }
4613     }
4614
4615     /* In pausing && IcsExamining mode, we ignore boards coming
4616        in if they are in a different variation than we are. */
4617     if (pauseExamInvalid) return;
4618     if (pausing && gameMode == IcsExamining) {
4619         if (moveNum <= pauseExamForwardMostMove) {
4620             pauseExamInvalid = TRUE;
4621             forwardMostMove = pauseExamForwardMostMove;
4622             return;
4623         }
4624     }
4625
4626   if (appData.debugMode) {
4627     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4628   }
4629     /* Parse the board */
4630     for (k = 0; k < ranks; k++) {
4631       for (j = 0; j < files; j++)
4632         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4633       if(gameInfo.holdingsWidth > 1) {
4634            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4635            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4636       }
4637     }
4638     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4639       board[5][BOARD_RGHT+1] = WhiteAngel;
4640       board[6][BOARD_RGHT+1] = WhiteMarshall;
4641       board[1][0] = BlackMarshall;
4642       board[2][0] = BlackAngel;
4643       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4644     }
4645     CopyBoard(boards[moveNum], board);
4646     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4647     if (moveNum == 0) {
4648         startedFromSetupPosition =
4649           !CompareBoards(board, initialPosition);
4650         if(startedFromSetupPosition)
4651             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4652     }
4653
4654     /* [HGM] Set castling rights. Take the outermost Rooks,
4655        to make it also work for FRC opening positions. Note that board12
4656        is really defective for later FRC positions, as it has no way to
4657        indicate which Rook can castle if they are on the same side of King.
4658        For the initial position we grant rights to the outermost Rooks,
4659        and remember thos rights, and we then copy them on positions
4660        later in an FRC game. This means WB might not recognize castlings with
4661        Rooks that have moved back to their original position as illegal,
4662        but in ICS mode that is not its job anyway.
4663     */
4664     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4665     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4666
4667         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4668             if(board[0][i] == WhiteRook) j = i;
4669         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4670         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4671             if(board[0][i] == WhiteRook) j = i;
4672         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4673         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4674             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4675         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4676         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4677             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4678         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4679
4680         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4681         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4682         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4683             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4684         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4685             if(board[BOARD_HEIGHT-1][k] == bKing)
4686                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4687         if(gameInfo.variant == VariantTwoKings) {
4688             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4689             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4690             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4691         }
4692     } else { int r;
4693         r = boards[moveNum][CASTLING][0] = initialRights[0];
4694         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4695         r = boards[moveNum][CASTLING][1] = initialRights[1];
4696         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4697         r = boards[moveNum][CASTLING][3] = initialRights[3];
4698         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4699         r = boards[moveNum][CASTLING][4] = initialRights[4];
4700         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4701         /* wildcastle kludge: always assume King has rights */
4702         r = boards[moveNum][CASTLING][2] = initialRights[2];
4703         r = boards[moveNum][CASTLING][5] = initialRights[5];
4704     }
4705     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4706     boards[moveNum][EP_STATUS] = EP_NONE;
4707     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4708     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4709     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4710
4711
4712     if (ics_getting_history == H_GOT_REQ_HEADER ||
4713         ics_getting_history == H_GOT_UNREQ_HEADER) {
4714         /* This was an initial position from a move list, not
4715            the current position */
4716         return;
4717     }
4718
4719     /* Update currentMove and known move number limits */
4720     newMove = newGame || moveNum > forwardMostMove;
4721
4722     if (newGame) {
4723         forwardMostMove = backwardMostMove = currentMove = moveNum;
4724         if (gameMode == IcsExamining && moveNum == 0) {
4725           /* Workaround for ICS limitation: we are not told the wild
4726              type when starting to examine a game.  But if we ask for
4727              the move list, the move list header will tell us */
4728             ics_getting_history = H_REQUESTED;
4729             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4730             SendToICS(str);
4731         }
4732     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4733                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4734 #if ZIPPY
4735         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4736         /* [HGM] applied this also to an engine that is silently watching        */
4737         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4738             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4739             gameInfo.variant == currentlyInitializedVariant) {
4740           takeback = forwardMostMove - moveNum;
4741           for (i = 0; i < takeback; i++) {
4742             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4743             SendToProgram("undo\n", &first);
4744           }
4745         }
4746 #endif
4747
4748         forwardMostMove = moveNum;
4749         if (!pausing || currentMove > forwardMostMove)
4750           currentMove = forwardMostMove;
4751     } else {
4752         /* New part of history that is not contiguous with old part */
4753         if (pausing && gameMode == IcsExamining) {
4754             pauseExamInvalid = TRUE;
4755             forwardMostMove = pauseExamForwardMostMove;
4756             return;
4757         }
4758         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4759 #if ZIPPY
4760             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4761                 // [HGM] when we will receive the move list we now request, it will be
4762                 // fed to the engine from the first move on. So if the engine is not
4763                 // in the initial position now, bring it there.
4764                 InitChessProgram(&first, 0);
4765             }
4766 #endif
4767             ics_getting_history = H_REQUESTED;
4768             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4769             SendToICS(str);
4770         }
4771         forwardMostMove = backwardMostMove = currentMove = moveNum;
4772     }
4773
4774     /* Update the clocks */
4775     if (strchr(elapsed_time, '.')) {
4776       /* Time is in ms */
4777       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4778       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4779     } else {
4780       /* Time is in seconds */
4781       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4782       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4783     }
4784
4785
4786 #if ZIPPY
4787     if (appData.zippyPlay && newGame &&
4788         gameMode != IcsObserving && gameMode != IcsIdle &&
4789         gameMode != IcsExamining)
4790       ZippyFirstBoard(moveNum, basetime, increment);
4791 #endif
4792
4793     /* Put the move on the move list, first converting
4794        to canonical algebraic form. */
4795     if (moveNum > 0) {
4796   if (appData.debugMode) {
4797     int f = forwardMostMove;
4798     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4799             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4800             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4801     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4802     fprintf(debugFP, "moveNum = %d\n", moveNum);
4803     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4804     setbuf(debugFP, NULL);
4805   }
4806         if (moveNum <= backwardMostMove) {
4807             /* We don't know what the board looked like before
4808                this move.  Punt. */
4809           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4810             strcat(parseList[moveNum - 1], " ");
4811             strcat(parseList[moveNum - 1], elapsed_time);
4812             moveList[moveNum - 1][0] = NULLCHAR;
4813         } else if (strcmp(move_str, "none") == 0) {
4814             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4815             /* Again, we don't know what the board looked like;
4816                this is really the start of the game. */
4817             parseList[moveNum - 1][0] = NULLCHAR;
4818             moveList[moveNum - 1][0] = NULLCHAR;
4819             backwardMostMove = moveNum;
4820             startedFromSetupPosition = TRUE;
4821             fromX = fromY = toX = toY = -1;
4822         } else {
4823           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4824           //                 So we parse the long-algebraic move string in stead of the SAN move
4825           int valid; char buf[MSG_SIZ], *prom;
4826
4827           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4828                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4829           // str looks something like "Q/a1-a2"; kill the slash
4830           if(str[1] == '/')
4831             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4832           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4833           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4834                 strcat(buf, prom); // long move lacks promo specification!
4835           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4836                 if(appData.debugMode)
4837                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4838                 safeStrCpy(move_str, buf, MSG_SIZ);
4839           }
4840           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4841                                 &fromX, &fromY, &toX, &toY, &promoChar)
4842                || ParseOneMove(buf, moveNum - 1, &moveType,
4843                                 &fromX, &fromY, &toX, &toY, &promoChar);
4844           // end of long SAN patch
4845           if (valid) {
4846             (void) CoordsToAlgebraic(boards[moveNum - 1],
4847                                      PosFlags(moveNum - 1),
4848                                      fromY, fromX, toY, toX, promoChar,
4849                                      parseList[moveNum-1]);
4850             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4851               case MT_NONE:
4852               case MT_STALEMATE:
4853               default:
4854                 break;
4855               case MT_CHECK:
4856                 if(!IS_SHOGI(gameInfo.variant))
4857                     strcat(parseList[moveNum - 1], "+");
4858                 break;
4859               case MT_CHECKMATE:
4860               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4861                 strcat(parseList[moveNum - 1], "#");
4862                 break;
4863             }
4864             strcat(parseList[moveNum - 1], " ");
4865             strcat(parseList[moveNum - 1], elapsed_time);
4866             /* currentMoveString is set as a side-effect of ParseOneMove */
4867             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4868             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4869             strcat(moveList[moveNum - 1], "\n");
4870
4871             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4872                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4873               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4874                 ChessSquare old, new = boards[moveNum][k][j];
4875                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4876                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4877                   if(old == new) continue;
4878                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4879                   else if(new == WhiteWazir || new == BlackWazir) {
4880                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4881                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4882                       else boards[moveNum][k][j] = old; // preserve type of Gold
4883                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4884                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4885               }
4886           } else {
4887             /* Move from ICS was illegal!?  Punt. */
4888             if (appData.debugMode) {
4889               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4890               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4891             }
4892             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4893             strcat(parseList[moveNum - 1], " ");
4894             strcat(parseList[moveNum - 1], elapsed_time);
4895             moveList[moveNum - 1][0] = NULLCHAR;
4896             fromX = fromY = toX = toY = -1;
4897           }
4898         }
4899   if (appData.debugMode) {
4900     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4901     setbuf(debugFP, NULL);
4902   }
4903
4904 #if ZIPPY
4905         /* Send move to chess program (BEFORE animating it). */
4906         if (appData.zippyPlay && !newGame && newMove &&
4907            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4908
4909             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4910                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4911                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4912                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4913                             move_str);
4914                     DisplayError(str, 0);
4915                 } else {
4916                     if (first.sendTime) {
4917                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4918                     }
4919                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4920                     if (firstMove && !bookHit) {
4921                         firstMove = FALSE;
4922                         if (first.useColors) {
4923                           SendToProgram(gameMode == IcsPlayingWhite ?
4924                                         "white\ngo\n" :
4925                                         "black\ngo\n", &first);
4926                         } else {
4927                           SendToProgram("go\n", &first);
4928                         }
4929                         first.maybeThinking = TRUE;
4930                     }
4931                 }
4932             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4933               if (moveList[moveNum - 1][0] == NULLCHAR) {
4934                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4935                 DisplayError(str, 0);
4936               } else {
4937                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4938                 SendMoveToProgram(moveNum - 1, &first);
4939               }
4940             }
4941         }
4942 #endif
4943     }
4944
4945     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4946         /* If move comes from a remote source, animate it.  If it
4947            isn't remote, it will have already been animated. */
4948         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4949             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4950         }
4951         if (!pausing && appData.highlightLastMove) {
4952             SetHighlights(fromX, fromY, toX, toY);
4953         }
4954     }
4955
4956     /* Start the clocks */
4957     whiteFlag = blackFlag = FALSE;
4958     appData.clockMode = !(basetime == 0 && increment == 0);
4959     if (ticking == 0) {
4960       ics_clock_paused = TRUE;
4961       StopClocks();
4962     } else if (ticking == 1) {
4963       ics_clock_paused = FALSE;
4964     }
4965     if (gameMode == IcsIdle ||
4966         relation == RELATION_OBSERVING_STATIC ||
4967         relation == RELATION_EXAMINING ||
4968         ics_clock_paused)
4969       DisplayBothClocks();
4970     else
4971       StartClocks();
4972
4973     /* Display opponents and material strengths */
4974     if (gameInfo.variant != VariantBughouse &&
4975         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4976         if (tinyLayout || smallLayout) {
4977             if(gameInfo.variant == VariantNormal)
4978               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4979                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4980                     basetime, increment);
4981             else
4982               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4983                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4984                     basetime, increment, (int) gameInfo.variant);
4985         } else {
4986             if(gameInfo.variant == VariantNormal)
4987               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4988                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4989                     basetime, increment);
4990             else
4991               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4992                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4993                     basetime, increment, VariantName(gameInfo.variant));
4994         }
4995         DisplayTitle(str);
4996   if (appData.debugMode) {
4997     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4998   }
4999     }
5000
5001
5002     /* Display the board */
5003     if (!pausing && !appData.noGUI) {
5004
5005       if (appData.premove)
5006           if (!gotPremove ||
5007              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5008              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5009               ClearPremoveHighlights();
5010
5011       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5012         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5013       DrawPosition(j, boards[currentMove]);
5014
5015       DisplayMove(moveNum - 1);
5016       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5017             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5018               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5019         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5020       }
5021     }
5022
5023     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5024 #if ZIPPY
5025     if(bookHit) { // [HGM] book: simulate book reply
5026         static char bookMove[MSG_SIZ]; // a bit generous?
5027
5028         programStats.nodes = programStats.depth = programStats.time =
5029         programStats.score = programStats.got_only_move = 0;
5030         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5031
5032         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5033         strcat(bookMove, bookHit);
5034         HandleMachineMove(bookMove, &first);
5035     }
5036 #endif
5037 }
5038
5039 void
5040 GetMoveListEvent ()
5041 {
5042     char buf[MSG_SIZ];
5043     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5044         ics_getting_history = H_REQUESTED;
5045         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5046         SendToICS(buf);
5047     }
5048 }
5049
5050 void
5051 SendToBoth (char *msg)
5052 {   // to make it easy to keep two engines in step in dual analysis
5053     SendToProgram(msg, &first);
5054     if(second.analyzing) SendToProgram(msg, &second);
5055 }
5056
5057 void
5058 AnalysisPeriodicEvent (int force)
5059 {
5060     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5061          && !force) || !appData.periodicUpdates)
5062       return;
5063
5064     /* Send . command to Crafty to collect stats */
5065     SendToBoth(".\n");
5066
5067     /* Don't send another until we get a response (this makes
5068        us stop sending to old Crafty's which don't understand
5069        the "." command (sending illegal cmds resets node count & time,
5070        which looks bad)) */
5071     programStats.ok_to_send = 0;
5072 }
5073
5074 void
5075 ics_update_width (int new_width)
5076 {
5077         ics_printf("set width %d\n", new_width);
5078 }
5079
5080 void
5081 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5082 {
5083     char buf[MSG_SIZ];
5084
5085     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5086         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5087             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5088             SendToProgram(buf, cps);
5089             return;
5090         }
5091         // null move in variant where engine does not understand it (for analysis purposes)
5092         SendBoard(cps, moveNum + 1); // send position after move in stead.
5093         return;
5094     }
5095     if (cps->useUsermove) {
5096       SendToProgram("usermove ", cps);
5097     }
5098     if (cps->useSAN) {
5099       char *space;
5100       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5101         int len = space - parseList[moveNum];
5102         memcpy(buf, parseList[moveNum], len);
5103         buf[len++] = '\n';
5104         buf[len] = NULLCHAR;
5105       } else {
5106         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5107       }
5108       SendToProgram(buf, cps);
5109     } else {
5110       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5111         AlphaRank(moveList[moveNum], 4);
5112         SendToProgram(moveList[moveNum], cps);
5113         AlphaRank(moveList[moveNum], 4); // and back
5114       } else
5115       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5116        * the engine. It would be nice to have a better way to identify castle
5117        * moves here. */
5118       if(appData.fischerCastling && cps->useOOCastle) {
5119         int fromX = moveList[moveNum][0] - AAA;
5120         int fromY = moveList[moveNum][1] - ONE;
5121         int toX = moveList[moveNum][2] - AAA;
5122         int toY = moveList[moveNum][3] - ONE;
5123         if((boards[moveNum][fromY][fromX] == WhiteKing
5124             && boards[moveNum][toY][toX] == WhiteRook)
5125            || (boards[moveNum][fromY][fromX] == BlackKing
5126                && boards[moveNum][toY][toX] == BlackRook)) {
5127           if(toX > fromX) SendToProgram("O-O\n", cps);
5128           else SendToProgram("O-O-O\n", cps);
5129         }
5130         else SendToProgram(moveList[moveNum], cps);
5131       } else
5132       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5133           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5134                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5135                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5136                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5137           SendToProgram(buf, cps);
5138       } else
5139       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5140         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5141           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5142           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5143                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5144         } else
5145           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5146                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5147         SendToProgram(buf, cps);
5148       }
5149       else SendToProgram(moveList[moveNum], cps);
5150       /* End of additions by Tord */
5151     }
5152
5153     /* [HGM] setting up the opening has brought engine in force mode! */
5154     /*       Send 'go' if we are in a mode where machine should play. */
5155     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5156         (gameMode == TwoMachinesPlay   ||
5157 #if ZIPPY
5158          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5159 #endif
5160          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5161         SendToProgram("go\n", cps);
5162   if (appData.debugMode) {
5163     fprintf(debugFP, "(extra)\n");
5164   }
5165     }
5166     setboardSpoiledMachineBlack = 0;
5167 }
5168
5169 void
5170 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5171 {
5172     char user_move[MSG_SIZ];
5173     char suffix[4];
5174
5175     if(gameInfo.variant == VariantSChess && promoChar) {
5176         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5177         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5178     } else suffix[0] = NULLCHAR;
5179
5180     switch (moveType) {
5181       default:
5182         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5183                 (int)moveType, fromX, fromY, toX, toY);
5184         DisplayError(user_move + strlen("say "), 0);
5185         break;
5186       case WhiteKingSideCastle:
5187       case BlackKingSideCastle:
5188       case WhiteQueenSideCastleWild:
5189       case BlackQueenSideCastleWild:
5190       /* PUSH Fabien */
5191       case WhiteHSideCastleFR:
5192       case BlackHSideCastleFR:
5193       /* POP Fabien */
5194         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5195         break;
5196       case WhiteQueenSideCastle:
5197       case BlackQueenSideCastle:
5198       case WhiteKingSideCastleWild:
5199       case BlackKingSideCastleWild:
5200       /* PUSH Fabien */
5201       case WhiteASideCastleFR:
5202       case BlackASideCastleFR:
5203       /* POP Fabien */
5204         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5205         break;
5206       case WhiteNonPromotion:
5207       case BlackNonPromotion:
5208         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5209         break;
5210       case WhitePromotion:
5211       case BlackPromotion:
5212         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5213            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5214           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5215                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5216                 PieceToChar(WhiteFerz));
5217         else if(gameInfo.variant == VariantGreat)
5218           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5219                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5220                 PieceToChar(WhiteMan));
5221         else
5222           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5223                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5224                 promoChar);
5225         break;
5226       case WhiteDrop:
5227       case BlackDrop:
5228       drop:
5229         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5230                  ToUpper(PieceToChar((ChessSquare) fromX)),
5231                  AAA + toX, ONE + toY);
5232         break;
5233       case IllegalMove:  /* could be a variant we don't quite understand */
5234         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5235       case NormalMove:
5236       case WhiteCapturesEnPassant:
5237       case BlackCapturesEnPassant:
5238         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5239                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5240         break;
5241     }
5242     SendToICS(user_move);
5243     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5244         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5245 }
5246
5247 void
5248 UploadGameEvent ()
5249 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5250     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5251     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5252     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5253       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5254       return;
5255     }
5256     if(gameMode != IcsExamining) { // is this ever not the case?
5257         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5258
5259         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5260           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5261         } else { // on FICS we must first go to general examine mode
5262           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5263         }
5264         if(gameInfo.variant != VariantNormal) {
5265             // try figure out wild number, as xboard names are not always valid on ICS
5266             for(i=1; i<=36; i++) {
5267               snprintf(buf, MSG_SIZ, "wild/%d", i);
5268                 if(StringToVariant(buf) == gameInfo.variant) break;
5269             }
5270             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5271             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5272             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5273         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5274         SendToICS(ics_prefix);
5275         SendToICS(buf);
5276         if(startedFromSetupPosition || backwardMostMove != 0) {
5277           fen = PositionToFEN(backwardMostMove, NULL, 1);
5278           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5279             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5280             SendToICS(buf);
5281           } else { // FICS: everything has to set by separate bsetup commands
5282             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5283             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5284             SendToICS(buf);
5285             if(!WhiteOnMove(backwardMostMove)) {
5286                 SendToICS("bsetup tomove black\n");
5287             }
5288             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5289             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5290             SendToICS(buf);
5291             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5292             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5293             SendToICS(buf);
5294             i = boards[backwardMostMove][EP_STATUS];
5295             if(i >= 0) { // set e.p.
5296               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5297                 SendToICS(buf);
5298             }
5299             bsetup++;
5300           }
5301         }
5302       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5303             SendToICS("bsetup done\n"); // switch to normal examining.
5304     }
5305     for(i = backwardMostMove; i<last; i++) {
5306         char buf[20];
5307         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5308         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5309             int len = strlen(moveList[i]);
5310             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5311             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5312         }
5313         SendToICS(buf);
5314     }
5315     SendToICS(ics_prefix);
5316     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5317 }
5318
5319 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5320
5321 void
5322 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5323 {
5324     if (rf == DROP_RANK) {
5325       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5326       sprintf(move, "%c@%c%c\n",
5327                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5328     } else {
5329         if (promoChar == 'x' || promoChar == NULLCHAR) {
5330           sprintf(move, "%c%c%c%c\n",
5331                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5332           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5333         } else {
5334             sprintf(move, "%c%c%c%c%c\n",
5335                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5336         }
5337     }
5338 }
5339
5340 void
5341 ProcessICSInitScript (FILE *f)
5342 {
5343     char buf[MSG_SIZ];
5344
5345     while (fgets(buf, MSG_SIZ, f)) {
5346         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5347     }
5348
5349     fclose(f);
5350 }
5351
5352
5353 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5354 int dragging;
5355 static ClickType lastClickType;
5356
5357 int
5358 Partner (ChessSquare *p)
5359 { // change piece into promotion partner if one shogi-promotes to the other
5360   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5361   ChessSquare partner;
5362   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5363   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5364   *p = partner;
5365   return 1;
5366 }
5367
5368 void
5369 Sweep (int step)
5370 {
5371     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5372     static int toggleFlag;
5373     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5374     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5375     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5376     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5377     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5378     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5379     do {
5380         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5381         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5382         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5383         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5384         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5385         if(!step) step = -1;
5386     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5387             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5388             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5389             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5390     if(toX >= 0) {
5391         int victim = boards[currentMove][toY][toX];
5392         boards[currentMove][toY][toX] = promoSweep;
5393         DrawPosition(FALSE, boards[currentMove]);
5394         boards[currentMove][toY][toX] = victim;
5395     } else
5396     ChangeDragPiece(promoSweep);
5397 }
5398
5399 int
5400 PromoScroll (int x, int y)
5401 {
5402   int step = 0;
5403
5404   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5405   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5406   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5407   if(!step) return FALSE;
5408   lastX = x; lastY = y;
5409   if((promoSweep < BlackPawn) == flipView) step = -step;
5410   if(step > 0) selectFlag = 1;
5411   if(!selectFlag) Sweep(step);
5412   return FALSE;
5413 }
5414
5415 void
5416 NextPiece (int step)
5417 {
5418     ChessSquare piece = boards[currentMove][toY][toX];
5419     do {
5420         pieceSweep -= step;
5421         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5422         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5423         if(!step) step = -1;
5424     } while(PieceToChar(pieceSweep) == '.');
5425     boards[currentMove][toY][toX] = pieceSweep;
5426     DrawPosition(FALSE, boards[currentMove]);
5427     boards[currentMove][toY][toX] = piece;
5428 }
5429 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5430 void
5431 AlphaRank (char *move, int n)
5432 {
5433 //    char *p = move, c; int x, y;
5434
5435     if (appData.debugMode) {
5436         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5437     }
5438
5439     if(move[1]=='*' &&
5440        move[2]>='0' && move[2]<='9' &&
5441        move[3]>='a' && move[3]<='x'    ) {
5442         move[1] = '@';
5443         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5444         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5445     } else
5446     if(move[0]>='0' && move[0]<='9' &&
5447        move[1]>='a' && move[1]<='x' &&
5448        move[2]>='0' && move[2]<='9' &&
5449        move[3]>='a' && move[3]<='x'    ) {
5450         /* input move, Shogi -> normal */
5451         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5452         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5453         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5454         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5455     } else
5456     if(move[1]=='@' &&
5457        move[3]>='0' && move[3]<='9' &&
5458        move[2]>='a' && move[2]<='x'    ) {
5459         move[1] = '*';
5460         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5461         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5462     } else
5463     if(
5464        move[0]>='a' && move[0]<='x' &&
5465        move[3]>='0' && move[3]<='9' &&
5466        move[2]>='a' && move[2]<='x'    ) {
5467          /* output move, normal -> Shogi */
5468         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5469         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5470         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5471         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5472         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5473     }
5474     if (appData.debugMode) {
5475         fprintf(debugFP, "   out = '%s'\n", move);
5476     }
5477 }
5478
5479 char yy_textstr[8000];
5480
5481 /* Parser for moves from gnuchess, ICS, or user typein box */
5482 Boolean
5483 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5484 {
5485     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5486
5487     switch (*moveType) {
5488       case WhitePromotion:
5489       case BlackPromotion:
5490       case WhiteNonPromotion:
5491       case BlackNonPromotion:
5492       case NormalMove:
5493       case FirstLeg:
5494       case WhiteCapturesEnPassant:
5495       case BlackCapturesEnPassant:
5496       case WhiteKingSideCastle:
5497       case WhiteQueenSideCastle:
5498       case BlackKingSideCastle:
5499       case BlackQueenSideCastle:
5500       case WhiteKingSideCastleWild:
5501       case WhiteQueenSideCastleWild:
5502       case BlackKingSideCastleWild:
5503       case BlackQueenSideCastleWild:
5504       /* Code added by Tord: */
5505       case WhiteHSideCastleFR:
5506       case WhiteASideCastleFR:
5507       case BlackHSideCastleFR:
5508       case BlackASideCastleFR:
5509       /* End of code added by Tord */
5510       case IllegalMove:         /* bug or odd chess variant */
5511         *fromX = currentMoveString[0] - AAA;
5512         *fromY = currentMoveString[1] - ONE;
5513         *toX = currentMoveString[2] - AAA;
5514         *toY = currentMoveString[3] - ONE;
5515         *promoChar = currentMoveString[4];
5516         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5517             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5518     if (appData.debugMode) {
5519         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5520     }
5521             *fromX = *fromY = *toX = *toY = 0;
5522             return FALSE;
5523         }
5524         if (appData.testLegality) {
5525           return (*moveType != IllegalMove);
5526         } else {
5527           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5528                          // [HGM] lion: if this is a double move we are less critical
5529                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5530         }
5531
5532       case WhiteDrop:
5533       case BlackDrop:
5534         *fromX = *moveType == WhiteDrop ?
5535           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5536           (int) CharToPiece(ToLower(currentMoveString[0]));
5537         *fromY = DROP_RANK;
5538         *toX = currentMoveString[2] - AAA;
5539         *toY = currentMoveString[3] - ONE;
5540         *promoChar = NULLCHAR;
5541         return TRUE;
5542
5543       case AmbiguousMove:
5544       case ImpossibleMove:
5545       case EndOfFile:
5546       case ElapsedTime:
5547       case Comment:
5548       case PGNTag:
5549       case NAG:
5550       case WhiteWins:
5551       case BlackWins:
5552       case GameIsDrawn:
5553       default:
5554     if (appData.debugMode) {
5555         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5556     }
5557         /* bug? */
5558         *fromX = *fromY = *toX = *toY = 0;
5559         *promoChar = NULLCHAR;
5560         return FALSE;
5561     }
5562 }
5563
5564 Boolean pushed = FALSE;
5565 char *lastParseAttempt;
5566
5567 void
5568 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5569 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5570   int fromX, fromY, toX, toY; char promoChar;
5571   ChessMove moveType;
5572   Boolean valid;
5573   int nr = 0;
5574
5575   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5576   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5577     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5578     pushed = TRUE;
5579   }
5580   endPV = forwardMostMove;
5581   do {
5582     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5583     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5584     lastParseAttempt = pv;
5585     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5586     if(!valid && nr == 0 &&
5587        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5588         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5589         // Hande case where played move is different from leading PV move
5590         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5591         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5592         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5593         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5594           endPV += 2; // if position different, keep this
5595           moveList[endPV-1][0] = fromX + AAA;
5596           moveList[endPV-1][1] = fromY + ONE;
5597           moveList[endPV-1][2] = toX + AAA;
5598           moveList[endPV-1][3] = toY + ONE;
5599           parseList[endPV-1][0] = NULLCHAR;
5600           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5601         }
5602       }
5603     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5604     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5605     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5606     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5607         valid++; // allow comments in PV
5608         continue;
5609     }
5610     nr++;
5611     if(endPV+1 > framePtr) break; // no space, truncate
5612     if(!valid) break;
5613     endPV++;
5614     CopyBoard(boards[endPV], boards[endPV-1]);
5615     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5616     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5617     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5618     CoordsToAlgebraic(boards[endPV - 1],
5619                              PosFlags(endPV - 1),
5620                              fromY, fromX, toY, toX, promoChar,
5621                              parseList[endPV - 1]);
5622   } while(valid);
5623   if(atEnd == 2) return; // used hidden, for PV conversion
5624   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5625   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5626   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5627                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5628   DrawPosition(TRUE, boards[currentMove]);
5629 }
5630
5631 int
5632 MultiPV (ChessProgramState *cps)
5633 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5634         int i;
5635         for(i=0; i<cps->nrOptions; i++)
5636             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5637                 return i;
5638         return -1;
5639 }
5640
5641 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5642
5643 Boolean
5644 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5645 {
5646         int startPV, multi, lineStart, origIndex = index;
5647         char *p, buf2[MSG_SIZ];
5648         ChessProgramState *cps = (pane ? &second : &first);
5649
5650         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5651         lastX = x; lastY = y;
5652         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5653         lineStart = startPV = index;
5654         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5655         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5656         index = startPV;
5657         do{ while(buf[index] && buf[index] != '\n') index++;
5658         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5659         buf[index] = 0;
5660         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5661                 int n = cps->option[multi].value;
5662                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5663                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5664                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5665                 cps->option[multi].value = n;
5666                 *start = *end = 0;
5667                 return FALSE;
5668         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5669                 ExcludeClick(origIndex - lineStart);
5670                 return FALSE;
5671         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5672                 Collapse(origIndex - lineStart);
5673                 return FALSE;
5674         }
5675         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5676         *start = startPV; *end = index-1;
5677         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5678         return TRUE;
5679 }
5680
5681 char *
5682 PvToSAN (char *pv)
5683 {
5684         static char buf[10*MSG_SIZ];
5685         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5686         *buf = NULLCHAR;
5687         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5688         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5689         for(i = forwardMostMove; i<endPV; i++){
5690             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5691             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5692             k += strlen(buf+k);
5693         }
5694         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5695         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5696         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5697         endPV = savedEnd;
5698         return buf;
5699 }
5700
5701 Boolean
5702 LoadPV (int x, int y)
5703 { // called on right mouse click to load PV
5704   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5705   lastX = x; lastY = y;
5706   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5707   extendGame = FALSE;
5708   return TRUE;
5709 }
5710
5711 void
5712 UnLoadPV ()
5713 {
5714   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5715   if(endPV < 0) return;
5716   if(appData.autoCopyPV) CopyFENToClipboard();
5717   endPV = -1;
5718   if(extendGame && currentMove > forwardMostMove) {
5719         Boolean saveAnimate = appData.animate;
5720         if(pushed) {
5721             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5722                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5723             } else storedGames--; // abandon shelved tail of original game
5724         }
5725         pushed = FALSE;
5726         forwardMostMove = currentMove;
5727         currentMove = oldFMM;
5728         appData.animate = FALSE;
5729         ToNrEvent(forwardMostMove);
5730         appData.animate = saveAnimate;
5731   }
5732   currentMove = forwardMostMove;
5733   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5734   ClearPremoveHighlights();
5735   DrawPosition(TRUE, boards[currentMove]);
5736 }
5737
5738 void
5739 MovePV (int x, int y, int h)
5740 { // step through PV based on mouse coordinates (called on mouse move)
5741   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5742
5743   // we must somehow check if right button is still down (might be released off board!)
5744   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5745   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5746   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5747   if(!step) return;
5748   lastX = x; lastY = y;
5749
5750   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5751   if(endPV < 0) return;
5752   if(y < margin) step = 1; else
5753   if(y > h - margin) step = -1;
5754   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5755   currentMove += step;
5756   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5757   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5758                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5759   DrawPosition(FALSE, boards[currentMove]);
5760 }
5761
5762
5763 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5764 // All positions will have equal probability, but the current method will not provide a unique
5765 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5766 #define DARK 1
5767 #define LITE 2
5768 #define ANY 3
5769
5770 int squaresLeft[4];
5771 int piecesLeft[(int)BlackPawn];
5772 int seed, nrOfShuffles;
5773
5774 void
5775 GetPositionNumber ()
5776 {       // sets global variable seed
5777         int i;
5778
5779         seed = appData.defaultFrcPosition;
5780         if(seed < 0) { // randomize based on time for negative FRC position numbers
5781                 for(i=0; i<50; i++) seed += random();
5782                 seed = random() ^ random() >> 8 ^ random() << 8;
5783                 if(seed<0) seed = -seed;
5784         }
5785 }
5786
5787 int
5788 put (Board board, int pieceType, int rank, int n, int shade)
5789 // put the piece on the (n-1)-th empty squares of the given shade
5790 {
5791         int i;
5792
5793         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5794                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5795                         board[rank][i] = (ChessSquare) pieceType;
5796                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5797                         squaresLeft[ANY]--;
5798                         piecesLeft[pieceType]--;
5799                         return i;
5800                 }
5801         }
5802         return -1;
5803 }
5804
5805
5806 void
5807 AddOnePiece (Board board, int pieceType, int rank, int shade)
5808 // calculate where the next piece goes, (any empty square), and put it there
5809 {
5810         int i;
5811
5812         i = seed % squaresLeft[shade];
5813         nrOfShuffles *= squaresLeft[shade];
5814         seed /= squaresLeft[shade];
5815         put(board, pieceType, rank, i, shade);
5816 }
5817
5818 void
5819 AddTwoPieces (Board board, int pieceType, int rank)
5820 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5821 {
5822         int i, n=squaresLeft[ANY], j=n-1, k;
5823
5824         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5825         i = seed % k;  // pick one
5826         nrOfShuffles *= k;
5827         seed /= k;
5828         while(i >= j) i -= j--;
5829         j = n - 1 - j; i += j;
5830         put(board, pieceType, rank, j, ANY);
5831         put(board, pieceType, rank, i, ANY);
5832 }
5833
5834 void
5835 SetUpShuffle (Board board, int number)
5836 {
5837         int i, p, first=1;
5838
5839         GetPositionNumber(); nrOfShuffles = 1;
5840
5841         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5842         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5843         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5844
5845         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5846
5847         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5848             p = (int) board[0][i];
5849             if(p < (int) BlackPawn) piecesLeft[p] ++;
5850             board[0][i] = EmptySquare;
5851         }
5852
5853         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5854             // shuffles restricted to allow normal castling put KRR first
5855             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5856                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5857             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5858                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5859             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5860                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5861             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5862                 put(board, WhiteRook, 0, 0, ANY);
5863             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5864         }
5865
5866         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5867             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5868             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5869                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5870                 while(piecesLeft[p] >= 2) {
5871                     AddOnePiece(board, p, 0, LITE);
5872                     AddOnePiece(board, p, 0, DARK);
5873                 }
5874                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5875             }
5876
5877         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5878             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5879             // but we leave King and Rooks for last, to possibly obey FRC restriction
5880             if(p == (int)WhiteRook) continue;
5881             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5882             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5883         }
5884
5885         // now everything is placed, except perhaps King (Unicorn) and Rooks
5886
5887         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5888             // Last King gets castling rights
5889             while(piecesLeft[(int)WhiteUnicorn]) {
5890                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5891                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5892             }
5893
5894             while(piecesLeft[(int)WhiteKing]) {
5895                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5896                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5897             }
5898
5899
5900         } else {
5901             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5902             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5903         }
5904
5905         // Only Rooks can be left; simply place them all
5906         while(piecesLeft[(int)WhiteRook]) {
5907                 i = put(board, WhiteRook, 0, 0, ANY);
5908                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5909                         if(first) {
5910                                 first=0;
5911                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5912                         }
5913                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5914                 }
5915         }
5916         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5917             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5918         }
5919
5920         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5921 }
5922
5923 int
5924 SetCharTable (char *table, const char * map)
5925 /* [HGM] moved here from winboard.c because of its general usefulness */
5926 /*       Basically a safe strcpy that uses the last character as King */
5927 {
5928     int result = FALSE; int NrPieces;
5929
5930     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5931                     && NrPieces >= 12 && !(NrPieces&1)) {
5932         int i; /* [HGM] Accept even length from 12 to 34 */
5933
5934         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5935         for( i=0; i<NrPieces/2-1; i++ ) {
5936             table[i] = map[i];
5937             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5938         }
5939         table[(int) WhiteKing]  = map[NrPieces/2-1];
5940         table[(int) BlackKing]  = map[NrPieces-1];
5941
5942         result = TRUE;
5943     }
5944
5945     return result;
5946 }
5947
5948 void
5949 Prelude (Board board)
5950 {       // [HGM] superchess: random selection of exo-pieces
5951         int i, j, k; ChessSquare p;
5952         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5953
5954         GetPositionNumber(); // use FRC position number
5955
5956         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5957             SetCharTable(pieceToChar, appData.pieceToCharTable);
5958             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5959                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5960         }
5961
5962         j = seed%4;                 seed /= 4;
5963         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5964         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5965         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5966         j = seed%3 + (seed%3 >= j); seed /= 3;
5967         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5968         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5969         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5970         j = seed%3;                 seed /= 3;
5971         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5972         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5973         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5974         j = seed%2 + (seed%2 >= j); seed /= 2;
5975         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5976         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5977         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5978         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5979         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5980         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5981         put(board, exoPieces[0],    0, 0, ANY);
5982         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5983 }
5984
5985 void
5986 InitPosition (int redraw)
5987 {
5988     ChessSquare (* pieces)[BOARD_FILES];
5989     int i, j, pawnRow=1, pieceRows=1, overrule,
5990     oldx = gameInfo.boardWidth,
5991     oldy = gameInfo.boardHeight,
5992     oldh = gameInfo.holdingsWidth;
5993     static int oldv;
5994
5995     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5996
5997     /* [AS] Initialize pv info list [HGM] and game status */
5998     {
5999         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6000             pvInfoList[i].depth = 0;
6001             boards[i][EP_STATUS] = EP_NONE;
6002             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6003         }
6004
6005         initialRulePlies = 0; /* 50-move counter start */
6006
6007         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6008         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6009     }
6010
6011
6012     /* [HGM] logic here is completely changed. In stead of full positions */
6013     /* the initialized data only consist of the two backranks. The switch */
6014     /* selects which one we will use, which is than copied to the Board   */
6015     /* initialPosition, which for the rest is initialized by Pawns and    */
6016     /* empty squares. This initial position is then copied to boards[0],  */
6017     /* possibly after shuffling, so that it remains available.            */
6018
6019     gameInfo.holdingsWidth = 0; /* default board sizes */
6020     gameInfo.boardWidth    = 8;
6021     gameInfo.boardHeight   = 8;
6022     gameInfo.holdingsSize  = 0;
6023     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6024     for(i=0; i<BOARD_FILES-2; i++)
6025       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6026     initialPosition[EP_STATUS] = EP_NONE;
6027     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6028     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6029          SetCharTable(pieceNickName, appData.pieceNickNames);
6030     else SetCharTable(pieceNickName, "............");
6031     pieces = FIDEArray;
6032
6033     switch (gameInfo.variant) {
6034     case VariantFischeRandom:
6035       shuffleOpenings = TRUE;
6036       appData.fischerCastling = TRUE;
6037     default:
6038       break;
6039     case VariantShatranj:
6040       pieces = ShatranjArray;
6041       nrCastlingRights = 0;
6042       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6043       break;
6044     case VariantMakruk:
6045       pieces = makrukArray;
6046       nrCastlingRights = 0;
6047       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6048       break;
6049     case VariantASEAN:
6050       pieces = aseanArray;
6051       nrCastlingRights = 0;
6052       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6053       break;
6054     case VariantTwoKings:
6055       pieces = twoKingsArray;
6056       break;
6057     case VariantGrand:
6058       pieces = GrandArray;
6059       nrCastlingRights = 0;
6060       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6061       gameInfo.boardWidth = 10;
6062       gameInfo.boardHeight = 10;
6063       gameInfo.holdingsSize = 7;
6064       break;
6065     case VariantCapaRandom:
6066       shuffleOpenings = TRUE;
6067       appData.fischerCastling = TRUE;
6068     case VariantCapablanca:
6069       pieces = CapablancaArray;
6070       gameInfo.boardWidth = 10;
6071       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6072       break;
6073     case VariantGothic:
6074       pieces = GothicArray;
6075       gameInfo.boardWidth = 10;
6076       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6077       break;
6078     case VariantSChess:
6079       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6080       gameInfo.holdingsSize = 7;
6081       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6082       break;
6083     case VariantJanus:
6084       pieces = JanusArray;
6085       gameInfo.boardWidth = 10;
6086       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6087       nrCastlingRights = 6;
6088         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6089         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6090         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6091         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6092         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6093         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6094       break;
6095     case VariantFalcon:
6096       pieces = FalconArray;
6097       gameInfo.boardWidth = 10;
6098       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6099       break;
6100     case VariantXiangqi:
6101       pieces = XiangqiArray;
6102       gameInfo.boardWidth  = 9;
6103       gameInfo.boardHeight = 10;
6104       nrCastlingRights = 0;
6105       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6106       break;
6107     case VariantShogi:
6108       pieces = ShogiArray;
6109       gameInfo.boardWidth  = 9;
6110       gameInfo.boardHeight = 9;
6111       gameInfo.holdingsSize = 7;
6112       nrCastlingRights = 0;
6113       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6114       break;
6115     case VariantChu:
6116       pieces = ChuArray; pieceRows = 3;
6117       gameInfo.boardWidth  = 12;
6118       gameInfo.boardHeight = 12;
6119       nrCastlingRights = 0;
6120       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6121                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6122       break;
6123     case VariantCourier:
6124       pieces = CourierArray;
6125       gameInfo.boardWidth  = 12;
6126       nrCastlingRights = 0;
6127       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6128       break;
6129     case VariantKnightmate:
6130       pieces = KnightmateArray;
6131       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6132       break;
6133     case VariantSpartan:
6134       pieces = SpartanArray;
6135       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6136       break;
6137     case VariantLion:
6138       pieces = lionArray;
6139       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6140       break;
6141     case VariantChuChess:
6142       pieces = ChuChessArray;
6143       gameInfo.boardWidth = 10;
6144       gameInfo.boardHeight = 10;
6145       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6146       break;
6147     case VariantFairy:
6148       pieces = fairyArray;
6149       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6150       break;
6151     case VariantGreat:
6152       pieces = GreatArray;
6153       gameInfo.boardWidth = 10;
6154       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6155       gameInfo.holdingsSize = 8;
6156       break;
6157     case VariantSuper:
6158       pieces = FIDEArray;
6159       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6160       gameInfo.holdingsSize = 8;
6161       startedFromSetupPosition = TRUE;
6162       break;
6163     case VariantCrazyhouse:
6164     case VariantBughouse:
6165       pieces = FIDEArray;
6166       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6167       gameInfo.holdingsSize = 5;
6168       break;
6169     case VariantWildCastle:
6170       pieces = FIDEArray;
6171       /* !!?shuffle with kings guaranteed to be on d or e file */
6172       shuffleOpenings = 1;
6173       break;
6174     case VariantNoCastle:
6175       pieces = FIDEArray;
6176       nrCastlingRights = 0;
6177       /* !!?unconstrained back-rank shuffle */
6178       shuffleOpenings = 1;
6179       break;
6180     }
6181
6182     overrule = 0;
6183     if(appData.NrFiles >= 0) {
6184         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6185         gameInfo.boardWidth = appData.NrFiles;
6186     }
6187     if(appData.NrRanks >= 0) {
6188         gameInfo.boardHeight = appData.NrRanks;
6189     }
6190     if(appData.holdingsSize >= 0) {
6191         i = appData.holdingsSize;
6192         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6193         gameInfo.holdingsSize = i;
6194     }
6195     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6196     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6197         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6198
6199     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6200     if(pawnRow < 1) pawnRow = 1;
6201     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6202        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6203     if(gameInfo.variant == VariantChu) pawnRow = 3;
6204
6205     /* User pieceToChar list overrules defaults */
6206     if(appData.pieceToCharTable != NULL)
6207         SetCharTable(pieceToChar, appData.pieceToCharTable);
6208
6209     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6210
6211         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6212             s = (ChessSquare) 0; /* account holding counts in guard band */
6213         for( i=0; i<BOARD_HEIGHT; i++ )
6214             initialPosition[i][j] = s;
6215
6216         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6217         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6218         initialPosition[pawnRow][j] = WhitePawn;
6219         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6220         if(gameInfo.variant == VariantXiangqi) {
6221             if(j&1) {
6222                 initialPosition[pawnRow][j] =
6223                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6224                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6225                    initialPosition[2][j] = WhiteCannon;
6226                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6227                 }
6228             }
6229         }
6230         if(gameInfo.variant == VariantChu) {
6231              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6232                initialPosition[pawnRow+1][j] = WhiteCobra,
6233                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6234              for(i=1; i<pieceRows; i++) {
6235                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6236                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6237              }
6238         }
6239         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6240             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6241                initialPosition[0][j] = WhiteRook;
6242                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6243             }
6244         }
6245         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6246     }
6247     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6248     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6249
6250             j=BOARD_LEFT+1;
6251             initialPosition[1][j] = WhiteBishop;
6252             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6253             j=BOARD_RGHT-2;
6254             initialPosition[1][j] = WhiteRook;
6255             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6256     }
6257
6258     if( nrCastlingRights == -1) {
6259         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6260         /*       This sets default castling rights from none to normal corners   */
6261         /* Variants with other castling rights must set them themselves above    */
6262         nrCastlingRights = 6;
6263
6264         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6265         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6266         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6267         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6268         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6269         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6270      }
6271
6272      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6273      if(gameInfo.variant == VariantGreat) { // promotion commoners
6274         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6275         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6276         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6277         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6278      }
6279      if( gameInfo.variant == VariantSChess ) {
6280       initialPosition[1][0] = BlackMarshall;
6281       initialPosition[2][0] = BlackAngel;
6282       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6283       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6284       initialPosition[1][1] = initialPosition[2][1] =
6285       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6286      }
6287   if (appData.debugMode) {
6288     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6289   }
6290     if(shuffleOpenings) {
6291         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6292         startedFromSetupPosition = TRUE;
6293     }
6294     if(startedFromPositionFile) {
6295       /* [HGM] loadPos: use PositionFile for every new game */
6296       CopyBoard(initialPosition, filePosition);
6297       for(i=0; i<nrCastlingRights; i++)
6298           initialRights[i] = filePosition[CASTLING][i];
6299       startedFromSetupPosition = TRUE;
6300     }
6301
6302     CopyBoard(boards[0], initialPosition);
6303
6304     if(oldx != gameInfo.boardWidth ||
6305        oldy != gameInfo.boardHeight ||
6306        oldv != gameInfo.variant ||
6307        oldh != gameInfo.holdingsWidth
6308                                          )
6309             InitDrawingSizes(-2 ,0);
6310
6311     oldv = gameInfo.variant;
6312     if (redraw)
6313       DrawPosition(TRUE, boards[currentMove]);
6314 }
6315
6316 void
6317 SendBoard (ChessProgramState *cps, int moveNum)
6318 {
6319     char message[MSG_SIZ];
6320
6321     if (cps->useSetboard) {
6322       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6323       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6324       SendToProgram(message, cps);
6325       free(fen);
6326
6327     } else {
6328       ChessSquare *bp;
6329       int i, j, left=0, right=BOARD_WIDTH;
6330       /* Kludge to set black to move, avoiding the troublesome and now
6331        * deprecated "black" command.
6332        */
6333       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6334         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6335
6336       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6337
6338       SendToProgram("edit\n", cps);
6339       SendToProgram("#\n", cps);
6340       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6341         bp = &boards[moveNum][i][left];
6342         for (j = left; j < right; j++, bp++) {
6343           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6344           if ((int) *bp < (int) BlackPawn) {
6345             if(j == BOARD_RGHT+1)
6346                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6347             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6348             if(message[0] == '+' || message[0] == '~') {
6349               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6350                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6351                         AAA + j, ONE + i);
6352             }
6353             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6354                 message[1] = BOARD_RGHT   - 1 - j + '1';
6355                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6356             }
6357             SendToProgram(message, cps);
6358           }
6359         }
6360       }
6361
6362       SendToProgram("c\n", cps);
6363       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6364         bp = &boards[moveNum][i][left];
6365         for (j = left; j < right; j++, bp++) {
6366           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6367           if (((int) *bp != (int) EmptySquare)
6368               && ((int) *bp >= (int) BlackPawn)) {
6369             if(j == BOARD_LEFT-2)
6370                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6371             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6372                     AAA + j, ONE + i);
6373             if(message[0] == '+' || message[0] == '~') {
6374               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6375                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6376                         AAA + j, ONE + i);
6377             }
6378             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6379                 message[1] = BOARD_RGHT   - 1 - j + '1';
6380                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6381             }
6382             SendToProgram(message, cps);
6383           }
6384         }
6385       }
6386
6387       SendToProgram(".\n", cps);
6388     }
6389     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6390 }
6391
6392 char exclusionHeader[MSG_SIZ];
6393 int exCnt, excludePtr;
6394 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6395 static Exclusion excluTab[200];
6396 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6397
6398 static void
6399 WriteMap (int s)
6400 {
6401     int j;
6402     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6403     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6404 }
6405
6406 static void
6407 ClearMap ()
6408 {
6409     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6410     excludePtr = 24; exCnt = 0;
6411     WriteMap(0);
6412 }
6413
6414 static void
6415 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6416 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6417     char buf[2*MOVE_LEN], *p;
6418     Exclusion *e = excluTab;
6419     int i;
6420     for(i=0; i<exCnt; i++)
6421         if(e[i].ff == fromX && e[i].fr == fromY &&
6422            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6423     if(i == exCnt) { // was not in exclude list; add it
6424         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6425         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6426             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6427             return; // abort
6428         }
6429         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6430         excludePtr++; e[i].mark = excludePtr++;
6431         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6432         exCnt++;
6433     }
6434     exclusionHeader[e[i].mark] = state;
6435 }
6436
6437 static int
6438 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6439 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6440     char buf[MSG_SIZ];
6441     int j, k;
6442     ChessMove moveType;
6443     if((signed char)promoChar == -1) { // kludge to indicate best move
6444         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6445             return 1; // if unparsable, abort
6446     }
6447     // update exclusion map (resolving toggle by consulting existing state)
6448     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6449     j = k%8; k >>= 3;
6450     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6451     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6452          excludeMap[k] |=   1<<j;
6453     else excludeMap[k] &= ~(1<<j);
6454     // update header
6455     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6456     // inform engine
6457     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6458     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6459     SendToBoth(buf);
6460     return (state == '+');
6461 }
6462
6463 static void
6464 ExcludeClick (int index)
6465 {
6466     int i, j;
6467     Exclusion *e = excluTab;
6468     if(index < 25) { // none, best or tail clicked
6469         if(index < 13) { // none: include all
6470             WriteMap(0); // clear map
6471             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6472             SendToBoth("include all\n"); // and inform engine
6473         } else if(index > 18) { // tail
6474             if(exclusionHeader[19] == '-') { // tail was excluded
6475                 SendToBoth("include all\n");
6476                 WriteMap(0); // clear map completely
6477                 // now re-exclude selected moves
6478                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6479                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6480             } else { // tail was included or in mixed state
6481                 SendToBoth("exclude all\n");
6482                 WriteMap(0xFF); // fill map completely
6483                 // now re-include selected moves
6484                 j = 0; // count them
6485                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6486                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6487                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6488             }
6489         } else { // best
6490             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6491         }
6492     } else {
6493         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6494             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6495             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6496             break;
6497         }
6498     }
6499 }
6500
6501 ChessSquare
6502 DefaultPromoChoice (int white)
6503 {
6504     ChessSquare result;
6505     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6506        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6507         result = WhiteFerz; // no choice
6508     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6509         result= WhiteKing; // in Suicide Q is the last thing we want
6510     else if(gameInfo.variant == VariantSpartan)
6511         result = white ? WhiteQueen : WhiteAngel;
6512     else result = WhiteQueen;
6513     if(!white) result = WHITE_TO_BLACK result;
6514     return result;
6515 }
6516
6517 static int autoQueen; // [HGM] oneclick
6518
6519 int
6520 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6521 {
6522     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6523     /* [HGM] add Shogi promotions */
6524     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6525     ChessSquare piece, partner;
6526     ChessMove moveType;
6527     Boolean premove;
6528
6529     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6530     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6531
6532     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6533       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6534         return FALSE;
6535
6536     piece = boards[currentMove][fromY][fromX];
6537     if(gameInfo.variant == VariantChu) {
6538         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6539         promotionZoneSize = BOARD_HEIGHT/3;
6540         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6541     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6542         promotionZoneSize = BOARD_HEIGHT/3;
6543         highestPromotingPiece = (int)WhiteAlfil;
6544     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6545         promotionZoneSize = 3;
6546     }
6547
6548     // Treat Lance as Pawn when it is not representing Amazon or Lance
6549     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6550         if(piece == WhiteLance) piece = WhitePawn; else
6551         if(piece == BlackLance) piece = BlackPawn;
6552     }
6553
6554     // next weed out all moves that do not touch the promotion zone at all
6555     if((int)piece >= BlackPawn) {
6556         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6557              return FALSE;
6558         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6559         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6560     } else {
6561         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6562            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6563         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6564              return FALSE;
6565     }
6566
6567     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6568
6569     // weed out mandatory Shogi promotions
6570     if(gameInfo.variant == VariantShogi) {
6571         if(piece >= BlackPawn) {
6572             if(toY == 0 && piece == BlackPawn ||
6573                toY == 0 && piece == BlackQueen ||
6574                toY <= 1 && piece == BlackKnight) {
6575                 *promoChoice = '+';
6576                 return FALSE;
6577             }
6578         } else {
6579             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6580                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6581                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6582                 *promoChoice = '+';
6583                 return FALSE;
6584             }
6585         }
6586     }
6587
6588     // weed out obviously illegal Pawn moves
6589     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6590         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6591         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6592         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6593         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6594         // note we are not allowed to test for valid (non-)capture, due to premove
6595     }
6596
6597     // we either have a choice what to promote to, or (in Shogi) whether to promote
6598     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6599        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6600         ChessSquare p=BlackFerz;  // no choice
6601         while(p < EmptySquare) {  //but make sure we use piece that exists
6602             *promoChoice = PieceToChar(p++);
6603             if(*promoChoice != '.') break;
6604         }
6605         return FALSE;
6606     }
6607     // no sense asking what we must promote to if it is going to explode...
6608     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6609         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6610         return FALSE;
6611     }
6612     // give caller the default choice even if we will not make it
6613     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6614     partner = piece; // pieces can promote if the pieceToCharTable says so
6615     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6616     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6617     if(        sweepSelect && gameInfo.variant != VariantGreat
6618                            && gameInfo.variant != VariantGrand
6619                            && gameInfo.variant != VariantSuper) return FALSE;
6620     if(autoQueen) return FALSE; // predetermined
6621
6622     // suppress promotion popup on illegal moves that are not premoves
6623     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6624               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6625     if(appData.testLegality && !premove) {
6626         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6627                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6628         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6629         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6630             return FALSE;
6631     }
6632
6633     return TRUE;
6634 }
6635
6636 int
6637 InPalace (int row, int column)
6638 {   /* [HGM] for Xiangqi */
6639     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6640          column < (BOARD_WIDTH + 4)/2 &&
6641          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6642     return FALSE;
6643 }
6644
6645 int
6646 PieceForSquare (int x, int y)
6647 {
6648   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6649      return -1;
6650   else
6651      return boards[currentMove][y][x];
6652 }
6653
6654 int
6655 OKToStartUserMove (int x, int y)
6656 {
6657     ChessSquare from_piece;
6658     int white_piece;
6659
6660     if (matchMode) return FALSE;
6661     if (gameMode == EditPosition) return TRUE;
6662
6663     if (x >= 0 && y >= 0)
6664       from_piece = boards[currentMove][y][x];
6665     else
6666       from_piece = EmptySquare;
6667
6668     if (from_piece == EmptySquare) return FALSE;
6669
6670     white_piece = (int)from_piece >= (int)WhitePawn &&
6671       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6672
6673     switch (gameMode) {
6674       case AnalyzeFile:
6675       case TwoMachinesPlay:
6676       case EndOfGame:
6677         return FALSE;
6678
6679       case IcsObserving:
6680       case IcsIdle:
6681         return FALSE;
6682
6683       case MachinePlaysWhite:
6684       case IcsPlayingBlack:
6685         if (appData.zippyPlay) return FALSE;
6686         if (white_piece) {
6687             DisplayMoveError(_("You are playing Black"));
6688             return FALSE;
6689         }
6690         break;
6691
6692       case MachinePlaysBlack:
6693       case IcsPlayingWhite:
6694         if (appData.zippyPlay) return FALSE;
6695         if (!white_piece) {
6696             DisplayMoveError(_("You are playing White"));
6697             return FALSE;
6698         }
6699         break;
6700
6701       case PlayFromGameFile:
6702             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6703       case EditGame:
6704         if (!white_piece && WhiteOnMove(currentMove)) {
6705             DisplayMoveError(_("It is White's turn"));
6706             return FALSE;
6707         }
6708         if (white_piece && !WhiteOnMove(currentMove)) {
6709             DisplayMoveError(_("It is Black's turn"));
6710             return FALSE;
6711         }
6712         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6713             /* Editing correspondence game history */
6714             /* Could disallow this or prompt for confirmation */
6715             cmailOldMove = -1;
6716         }
6717         break;
6718
6719       case BeginningOfGame:
6720         if (appData.icsActive) return FALSE;
6721         if (!appData.noChessProgram) {
6722             if (!white_piece) {
6723                 DisplayMoveError(_("You are playing White"));
6724                 return FALSE;
6725             }
6726         }
6727         break;
6728
6729       case Training:
6730         if (!white_piece && WhiteOnMove(currentMove)) {
6731             DisplayMoveError(_("It is White's turn"));
6732             return FALSE;
6733         }
6734         if (white_piece && !WhiteOnMove(currentMove)) {
6735             DisplayMoveError(_("It is Black's turn"));
6736             return FALSE;
6737         }
6738         break;
6739
6740       default:
6741       case IcsExamining:
6742         break;
6743     }
6744     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6745         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6746         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6747         && gameMode != AnalyzeFile && gameMode != Training) {
6748         DisplayMoveError(_("Displayed position is not current"));
6749         return FALSE;
6750     }
6751     return TRUE;
6752 }
6753
6754 Boolean
6755 OnlyMove (int *x, int *y, Boolean captures)
6756 {
6757     DisambiguateClosure cl;
6758     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6759     switch(gameMode) {
6760       case MachinePlaysBlack:
6761       case IcsPlayingWhite:
6762       case BeginningOfGame:
6763         if(!WhiteOnMove(currentMove)) return FALSE;
6764         break;
6765       case MachinePlaysWhite:
6766       case IcsPlayingBlack:
6767         if(WhiteOnMove(currentMove)) return FALSE;
6768         break;
6769       case EditGame:
6770         break;
6771       default:
6772         return FALSE;
6773     }
6774     cl.pieceIn = EmptySquare;
6775     cl.rfIn = *y;
6776     cl.ffIn = *x;
6777     cl.rtIn = -1;
6778     cl.ftIn = -1;
6779     cl.promoCharIn = NULLCHAR;
6780     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6781     if( cl.kind == NormalMove ||
6782         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6783         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6784         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6785       fromX = cl.ff;
6786       fromY = cl.rf;
6787       *x = cl.ft;
6788       *y = cl.rt;
6789       return TRUE;
6790     }
6791     if(cl.kind != ImpossibleMove) return FALSE;
6792     cl.pieceIn = EmptySquare;
6793     cl.rfIn = -1;
6794     cl.ffIn = -1;
6795     cl.rtIn = *y;
6796     cl.ftIn = *x;
6797     cl.promoCharIn = NULLCHAR;
6798     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6799     if( cl.kind == NormalMove ||
6800         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6801         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6802         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6803       fromX = cl.ff;
6804       fromY = cl.rf;
6805       *x = cl.ft;
6806       *y = cl.rt;
6807       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6808       return TRUE;
6809     }
6810     return FALSE;
6811 }
6812
6813 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6814 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6815 int lastLoadGameUseList = FALSE;
6816 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6817 ChessMove lastLoadGameStart = EndOfFile;
6818 int doubleClick;
6819
6820 void
6821 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6822 {
6823     ChessMove moveType;
6824     ChessSquare pup;
6825     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6826
6827     /* Check if the user is playing in turn.  This is complicated because we
6828        let the user "pick up" a piece before it is his turn.  So the piece he
6829        tried to pick up may have been captured by the time he puts it down!
6830        Therefore we use the color the user is supposed to be playing in this
6831        test, not the color of the piece that is currently on the starting
6832        square---except in EditGame mode, where the user is playing both
6833        sides; fortunately there the capture race can't happen.  (It can
6834        now happen in IcsExamining mode, but that's just too bad.  The user
6835        will get a somewhat confusing message in that case.)
6836        */
6837
6838     switch (gameMode) {
6839       case AnalyzeFile:
6840       case TwoMachinesPlay:
6841       case EndOfGame:
6842       case IcsObserving:
6843       case IcsIdle:
6844         /* We switched into a game mode where moves are not accepted,
6845            perhaps while the mouse button was down. */
6846         return;
6847
6848       case MachinePlaysWhite:
6849         /* User is moving for Black */
6850         if (WhiteOnMove(currentMove)) {
6851             DisplayMoveError(_("It is White's turn"));
6852             return;
6853         }
6854         break;
6855
6856       case MachinePlaysBlack:
6857         /* User is moving for White */
6858         if (!WhiteOnMove(currentMove)) {
6859             DisplayMoveError(_("It is Black's turn"));
6860             return;
6861         }
6862         break;
6863
6864       case PlayFromGameFile:
6865             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6866       case EditGame:
6867       case IcsExamining:
6868       case BeginningOfGame:
6869       case AnalyzeMode:
6870       case Training:
6871         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6872         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6873             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6874             /* User is moving for Black */
6875             if (WhiteOnMove(currentMove)) {
6876                 DisplayMoveError(_("It is White's turn"));
6877                 return;
6878             }
6879         } else {
6880             /* User is moving for White */
6881             if (!WhiteOnMove(currentMove)) {
6882                 DisplayMoveError(_("It is Black's turn"));
6883                 return;
6884             }
6885         }
6886         break;
6887
6888       case IcsPlayingBlack:
6889         /* User is moving for Black */
6890         if (WhiteOnMove(currentMove)) {
6891             if (!appData.premove) {
6892                 DisplayMoveError(_("It is White's turn"));
6893             } else if (toX >= 0 && toY >= 0) {
6894                 premoveToX = toX;
6895                 premoveToY = toY;
6896                 premoveFromX = fromX;
6897                 premoveFromY = fromY;
6898                 premovePromoChar = promoChar;
6899                 gotPremove = 1;
6900                 if (appData.debugMode)
6901                     fprintf(debugFP, "Got premove: fromX %d,"
6902                             "fromY %d, toX %d, toY %d\n",
6903                             fromX, fromY, toX, toY);
6904             }
6905             return;
6906         }
6907         break;
6908
6909       case IcsPlayingWhite:
6910         /* User is moving for White */
6911         if (!WhiteOnMove(currentMove)) {
6912             if (!appData.premove) {
6913                 DisplayMoveError(_("It is Black's turn"));
6914             } else if (toX >= 0 && toY >= 0) {
6915                 premoveToX = toX;
6916                 premoveToY = toY;
6917                 premoveFromX = fromX;
6918                 premoveFromY = fromY;
6919                 premovePromoChar = promoChar;
6920                 gotPremove = 1;
6921                 if (appData.debugMode)
6922                     fprintf(debugFP, "Got premove: fromX %d,"
6923                             "fromY %d, toX %d, toY %d\n",
6924                             fromX, fromY, toX, toY);
6925             }
6926             return;
6927         }
6928         break;
6929
6930       default:
6931         break;
6932
6933       case EditPosition:
6934         /* EditPosition, empty square, or different color piece;
6935            click-click move is possible */
6936         if (toX == -2 || toY == -2) {
6937             boards[0][fromY][fromX] = EmptySquare;
6938             DrawPosition(FALSE, boards[currentMove]);
6939             return;
6940         } else if (toX >= 0 && toY >= 0) {
6941             boards[0][toY][toX] = boards[0][fromY][fromX];
6942             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6943                 if(boards[0][fromY][0] != EmptySquare) {
6944                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6945                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6946                 }
6947             } else
6948             if(fromX == BOARD_RGHT+1) {
6949                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6950                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6951                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6952                 }
6953             } else
6954             boards[0][fromY][fromX] = gatingPiece;
6955             DrawPosition(FALSE, boards[currentMove]);
6956             return;
6957         }
6958         return;
6959     }
6960
6961     if(toX < 0 || toY < 0) return;
6962     pup = boards[currentMove][toY][toX];
6963
6964     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6965     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6966          if( pup != EmptySquare ) return;
6967          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6968            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6969                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6970            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6971            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6972            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6973            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6974          fromY = DROP_RANK;
6975     }
6976
6977     /* [HGM] always test for legality, to get promotion info */
6978     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6979                                          fromY, fromX, toY, toX, promoChar);
6980
6981     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6982
6983     /* [HGM] but possibly ignore an IllegalMove result */
6984     if (appData.testLegality) {
6985         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6986             DisplayMoveError(_("Illegal move"));
6987             return;
6988         }
6989     }
6990
6991     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6992         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6993              ClearPremoveHighlights(); // was included
6994         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6995         return;
6996     }
6997
6998     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6999 }
7000
7001 /* Common tail of UserMoveEvent and DropMenuEvent */
7002 int
7003 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7004 {
7005     char *bookHit = 0;
7006
7007     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7008         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7009         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7010         if(WhiteOnMove(currentMove)) {
7011             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7012         } else {
7013             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7014         }
7015     }
7016
7017     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7018        move type in caller when we know the move is a legal promotion */
7019     if(moveType == NormalMove && promoChar)
7020         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7021
7022     /* [HGM] <popupFix> The following if has been moved here from
7023        UserMoveEvent(). Because it seemed to belong here (why not allow
7024        piece drops in training games?), and because it can only be
7025        performed after it is known to what we promote. */
7026     if (gameMode == Training) {
7027       /* compare the move played on the board to the next move in the
7028        * game. If they match, display the move and the opponent's response.
7029        * If they don't match, display an error message.
7030        */
7031       int saveAnimate;
7032       Board testBoard;
7033       CopyBoard(testBoard, boards[currentMove]);
7034       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7035
7036       if (CompareBoards(testBoard, boards[currentMove+1])) {
7037         ForwardInner(currentMove+1);
7038
7039         /* Autoplay the opponent's response.
7040          * if appData.animate was TRUE when Training mode was entered,
7041          * the response will be animated.
7042          */
7043         saveAnimate = appData.animate;
7044         appData.animate = animateTraining;
7045         ForwardInner(currentMove+1);
7046         appData.animate = saveAnimate;
7047
7048         /* check for the end of the game */
7049         if (currentMove >= forwardMostMove) {
7050           gameMode = PlayFromGameFile;
7051           ModeHighlight();
7052           SetTrainingModeOff();
7053           DisplayInformation(_("End of game"));
7054         }
7055       } else {
7056         DisplayError(_("Incorrect move"), 0);
7057       }
7058       return 1;
7059     }
7060
7061   /* Ok, now we know that the move is good, so we can kill
7062      the previous line in Analysis Mode */
7063   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7064                                 && currentMove < forwardMostMove) {
7065     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7066     else forwardMostMove = currentMove;
7067   }
7068
7069   ClearMap();
7070
7071   /* If we need the chess program but it's dead, restart it */
7072   ResurrectChessProgram();
7073
7074   /* A user move restarts a paused game*/
7075   if (pausing)
7076     PauseEvent();
7077
7078   thinkOutput[0] = NULLCHAR;
7079
7080   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7081
7082   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7083     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7084     return 1;
7085   }
7086
7087   if (gameMode == BeginningOfGame) {
7088     if (appData.noChessProgram) {
7089       gameMode = EditGame;
7090       SetGameInfo();
7091     } else {
7092       char buf[MSG_SIZ];
7093       gameMode = MachinePlaysBlack;
7094       StartClocks();
7095       SetGameInfo();
7096       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7097       DisplayTitle(buf);
7098       if (first.sendName) {
7099         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7100         SendToProgram(buf, &first);
7101       }
7102       StartClocks();
7103     }
7104     ModeHighlight();
7105   }
7106
7107   /* Relay move to ICS or chess engine */
7108   if (appData.icsActive) {
7109     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7110         gameMode == IcsExamining) {
7111       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7112         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7113         SendToICS("draw ");
7114         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7115       }
7116       // also send plain move, in case ICS does not understand atomic claims
7117       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7118       ics_user_moved = 1;
7119     }
7120   } else {
7121     if (first.sendTime && (gameMode == BeginningOfGame ||
7122                            gameMode == MachinePlaysWhite ||
7123                            gameMode == MachinePlaysBlack)) {
7124       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7125     }
7126     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7127          // [HGM] book: if program might be playing, let it use book
7128         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7129         first.maybeThinking = TRUE;
7130     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7131         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7132         SendBoard(&first, currentMove+1);
7133         if(second.analyzing) {
7134             if(!second.useSetboard) SendToProgram("undo\n", &second);
7135             SendBoard(&second, currentMove+1);
7136         }
7137     } else {
7138         SendMoveToProgram(forwardMostMove-1, &first);
7139         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7140     }
7141     if (currentMove == cmailOldMove + 1) {
7142       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7143     }
7144   }
7145
7146   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7147
7148   switch (gameMode) {
7149   case EditGame:
7150     if(appData.testLegality)
7151     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7152     case MT_NONE:
7153     case MT_CHECK:
7154       break;
7155     case MT_CHECKMATE:
7156     case MT_STAINMATE:
7157       if (WhiteOnMove(currentMove)) {
7158         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7159       } else {
7160         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7161       }
7162       break;
7163     case MT_STALEMATE:
7164       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7165       break;
7166     }
7167     break;
7168
7169   case MachinePlaysBlack:
7170   case MachinePlaysWhite:
7171     /* disable certain menu options while machine is thinking */
7172     SetMachineThinkingEnables();
7173     break;
7174
7175   default:
7176     break;
7177   }
7178
7179   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7180   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7181
7182   if(bookHit) { // [HGM] book: simulate book reply
7183         static char bookMove[MSG_SIZ]; // a bit generous?
7184
7185         programStats.nodes = programStats.depth = programStats.time =
7186         programStats.score = programStats.got_only_move = 0;
7187         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7188
7189         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7190         strcat(bookMove, bookHit);
7191         HandleMachineMove(bookMove, &first);
7192   }
7193   return 1;
7194 }
7195
7196 void
7197 MarkByFEN(char *fen)
7198 {
7199         int r, f;
7200         if(!appData.markers || !appData.highlightDragging) return;
7201         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7202         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7203         while(*fen) {
7204             int s = 0;
7205             marker[r][f] = 0;
7206             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7207             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7208             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7209             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7210             if(*fen == 'T') marker[r][f++] = 0; else
7211             if(*fen == 'Y') marker[r][f++] = 1; else
7212             if(*fen == 'G') marker[r][f++] = 3; else
7213             if(*fen == 'B') marker[r][f++] = 4; else
7214             if(*fen == 'C') marker[r][f++] = 5; else
7215             if(*fen == 'M') marker[r][f++] = 6; else
7216             if(*fen == 'W') marker[r][f++] = 7; else
7217             if(*fen == 'D') marker[r][f++] = 8; else
7218             if(*fen == 'R') marker[r][f++] = 2; else {
7219                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7220               f += s; fen -= s>0;
7221             }
7222             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7223             if(r < 0) break;
7224             fen++;
7225         }
7226         DrawPosition(TRUE, NULL);
7227 }
7228
7229 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7230
7231 void
7232 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7233 {
7234     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7235     Markers *m = (Markers *) closure;
7236     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7237         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7238                          || kind == WhiteCapturesEnPassant
7239                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7240     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7241 }
7242
7243 static int hoverSavedValid;
7244
7245 void
7246 MarkTargetSquares (int clear)
7247 {
7248   int x, y, sum=0;
7249   if(clear) { // no reason to ever suppress clearing
7250     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7251     hoverSavedValid = 0;
7252     if(!sum) return; // nothing was cleared,no redraw needed
7253   } else {
7254     int capt = 0;
7255     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7256        !appData.testLegality || gameMode == EditPosition) return;
7257     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7258     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7259       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7260       if(capt)
7261       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7262     }
7263   }
7264   DrawPosition(FALSE, NULL);
7265 }
7266
7267 int
7268 Explode (Board board, int fromX, int fromY, int toX, int toY)
7269 {
7270     if(gameInfo.variant == VariantAtomic &&
7271        (board[toY][toX] != EmptySquare ||                     // capture?
7272         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7273                          board[fromY][fromX] == BlackPawn   )
7274       )) {
7275         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7276         return TRUE;
7277     }
7278     return FALSE;
7279 }
7280
7281 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7282
7283 int
7284 CanPromote (ChessSquare piece, int y)
7285 {
7286         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7287         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7288         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7289         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7290            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7291            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7292          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7293         return (piece == BlackPawn && y <= zone ||
7294                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7295                 piece == BlackLance && y == 1 ||
7296                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7297 }
7298
7299 void
7300 HoverEvent (int xPix, int yPix, int x, int y)
7301 {
7302         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7303         int r, f;
7304         if(!first.highlight) return;
7305         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7306         if(x == oldX && y == oldY) return; // only do something if we enter new square
7307         oldFromX = fromX; oldFromY = fromY;
7308         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7309           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7310             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7311           hoverSavedValid = 1;
7312         } else if(oldX != x || oldY != y) {
7313           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7314           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7315           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7316             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7317           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7318             char buf[MSG_SIZ];
7319             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7320             SendToProgram(buf, &first);
7321           }
7322           oldX = x; oldY = y;
7323 //        SetHighlights(fromX, fromY, x, y);
7324         }
7325 }
7326
7327 void ReportClick(char *action, int x, int y)
7328 {
7329         char buf[MSG_SIZ]; // Inform engine of what user does
7330         int r, f;
7331         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7332           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7333         if(!first.highlight || gameMode == EditPosition) return;
7334         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7335         SendToProgram(buf, &first);
7336 }
7337
7338 void
7339 LeftClick (ClickType clickType, int xPix, int yPix)
7340 {
7341     int x, y;
7342     Boolean saveAnimate;
7343     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7344     char promoChoice = NULLCHAR;
7345     ChessSquare piece;
7346     static TimeMark lastClickTime, prevClickTime;
7347
7348     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7349
7350     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7351
7352     if (clickType == Press) ErrorPopDown();
7353     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7354
7355     x = EventToSquare(xPix, BOARD_WIDTH);
7356     y = EventToSquare(yPix, BOARD_HEIGHT);
7357     if (!flipView && y >= 0) {
7358         y = BOARD_HEIGHT - 1 - y;
7359     }
7360     if (flipView && x >= 0) {
7361         x = BOARD_WIDTH - 1 - x;
7362     }
7363
7364     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7365         defaultPromoChoice = promoSweep;
7366         promoSweep = EmptySquare;   // terminate sweep
7367         promoDefaultAltered = TRUE;
7368         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7369     }
7370
7371     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7372         if(clickType == Release) return; // ignore upclick of click-click destination
7373         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7374         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7375         if(gameInfo.holdingsWidth &&
7376                 (WhiteOnMove(currentMove)
7377                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7378                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7379             // click in right holdings, for determining promotion piece
7380             ChessSquare p = boards[currentMove][y][x];
7381             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7382             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7383             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7384                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7385                 fromX = fromY = -1;
7386                 return;
7387             }
7388         }
7389         DrawPosition(FALSE, boards[currentMove]);
7390         return;
7391     }
7392
7393     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7394     if(clickType == Press
7395             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7396               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7397               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7398         return;
7399
7400     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7401         // could be static click on premove from-square: abort premove
7402         gotPremove = 0;
7403         ClearPremoveHighlights();
7404     }
7405
7406     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7407         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7408
7409     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7410         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7411                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7412         defaultPromoChoice = DefaultPromoChoice(side);
7413     }
7414
7415     autoQueen = appData.alwaysPromoteToQueen;
7416
7417     if (fromX == -1) {
7418       int originalY = y;
7419       gatingPiece = EmptySquare;
7420       if (clickType != Press) {
7421         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7422             DragPieceEnd(xPix, yPix); dragging = 0;
7423             DrawPosition(FALSE, NULL);
7424         }
7425         return;
7426       }
7427       doubleClick = FALSE;
7428       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7429         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7430       }
7431       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7432       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7433          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7434          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7435             /* First square */
7436             if (OKToStartUserMove(fromX, fromY)) {
7437                 second = 0;
7438                 ReportClick("lift", x, y);
7439                 MarkTargetSquares(0);
7440                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7441                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7442                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7443                     promoSweep = defaultPromoChoice;
7444                     selectFlag = 0; lastX = xPix; lastY = yPix;
7445                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7446                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7447                 }
7448                 if (appData.highlightDragging) {
7449                     SetHighlights(fromX, fromY, -1, -1);
7450                 } else {
7451                     ClearHighlights();
7452                 }
7453             } else fromX = fromY = -1;
7454             return;
7455         }
7456     }
7457
7458     /* fromX != -1 */
7459     if (clickType == Press && gameMode != EditPosition) {
7460         ChessSquare fromP;
7461         ChessSquare toP;
7462         int frc;
7463
7464         // ignore off-board to clicks
7465         if(y < 0 || x < 0) return;
7466
7467         /* Check if clicking again on the same color piece */
7468         fromP = boards[currentMove][fromY][fromX];
7469         toP = boards[currentMove][y][x];
7470         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7471         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7472            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7473              WhitePawn <= toP && toP <= WhiteKing &&
7474              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7475              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7476             (BlackPawn <= fromP && fromP <= BlackKing &&
7477              BlackPawn <= toP && toP <= BlackKing &&
7478              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7479              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7480             /* Clicked again on same color piece -- changed his mind */
7481             second = (x == fromX && y == fromY);
7482             killX = killY = -1;
7483             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7484                 second = FALSE; // first double-click rather than scond click
7485                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7486             }
7487             promoDefaultAltered = FALSE;
7488             MarkTargetSquares(1);
7489            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7490             if (appData.highlightDragging) {
7491                 SetHighlights(x, y, -1, -1);
7492             } else {
7493                 ClearHighlights();
7494             }
7495             if (OKToStartUserMove(x, y)) {
7496                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7497                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7498                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7499                  gatingPiece = boards[currentMove][fromY][fromX];
7500                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7501                 fromX = x;
7502                 fromY = y; dragging = 1;
7503                 ReportClick("lift", x, y);
7504                 MarkTargetSquares(0);
7505                 DragPieceBegin(xPix, yPix, FALSE);
7506                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7507                     promoSweep = defaultPromoChoice;
7508                     selectFlag = 0; lastX = xPix; lastY = yPix;
7509                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7510                 }
7511             }
7512            }
7513            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7514            second = FALSE;
7515         }
7516         // ignore clicks on holdings
7517         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7518     }
7519
7520     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7521         DragPieceEnd(xPix, yPix); dragging = 0;
7522         if(clearFlag) {
7523             // a deferred attempt to click-click move an empty square on top of a piece
7524             boards[currentMove][y][x] = EmptySquare;
7525             ClearHighlights();
7526             DrawPosition(FALSE, boards[currentMove]);
7527             fromX = fromY = -1; clearFlag = 0;
7528             return;
7529         }
7530         if (appData.animateDragging) {
7531             /* Undo animation damage if any */
7532             DrawPosition(FALSE, NULL);
7533         }
7534         if (second || sweepSelecting) {
7535             /* Second up/down in same square; just abort move */
7536             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7537             second = sweepSelecting = 0;
7538             fromX = fromY = -1;
7539             gatingPiece = EmptySquare;
7540             MarkTargetSquares(1);
7541             ClearHighlights();
7542             gotPremove = 0;
7543             ClearPremoveHighlights();
7544         } else {
7545             /* First upclick in same square; start click-click mode */
7546             SetHighlights(x, y, -1, -1);
7547         }
7548         return;
7549     }
7550
7551     clearFlag = 0;
7552
7553     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7554         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7555         DisplayMessage(_("only marked squares are legal"),"");
7556         DrawPosition(TRUE, NULL);
7557         return; // ignore to-click
7558     }
7559
7560     /* we now have a different from- and (possibly off-board) to-square */
7561     /* Completed move */
7562     if(!sweepSelecting) {
7563         toX = x;
7564         toY = y;
7565     }
7566
7567     piece = boards[currentMove][fromY][fromX];
7568
7569     saveAnimate = appData.animate;
7570     if (clickType == Press) {
7571         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7572         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7573             // must be Edit Position mode with empty-square selected
7574             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7575             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7576             return;
7577         }
7578         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7579             return;
7580         }
7581         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7582             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7583         } else
7584         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7585         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7586           if(appData.sweepSelect) {
7587             promoSweep = defaultPromoChoice;
7588             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7589             selectFlag = 0; lastX = xPix; lastY = yPix;
7590             Sweep(0); // Pawn that is going to promote: preview promotion piece
7591             sweepSelecting = 1;
7592             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7593             MarkTargetSquares(1);
7594           }
7595           return; // promo popup appears on up-click
7596         }
7597         /* Finish clickclick move */
7598         if (appData.animate || appData.highlightLastMove) {
7599             SetHighlights(fromX, fromY, toX, toY);
7600         } else {
7601             ClearHighlights();
7602         }
7603     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7604         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7605         if (appData.animate || appData.highlightLastMove) {
7606             SetHighlights(fromX, fromY, toX, toY);
7607         } else {
7608             ClearHighlights();
7609         }
7610     } else {
7611 #if 0
7612 // [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
7613         /* Finish drag move */
7614         if (appData.highlightLastMove) {
7615             SetHighlights(fromX, fromY, toX, toY);
7616         } else {
7617             ClearHighlights();
7618         }
7619 #endif
7620         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7621         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7622           dragging *= 2;            // flag button-less dragging if we are dragging
7623           MarkTargetSquares(1);
7624           if(x == killX && y == killY) killX = killY = -1; else {
7625             killX = x; killY = y;     //remeber this square as intermediate
7626             ReportClick("put", x, y); // and inform engine
7627             ReportClick("lift", x, y);
7628             MarkTargetSquares(0);
7629             return;
7630           }
7631         }
7632         DragPieceEnd(xPix, yPix); dragging = 0;
7633         /* Don't animate move and drag both */
7634         appData.animate = FALSE;
7635     }
7636
7637     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7638     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7639         ChessSquare piece = boards[currentMove][fromY][fromX];
7640         if(gameMode == EditPosition && piece != EmptySquare &&
7641            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7642             int n;
7643
7644             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7645                 n = PieceToNumber(piece - (int)BlackPawn);
7646                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7647                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7648                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7649             } else
7650             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7651                 n = PieceToNumber(piece);
7652                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7653                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7654                 boards[currentMove][n][BOARD_WIDTH-2]++;
7655             }
7656             boards[currentMove][fromY][fromX] = EmptySquare;
7657         }
7658         ClearHighlights();
7659         fromX = fromY = -1;
7660         MarkTargetSquares(1);
7661         DrawPosition(TRUE, boards[currentMove]);
7662         return;
7663     }
7664
7665     // off-board moves should not be highlighted
7666     if(x < 0 || y < 0) ClearHighlights();
7667     else ReportClick("put", x, y);
7668
7669     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7670
7671     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7672         SetHighlights(fromX, fromY, toX, toY);
7673         MarkTargetSquares(1);
7674         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7675             // [HGM] super: promotion to captured piece selected from holdings
7676             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7677             promotionChoice = TRUE;
7678             // kludge follows to temporarily execute move on display, without promoting yet
7679             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7680             boards[currentMove][toY][toX] = p;
7681             DrawPosition(FALSE, boards[currentMove]);
7682             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7683             boards[currentMove][toY][toX] = q;
7684             DisplayMessage("Click in holdings to choose piece", "");
7685             return;
7686         }
7687         PromotionPopUp(promoChoice);
7688     } else {
7689         int oldMove = currentMove;
7690         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7691         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7692         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7693         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7694            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7695             DrawPosition(TRUE, boards[currentMove]);
7696         MarkTargetSquares(1);
7697         fromX = fromY = -1;
7698     }
7699     appData.animate = saveAnimate;
7700     if (appData.animate || appData.animateDragging) {
7701         /* Undo animation damage if needed */
7702         DrawPosition(FALSE, NULL);
7703     }
7704 }
7705
7706 int
7707 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7708 {   // front-end-free part taken out of PieceMenuPopup
7709     int whichMenu; int xSqr, ySqr;
7710
7711     if(seekGraphUp) { // [HGM] seekgraph
7712         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7713         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7714         return -2;
7715     }
7716
7717     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7718          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7719         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7720         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7721         if(action == Press)   {
7722             originalFlip = flipView;
7723             flipView = !flipView; // temporarily flip board to see game from partners perspective
7724             DrawPosition(TRUE, partnerBoard);
7725             DisplayMessage(partnerStatus, "");
7726             partnerUp = TRUE;
7727         } else if(action == Release) {
7728             flipView = originalFlip;
7729             DrawPosition(TRUE, boards[currentMove]);
7730             partnerUp = FALSE;
7731         }
7732         return -2;
7733     }
7734
7735     xSqr = EventToSquare(x, BOARD_WIDTH);
7736     ySqr = EventToSquare(y, BOARD_HEIGHT);
7737     if (action == Release) {
7738         if(pieceSweep != EmptySquare) {
7739             EditPositionMenuEvent(pieceSweep, toX, toY);
7740             pieceSweep = EmptySquare;
7741         } else UnLoadPV(); // [HGM] pv
7742     }
7743     if (action != Press) return -2; // return code to be ignored
7744     switch (gameMode) {
7745       case IcsExamining:
7746         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7747       case EditPosition:
7748         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7749         if (xSqr < 0 || ySqr < 0) return -1;
7750         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7751         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7752         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7753         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7754         NextPiece(0);
7755         return 2; // grab
7756       case IcsObserving:
7757         if(!appData.icsEngineAnalyze) return -1;
7758       case IcsPlayingWhite:
7759       case IcsPlayingBlack:
7760         if(!appData.zippyPlay) goto noZip;
7761       case AnalyzeMode:
7762       case AnalyzeFile:
7763       case MachinePlaysWhite:
7764       case MachinePlaysBlack:
7765       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7766         if (!appData.dropMenu) {
7767           LoadPV(x, y);
7768           return 2; // flag front-end to grab mouse events
7769         }
7770         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7771            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7772       case EditGame:
7773       noZip:
7774         if (xSqr < 0 || ySqr < 0) return -1;
7775         if (!appData.dropMenu || appData.testLegality &&
7776             gameInfo.variant != VariantBughouse &&
7777             gameInfo.variant != VariantCrazyhouse) return -1;
7778         whichMenu = 1; // drop menu
7779         break;
7780       default:
7781         return -1;
7782     }
7783
7784     if (((*fromX = xSqr) < 0) ||
7785         ((*fromY = ySqr) < 0)) {
7786         *fromX = *fromY = -1;
7787         return -1;
7788     }
7789     if (flipView)
7790       *fromX = BOARD_WIDTH - 1 - *fromX;
7791     else
7792       *fromY = BOARD_HEIGHT - 1 - *fromY;
7793
7794     return whichMenu;
7795 }
7796
7797 void
7798 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7799 {
7800 //    char * hint = lastHint;
7801     FrontEndProgramStats stats;
7802
7803     stats.which = cps == &first ? 0 : 1;
7804     stats.depth = cpstats->depth;
7805     stats.nodes = cpstats->nodes;
7806     stats.score = cpstats->score;
7807     stats.time = cpstats->time;
7808     stats.pv = cpstats->movelist;
7809     stats.hint = lastHint;
7810     stats.an_move_index = 0;
7811     stats.an_move_count = 0;
7812
7813     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7814         stats.hint = cpstats->move_name;
7815         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7816         stats.an_move_count = cpstats->nr_moves;
7817     }
7818
7819     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
7820
7821     SetProgramStats( &stats );
7822 }
7823
7824 void
7825 ClearEngineOutputPane (int which)
7826 {
7827     static FrontEndProgramStats dummyStats;
7828     dummyStats.which = which;
7829     dummyStats.pv = "#";
7830     SetProgramStats( &dummyStats );
7831 }
7832
7833 #define MAXPLAYERS 500
7834
7835 char *
7836 TourneyStandings (int display)
7837 {
7838     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7839     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7840     char result, *p, *names[MAXPLAYERS];
7841
7842     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7843         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7844     names[0] = p = strdup(appData.participants);
7845     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7846
7847     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7848
7849     while(result = appData.results[nr]) {
7850         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7851         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7852         wScore = bScore = 0;
7853         switch(result) {
7854           case '+': wScore = 2; break;
7855           case '-': bScore = 2; break;
7856           case '=': wScore = bScore = 1; break;
7857           case ' ':
7858           case '*': return strdup("busy"); // tourney not finished
7859         }
7860         score[w] += wScore;
7861         score[b] += bScore;
7862         games[w]++;
7863         games[b]++;
7864         nr++;
7865     }
7866     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7867     for(w=0; w<nPlayers; w++) {
7868         bScore = -1;
7869         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7870         ranking[w] = b; points[w] = bScore; score[b] = -2;
7871     }
7872     p = malloc(nPlayers*34+1);
7873     for(w=0; w<nPlayers && w<display; w++)
7874         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7875     free(names[0]);
7876     return p;
7877 }
7878
7879 void
7880 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7881 {       // count all piece types
7882         int p, f, r;
7883         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7884         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7885         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7886                 p = board[r][f];
7887                 pCnt[p]++;
7888                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7889                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7890                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7891                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7892                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7893                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7894         }
7895 }
7896
7897 int
7898 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7899 {
7900         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7901         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7902
7903         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7904         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7905         if(myPawns == 2 && nMine == 3) // KPP
7906             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7907         if(myPawns == 1 && nMine == 2) // KP
7908             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7909         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7910             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7911         if(myPawns) return FALSE;
7912         if(pCnt[WhiteRook+side])
7913             return pCnt[BlackRook-side] ||
7914                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7915                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7916                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7917         if(pCnt[WhiteCannon+side]) {
7918             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7919             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7920         }
7921         if(pCnt[WhiteKnight+side])
7922             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7923         return FALSE;
7924 }
7925
7926 int
7927 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7928 {
7929         VariantClass v = gameInfo.variant;
7930
7931         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7932         if(v == VariantShatranj) return TRUE; // always winnable through baring
7933         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7934         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7935
7936         if(v == VariantXiangqi) {
7937                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7938
7939                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7940                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7941                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7942                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7943                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7944                 if(stale) // we have at least one last-rank P plus perhaps C
7945                     return majors // KPKX
7946                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7947                 else // KCA*E*
7948                     return pCnt[WhiteFerz+side] // KCAK
7949                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7950                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7951                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7952
7953         } else if(v == VariantKnightmate) {
7954                 if(nMine == 1) return FALSE;
7955                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7956         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7957                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7958
7959                 if(nMine == 1) return FALSE; // bare King
7960                 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
7961                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7962                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7963                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7964                 if(pCnt[WhiteKnight+side])
7965                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7966                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7967                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7968                 if(nBishops)
7969                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7970                 if(pCnt[WhiteAlfil+side])
7971                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7972                 if(pCnt[WhiteWazir+side])
7973                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7974         }
7975
7976         return TRUE;
7977 }
7978
7979 int
7980 CompareWithRights (Board b1, Board b2)
7981 {
7982     int rights = 0;
7983     if(!CompareBoards(b1, b2)) return FALSE;
7984     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7985     /* compare castling rights */
7986     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7987            rights++; /* King lost rights, while rook still had them */
7988     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7989         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7990            rights++; /* but at least one rook lost them */
7991     }
7992     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7993            rights++;
7994     if( b1[CASTLING][5] != NoRights ) {
7995         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7996            rights++;
7997     }
7998     return rights == 0;
7999 }
8000
8001 int
8002 Adjudicate (ChessProgramState *cps)
8003 {       // [HGM] some adjudications useful with buggy engines
8004         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8005         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8006         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8007         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8008         int k, drop, count = 0; static int bare = 1;
8009         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8010         Boolean canAdjudicate = !appData.icsActive;
8011
8012         // most tests only when we understand the game, i.e. legality-checking on
8013             if( appData.testLegality )
8014             {   /* [HGM] Some more adjudications for obstinate engines */
8015                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8016                 static int moveCount = 6;
8017                 ChessMove result;
8018                 char *reason = NULL;
8019
8020                 /* Count what is on board. */
8021                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8022
8023                 /* Some material-based adjudications that have to be made before stalemate test */
8024                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8025                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8026                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8027                      if(canAdjudicate && appData.checkMates) {
8028                          if(engineOpponent)
8029                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8030                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8031                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8032                          return 1;
8033                      }
8034                 }
8035
8036                 /* Bare King in Shatranj (loses) or Losers (wins) */
8037                 if( nrW == 1 || nrB == 1) {
8038                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8039                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8040                      if(canAdjudicate && appData.checkMates) {
8041                          if(engineOpponent)
8042                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8043                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8044                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8045                          return 1;
8046                      }
8047                   } else
8048                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8049                   {    /* bare King */
8050                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8051                         if(canAdjudicate && appData.checkMates) {
8052                             /* but only adjudicate if adjudication enabled */
8053                             if(engineOpponent)
8054                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8055                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8056                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8057                             return 1;
8058                         }
8059                   }
8060                 } else bare = 1;
8061
8062
8063             // don't wait for engine to announce game end if we can judge ourselves
8064             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8065               case MT_CHECK:
8066                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8067                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8068                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8069                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8070                             checkCnt++;
8071                         if(checkCnt >= 2) {
8072                             reason = "Xboard adjudication: 3rd check";
8073                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8074                             break;
8075                         }
8076                     }
8077                 }
8078               case MT_NONE:
8079               default:
8080                 break;
8081               case MT_STEALMATE:
8082               case MT_STALEMATE:
8083               case MT_STAINMATE:
8084                 reason = "Xboard adjudication: Stalemate";
8085                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8086                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8087                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8088                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8089                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8090                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8091                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8092                                                                         EP_CHECKMATE : EP_WINS);
8093                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8094                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8095                 }
8096                 break;
8097               case MT_CHECKMATE:
8098                 reason = "Xboard adjudication: Checkmate";
8099                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8100                 if(gameInfo.variant == VariantShogi) {
8101                     if(forwardMostMove > backwardMostMove
8102                        && moveList[forwardMostMove-1][1] == '@'
8103                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8104                         reason = "XBoard adjudication: pawn-drop mate";
8105                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8106                     }
8107                 }
8108                 break;
8109             }
8110
8111                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8112                     case EP_STALEMATE:
8113                         result = GameIsDrawn; break;
8114                     case EP_CHECKMATE:
8115                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8116                     case EP_WINS:
8117                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8118                     default:
8119                         result = EndOfFile;
8120                 }
8121                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8122                     if(engineOpponent)
8123                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8124                     GameEnds( result, reason, GE_XBOARD );
8125                     return 1;
8126                 }
8127
8128                 /* Next absolutely insufficient mating material. */
8129                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8130                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8131                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8132
8133                      /* always flag draws, for judging claims */
8134                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8135
8136                      if(canAdjudicate && appData.materialDraws) {
8137                          /* but only adjudicate them if adjudication enabled */
8138                          if(engineOpponent) {
8139                            SendToProgram("force\n", engineOpponent); // suppress reply
8140                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8141                          }
8142                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8143                          return 1;
8144                      }
8145                 }
8146
8147                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8148                 if(gameInfo.variant == VariantXiangqi ?
8149                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8150                  : nrW + nrB == 4 &&
8151                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8152                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8153                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8154                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8155                    ) ) {
8156                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8157                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8158                           if(engineOpponent) {
8159                             SendToProgram("force\n", engineOpponent); // suppress reply
8160                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8161                           }
8162                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8163                           return 1;
8164                      }
8165                 } else moveCount = 6;
8166             }
8167
8168         // Repetition draws and 50-move rule can be applied independently of legality testing
8169
8170                 /* Check for rep-draws */
8171                 count = 0;
8172                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8173                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8174                 for(k = forwardMostMove-2;
8175                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8176                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8177                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8178                     k-=2)
8179                 {   int rights=0;
8180                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8181                         /* compare castling rights */
8182                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8183                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8184                                 rights++; /* King lost rights, while rook still had them */
8185                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8186                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8187                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8188                                    rights++; /* but at least one rook lost them */
8189                         }
8190                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8191                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8192                                 rights++;
8193                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8194                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8195                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8196                                    rights++;
8197                         }
8198                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8199                             && appData.drawRepeats > 1) {
8200                              /* adjudicate after user-specified nr of repeats */
8201                              int result = GameIsDrawn;
8202                              char *details = "XBoard adjudication: repetition draw";
8203                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8204                                 // [HGM] xiangqi: check for forbidden perpetuals
8205                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8206                                 for(m=forwardMostMove; m>k; m-=2) {
8207                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8208                                         ourPerpetual = 0; // the current mover did not always check
8209                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8210                                         hisPerpetual = 0; // the opponent did not always check
8211                                 }
8212                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8213                                                                         ourPerpetual, hisPerpetual);
8214                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8215                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8216                                     details = "Xboard adjudication: perpetual checking";
8217                                 } else
8218                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8219                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8220                                 } else
8221                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8222                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8223                                         result = BlackWins;
8224                                         details = "Xboard adjudication: repetition";
8225                                     }
8226                                 } else // it must be XQ
8227                                 // Now check for perpetual chases
8228                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8229                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8230                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8231                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8232                                         static char resdet[MSG_SIZ];
8233                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8234                                         details = resdet;
8235                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8236                                     } else
8237                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8238                                         break; // Abort repetition-checking loop.
8239                                 }
8240                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8241                              }
8242                              if(engineOpponent) {
8243                                SendToProgram("force\n", engineOpponent); // suppress reply
8244                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8245                              }
8246                              GameEnds( result, details, GE_XBOARD );
8247                              return 1;
8248                         }
8249                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8250                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8251                     }
8252                 }
8253
8254                 /* Now we test for 50-move draws. Determine ply count */
8255                 count = forwardMostMove;
8256                 /* look for last irreversble move */
8257                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8258                     count--;
8259                 /* if we hit starting position, add initial plies */
8260                 if( count == backwardMostMove )
8261                     count -= initialRulePlies;
8262                 count = forwardMostMove - count;
8263                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8264                         // adjust reversible move counter for checks in Xiangqi
8265                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8266                         if(i < backwardMostMove) i = backwardMostMove;
8267                         while(i <= forwardMostMove) {
8268                                 lastCheck = inCheck; // check evasion does not count
8269                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8270                                 if(inCheck || lastCheck) count--; // check does not count
8271                                 i++;
8272                         }
8273                 }
8274                 if( count >= 100)
8275                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8276                          /* this is used to judge if draw claims are legal */
8277                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8278                          if(engineOpponent) {
8279                            SendToProgram("force\n", engineOpponent); // suppress reply
8280                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8281                          }
8282                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8283                          return 1;
8284                 }
8285
8286                 /* if draw offer is pending, treat it as a draw claim
8287                  * when draw condition present, to allow engines a way to
8288                  * claim draws before making their move to avoid a race
8289                  * condition occurring after their move
8290                  */
8291                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8292                          char *p = NULL;
8293                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8294                              p = "Draw claim: 50-move rule";
8295                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8296                              p = "Draw claim: 3-fold repetition";
8297                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8298                              p = "Draw claim: insufficient mating material";
8299                          if( p != NULL && canAdjudicate) {
8300                              if(engineOpponent) {
8301                                SendToProgram("force\n", engineOpponent); // suppress reply
8302                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8303                              }
8304                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8305                              return 1;
8306                          }
8307                 }
8308
8309                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8310                     if(engineOpponent) {
8311                       SendToProgram("force\n", engineOpponent); // suppress reply
8312                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8313                     }
8314                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8315                     return 1;
8316                 }
8317         return 0;
8318 }
8319
8320 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8321 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8322 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8323
8324 static int
8325 BitbaseProbe ()
8326 {
8327     int pieces[10], squares[10], cnt=0, r, f, res;
8328     static int loaded;
8329     static PPROBE_EGBB probeBB;
8330     if(!appData.testLegality) return 10;
8331     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8332     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8333     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8334     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8335         ChessSquare piece = boards[forwardMostMove][r][f];
8336         int black = (piece >= BlackPawn);
8337         int type = piece - black*BlackPawn;
8338         if(piece == EmptySquare) continue;
8339         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8340         if(type == WhiteKing) type = WhiteQueen + 1;
8341         type = egbbCode[type];
8342         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8343         pieces[cnt] = type + black*6;
8344         if(++cnt > 5) return 11;
8345     }
8346     pieces[cnt] = squares[cnt] = 0;
8347     // probe EGBB
8348     if(loaded == 2) return 13; // loading failed before
8349     if(loaded == 0) {
8350         loaded = 2; // prepare for failure
8351         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8352         HMODULE lib;
8353         PLOAD_EGBB loadBB;
8354         if(!path) return 13; // no egbb installed
8355         strncpy(buf, path + 8, MSG_SIZ);
8356         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8357         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8358         lib = LoadLibrary(buf);
8359         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8360         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8361         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8362         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8363         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8364         loaded = 1; // success!
8365     }
8366     res = probeBB(forwardMostMove & 1, pieces, squares);
8367     return res > 0 ? 1 : res < 0 ? -1 : 0;
8368 }
8369
8370 char *
8371 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8372 {   // [HGM] book: this routine intercepts moves to simulate book replies
8373     char *bookHit = NULL;
8374
8375     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8376         char buf[MSG_SIZ];
8377         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8378         SendToProgram(buf, cps);
8379     }
8380     //first determine if the incoming move brings opponent into his book
8381     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8382         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8383     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8384     if(bookHit != NULL && !cps->bookSuspend) {
8385         // make sure opponent is not going to reply after receiving move to book position
8386         SendToProgram("force\n", cps);
8387         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8388     }
8389     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8390     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8391     // now arrange restart after book miss
8392     if(bookHit) {
8393         // after a book hit we never send 'go', and the code after the call to this routine
8394         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8395         char buf[MSG_SIZ], *move = bookHit;
8396         if(cps->useSAN) {
8397             int fromX, fromY, toX, toY;
8398             char promoChar;
8399             ChessMove moveType;
8400             move = buf + 30;
8401             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8402                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8403                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8404                                     PosFlags(forwardMostMove),
8405                                     fromY, fromX, toY, toX, promoChar, move);
8406             } else {
8407                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8408                 bookHit = NULL;
8409             }
8410         }
8411         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8412         SendToProgram(buf, cps);
8413         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8414     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8415         SendToProgram("go\n", cps);
8416         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8417     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8418         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8419             SendToProgram("go\n", cps);
8420         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8421     }
8422     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8423 }
8424
8425 int
8426 LoadError (char *errmess, ChessProgramState *cps)
8427 {   // unloads engine and switches back to -ncp mode if it was first
8428     if(cps->initDone) return FALSE;
8429     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8430     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8431     cps->pr = NoProc;
8432     if(cps == &first) {
8433         appData.noChessProgram = TRUE;
8434         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8435         gameMode = BeginningOfGame; ModeHighlight();
8436         SetNCPMode();
8437     }
8438     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8439     DisplayMessage("", ""); // erase waiting message
8440     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8441     return TRUE;
8442 }
8443
8444 char *savedMessage;
8445 ChessProgramState *savedState;
8446 void
8447 DeferredBookMove (void)
8448 {
8449         if(savedState->lastPing != savedState->lastPong)
8450                     ScheduleDelayedEvent(DeferredBookMove, 10);
8451         else
8452         HandleMachineMove(savedMessage, savedState);
8453 }
8454
8455 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8456 static ChessProgramState *stalledEngine;
8457 static char stashedInputMove[MSG_SIZ];
8458
8459 void
8460 HandleMachineMove (char *message, ChessProgramState *cps)
8461 {
8462     static char firstLeg[20];
8463     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8464     char realname[MSG_SIZ];
8465     int fromX, fromY, toX, toY;
8466     ChessMove moveType;
8467     char promoChar, roar;
8468     char *p, *pv=buf1;
8469     int machineWhite, oldError;
8470     char *bookHit;
8471
8472     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8473         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8474         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8475             DisplayError(_("Invalid pairing from pairing engine"), 0);
8476             return;
8477         }
8478         pairingReceived = 1;
8479         NextMatchGame();
8480         return; // Skim the pairing messages here.
8481     }
8482
8483     oldError = cps->userError; cps->userError = 0;
8484
8485 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8486     /*
8487      * Kludge to ignore BEL characters
8488      */
8489     while (*message == '\007') message++;
8490
8491     /*
8492      * [HGM] engine debug message: ignore lines starting with '#' character
8493      */
8494     if(cps->debug && *message == '#') return;
8495
8496     /*
8497      * Look for book output
8498      */
8499     if (cps == &first && bookRequested) {
8500         if (message[0] == '\t' || message[0] == ' ') {
8501             /* Part of the book output is here; append it */
8502             strcat(bookOutput, message);
8503             strcat(bookOutput, "  \n");
8504             return;
8505         } else if (bookOutput[0] != NULLCHAR) {
8506             /* All of book output has arrived; display it */
8507             char *p = bookOutput;
8508             while (*p != NULLCHAR) {
8509                 if (*p == '\t') *p = ' ';
8510                 p++;
8511             }
8512             DisplayInformation(bookOutput);
8513             bookRequested = FALSE;
8514             /* Fall through to parse the current output */
8515         }
8516     }
8517
8518     /*
8519      * Look for machine move.
8520      */
8521     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8522         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8523     {
8524         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8525             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8526             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8527             stalledEngine = cps;
8528             if(appData.ponderNextMove) { // bring opponent out of ponder
8529                 if(gameMode == TwoMachinesPlay) {
8530                     if(cps->other->pause)
8531                         PauseEngine(cps->other);
8532                     else
8533                         SendToProgram("easy\n", cps->other);
8534                 }
8535             }
8536             StopClocks();
8537             return;
8538         }
8539
8540         /* This method is only useful on engines that support ping */
8541         if (cps->lastPing != cps->lastPong) {
8542           if (gameMode == BeginningOfGame) {
8543             /* Extra move from before last new; ignore */
8544             if (appData.debugMode) {
8545                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8546             }
8547           } else {
8548             if (appData.debugMode) {
8549                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8550                         cps->which, gameMode);
8551             }
8552
8553             SendToProgram("undo\n", cps);
8554           }
8555           return;
8556         }
8557
8558         switch (gameMode) {
8559           case BeginningOfGame:
8560             /* Extra move from before last reset; ignore */
8561             if (appData.debugMode) {
8562                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8563             }
8564             return;
8565
8566           case EndOfGame:
8567           case IcsIdle:
8568           default:
8569             /* Extra move after we tried to stop.  The mode test is
8570                not a reliable way of detecting this problem, but it's
8571                the best we can do on engines that don't support ping.
8572             */
8573             if (appData.debugMode) {
8574                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8575                         cps->which, gameMode);
8576             }
8577             SendToProgram("undo\n", cps);
8578             return;
8579
8580           case MachinePlaysWhite:
8581           case IcsPlayingWhite:
8582             machineWhite = TRUE;
8583             break;
8584
8585           case MachinePlaysBlack:
8586           case IcsPlayingBlack:
8587             machineWhite = FALSE;
8588             break;
8589
8590           case TwoMachinesPlay:
8591             machineWhite = (cps->twoMachinesColor[0] == 'w');
8592             break;
8593         }
8594         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8595             if (appData.debugMode) {
8596                 fprintf(debugFP,
8597                         "Ignoring move out of turn by %s, gameMode %d"
8598                         ", forwardMost %d\n",
8599                         cps->which, gameMode, forwardMostMove);
8600             }
8601             return;
8602         }
8603
8604         if(cps->alphaRank) AlphaRank(machineMove, 4);
8605
8606         // [HGM] lion: (some very limited) support for Alien protocol
8607         killX = killY = -1;
8608         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8609             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8610             return;
8611         } else if(firstLeg[0]) { // there was a previous leg;
8612             // only support case where same piece makes two step (and don't even test that!)
8613             char buf[20], *p = machineMove+1, *q = buf+1, f;
8614             safeStrCpy(buf, machineMove, 20);
8615             while(isdigit(*q)) q++; // find start of to-square
8616             safeStrCpy(machineMove, firstLeg, 20);
8617             while(isdigit(*p)) p++;
8618             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8619             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8620             firstLeg[0] = NULLCHAR;
8621         }
8622
8623         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8624                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8625             /* Machine move could not be parsed; ignore it. */
8626           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8627                     machineMove, _(cps->which));
8628             DisplayMoveError(buf1);
8629             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8630                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8631             if (gameMode == TwoMachinesPlay) {
8632               GameEnds(machineWhite ? BlackWins : WhiteWins,
8633                        buf1, GE_XBOARD);
8634             }
8635             return;
8636         }
8637
8638         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8639         /* So we have to redo legality test with true e.p. status here,  */
8640         /* to make sure an illegal e.p. capture does not slip through,   */
8641         /* to cause a forfeit on a justified illegal-move complaint      */
8642         /* of the opponent.                                              */
8643         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8644            ChessMove moveType;
8645            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8646                              fromY, fromX, toY, toX, promoChar);
8647             if(moveType == IllegalMove) {
8648               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8649                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8650                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8651                            buf1, GE_XBOARD);
8652                 return;
8653            } else if(!appData.fischerCastling)
8654            /* [HGM] Kludge to handle engines that send FRC-style castling
8655               when they shouldn't (like TSCP-Gothic) */
8656            switch(moveType) {
8657              case WhiteASideCastleFR:
8658              case BlackASideCastleFR:
8659                toX+=2;
8660                currentMoveString[2]++;
8661                break;
8662              case WhiteHSideCastleFR:
8663              case BlackHSideCastleFR:
8664                toX--;
8665                currentMoveString[2]--;
8666                break;
8667              default: ; // nothing to do, but suppresses warning of pedantic compilers
8668            }
8669         }
8670         hintRequested = FALSE;
8671         lastHint[0] = NULLCHAR;
8672         bookRequested = FALSE;
8673         /* Program may be pondering now */
8674         cps->maybeThinking = TRUE;
8675         if (cps->sendTime == 2) cps->sendTime = 1;
8676         if (cps->offeredDraw) cps->offeredDraw--;
8677
8678         /* [AS] Save move info*/
8679         pvInfoList[ forwardMostMove ].score = programStats.score;
8680         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8681         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8682
8683         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8684
8685         /* Test suites abort the 'game' after one move */
8686         if(*appData.finger) {
8687            static FILE *f;
8688            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8689            if(!f) f = fopen(appData.finger, "w");
8690            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8691            else { DisplayFatalError("Bad output file", errno, 0); return; }
8692            free(fen);
8693            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8694         }
8695
8696         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8697         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8698             int count = 0;
8699
8700             while( count < adjudicateLossPlies ) {
8701                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8702
8703                 if( count & 1 ) {
8704                     score = -score; /* Flip score for winning side */
8705                 }
8706
8707                 if( score > adjudicateLossThreshold ) {
8708                     break;
8709                 }
8710
8711                 count++;
8712             }
8713
8714             if( count >= adjudicateLossPlies ) {
8715                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8716
8717                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8718                     "Xboard adjudication",
8719                     GE_XBOARD );
8720
8721                 return;
8722             }
8723         }
8724
8725         if(Adjudicate(cps)) {
8726             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8727             return; // [HGM] adjudicate: for all automatic game ends
8728         }
8729
8730 #if ZIPPY
8731         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8732             first.initDone) {
8733           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8734                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8735                 SendToICS("draw ");
8736                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8737           }
8738           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8739           ics_user_moved = 1;
8740           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8741                 char buf[3*MSG_SIZ];
8742
8743                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8744                         programStats.score / 100.,
8745                         programStats.depth,
8746                         programStats.time / 100.,
8747                         (unsigned int)programStats.nodes,
8748                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8749                         programStats.movelist);
8750                 SendToICS(buf);
8751 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8752           }
8753         }
8754 #endif
8755
8756         /* [AS] Clear stats for next move */
8757         ClearProgramStats();
8758         thinkOutput[0] = NULLCHAR;
8759         hiddenThinkOutputState = 0;
8760
8761         bookHit = NULL;
8762         if (gameMode == TwoMachinesPlay) {
8763             /* [HGM] relaying draw offers moved to after reception of move */
8764             /* and interpreting offer as claim if it brings draw condition */
8765             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8766                 SendToProgram("draw\n", cps->other);
8767             }
8768             if (cps->other->sendTime) {
8769                 SendTimeRemaining(cps->other,
8770                                   cps->other->twoMachinesColor[0] == 'w');
8771             }
8772             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8773             if (firstMove && !bookHit) {
8774                 firstMove = FALSE;
8775                 if (cps->other->useColors) {
8776                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8777                 }
8778                 SendToProgram("go\n", cps->other);
8779             }
8780             cps->other->maybeThinking = TRUE;
8781         }
8782
8783         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8784
8785         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8786
8787         if (!pausing && appData.ringBellAfterMoves) {
8788             if(!roar) RingBell();
8789         }
8790
8791         /*
8792          * Reenable menu items that were disabled while
8793          * machine was thinking
8794          */
8795         if (gameMode != TwoMachinesPlay)
8796             SetUserThinkingEnables();
8797
8798         // [HGM] book: after book hit opponent has received move and is now in force mode
8799         // force the book reply into it, and then fake that it outputted this move by jumping
8800         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8801         if(bookHit) {
8802                 static char bookMove[MSG_SIZ]; // a bit generous?
8803
8804                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8805                 strcat(bookMove, bookHit);
8806                 message = bookMove;
8807                 cps = cps->other;
8808                 programStats.nodes = programStats.depth = programStats.time =
8809                 programStats.score = programStats.got_only_move = 0;
8810                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8811
8812                 if(cps->lastPing != cps->lastPong) {
8813                     savedMessage = message; // args for deferred call
8814                     savedState = cps;
8815                     ScheduleDelayedEvent(DeferredBookMove, 10);
8816                     return;
8817                 }
8818                 goto FakeBookMove;
8819         }
8820
8821         return;
8822     }
8823
8824     /* Set special modes for chess engines.  Later something general
8825      *  could be added here; for now there is just one kludge feature,
8826      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8827      *  when "xboard" is given as an interactive command.
8828      */
8829     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8830         cps->useSigint = FALSE;
8831         cps->useSigterm = FALSE;
8832     }
8833     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8834       ParseFeatures(message+8, cps);
8835       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8836     }
8837
8838     if (!strncmp(message, "setup ", 6) && 
8839         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8840           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8841                                         ) { // [HGM] allow first engine to define opening position
8842       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8843       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8844       *buf = NULLCHAR;
8845       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8846       if(startedFromSetupPosition) return;
8847       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8848       if(dummy >= 3) {
8849         while(message[s] && message[s++] != ' ');
8850         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8851            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8852             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8853             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8854           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8855           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8856         }
8857       }
8858       ParseFEN(boards[0], &dummy, message+s, FALSE);
8859       DrawPosition(TRUE, boards[0]);
8860       startedFromSetupPosition = TRUE;
8861       return;
8862     }
8863     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8864      * want this, I was asked to put it in, and obliged.
8865      */
8866     if (!strncmp(message, "setboard ", 9)) {
8867         Board initial_position;
8868
8869         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8870
8871         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8872             DisplayError(_("Bad FEN received from engine"), 0);
8873             return ;
8874         } else {
8875            Reset(TRUE, FALSE);
8876            CopyBoard(boards[0], initial_position);
8877            initialRulePlies = FENrulePlies;
8878            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8879            else gameMode = MachinePlaysBlack;
8880            DrawPosition(FALSE, boards[currentMove]);
8881         }
8882         return;
8883     }
8884
8885     /*
8886      * Look for communication commands
8887      */
8888     if (!strncmp(message, "telluser ", 9)) {
8889         if(message[9] == '\\' && message[10] == '\\')
8890             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8891         PlayTellSound();
8892         DisplayNote(message + 9);
8893         return;
8894     }
8895     if (!strncmp(message, "tellusererror ", 14)) {
8896         cps->userError = 1;
8897         if(message[14] == '\\' && message[15] == '\\')
8898             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8899         PlayTellSound();
8900         DisplayError(message + 14, 0);
8901         return;
8902     }
8903     if (!strncmp(message, "tellopponent ", 13)) {
8904       if (appData.icsActive) {
8905         if (loggedOn) {
8906           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8907           SendToICS(buf1);
8908         }
8909       } else {
8910         DisplayNote(message + 13);
8911       }
8912       return;
8913     }
8914     if (!strncmp(message, "tellothers ", 11)) {
8915       if (appData.icsActive) {
8916         if (loggedOn) {
8917           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8918           SendToICS(buf1);
8919         }
8920       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8921       return;
8922     }
8923     if (!strncmp(message, "tellall ", 8)) {
8924       if (appData.icsActive) {
8925         if (loggedOn) {
8926           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8927           SendToICS(buf1);
8928         }
8929       } else {
8930         DisplayNote(message + 8);
8931       }
8932       return;
8933     }
8934     if (strncmp(message, "warning", 7) == 0) {
8935         /* Undocumented feature, use tellusererror in new code */
8936         DisplayError(message, 0);
8937         return;
8938     }
8939     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8940         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8941         strcat(realname, " query");
8942         AskQuestion(realname, buf2, buf1, cps->pr);
8943         return;
8944     }
8945     /* Commands from the engine directly to ICS.  We don't allow these to be
8946      *  sent until we are logged on. Crafty kibitzes have been known to
8947      *  interfere with the login process.
8948      */
8949     if (loggedOn) {
8950         if (!strncmp(message, "tellics ", 8)) {
8951             SendToICS(message + 8);
8952             SendToICS("\n");
8953             return;
8954         }
8955         if (!strncmp(message, "tellicsnoalias ", 15)) {
8956             SendToICS(ics_prefix);
8957             SendToICS(message + 15);
8958             SendToICS("\n");
8959             return;
8960         }
8961         /* The following are for backward compatibility only */
8962         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8963             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8964             SendToICS(ics_prefix);
8965             SendToICS(message);
8966             SendToICS("\n");
8967             return;
8968         }
8969     }
8970     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8971         if(initPing == cps->lastPong) {
8972             if(gameInfo.variant == VariantUnknown) {
8973                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8974                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8975                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8976             }
8977             initPing = -1;
8978         }
8979         return;
8980     }
8981     if(!strncmp(message, "highlight ", 10)) {
8982         if(appData.testLegality && appData.markers) return;
8983         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8984         return;
8985     }
8986     if(!strncmp(message, "click ", 6)) {
8987         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8988         if(appData.testLegality || !appData.oneClick) return;
8989         sscanf(message+6, "%c%d%c", &f, &y, &c);
8990         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8991         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8992         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8993         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8994         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8995         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8996             LeftClick(Release, lastLeftX, lastLeftY);
8997         controlKey  = (c == ',');
8998         LeftClick(Press, x, y);
8999         LeftClick(Release, x, y);
9000         first.highlight = f;
9001         return;
9002     }
9003     /*
9004      * If the move is illegal, cancel it and redraw the board.
9005      * Also deal with other error cases.  Matching is rather loose
9006      * here to accommodate engines written before the spec.
9007      */
9008     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9009         strncmp(message, "Error", 5) == 0) {
9010         if (StrStr(message, "name") ||
9011             StrStr(message, "rating") || StrStr(message, "?") ||
9012             StrStr(message, "result") || StrStr(message, "board") ||
9013             StrStr(message, "bk") || StrStr(message, "computer") ||
9014             StrStr(message, "variant") || StrStr(message, "hint") ||
9015             StrStr(message, "random") || StrStr(message, "depth") ||
9016             StrStr(message, "accepted")) {
9017             return;
9018         }
9019         if (StrStr(message, "protover")) {
9020           /* Program is responding to input, so it's apparently done
9021              initializing, and this error message indicates it is
9022              protocol version 1.  So we don't need to wait any longer
9023              for it to initialize and send feature commands. */
9024           FeatureDone(cps, 1);
9025           cps->protocolVersion = 1;
9026           return;
9027         }
9028         cps->maybeThinking = FALSE;
9029
9030         if (StrStr(message, "draw")) {
9031             /* Program doesn't have "draw" command */
9032             cps->sendDrawOffers = 0;
9033             return;
9034         }
9035         if (cps->sendTime != 1 &&
9036             (StrStr(message, "time") || StrStr(message, "otim"))) {
9037           /* Program apparently doesn't have "time" or "otim" command */
9038           cps->sendTime = 0;
9039           return;
9040         }
9041         if (StrStr(message, "analyze")) {
9042             cps->analysisSupport = FALSE;
9043             cps->analyzing = FALSE;
9044 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9045             EditGameEvent(); // [HGM] try to preserve loaded game
9046             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9047             DisplayError(buf2, 0);
9048             return;
9049         }
9050         if (StrStr(message, "(no matching move)st")) {
9051           /* Special kludge for GNU Chess 4 only */
9052           cps->stKludge = TRUE;
9053           SendTimeControl(cps, movesPerSession, timeControl,
9054                           timeIncrement, appData.searchDepth,
9055                           searchTime);
9056           return;
9057         }
9058         if (StrStr(message, "(no matching move)sd")) {
9059           /* Special kludge for GNU Chess 4 only */
9060           cps->sdKludge = TRUE;
9061           SendTimeControl(cps, movesPerSession, timeControl,
9062                           timeIncrement, appData.searchDepth,
9063                           searchTime);
9064           return;
9065         }
9066         if (!StrStr(message, "llegal")) {
9067             return;
9068         }
9069         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9070             gameMode == IcsIdle) return;
9071         if (forwardMostMove <= backwardMostMove) return;
9072         if (pausing) PauseEvent();
9073       if(appData.forceIllegal) {
9074             // [HGM] illegal: machine refused move; force position after move into it
9075           SendToProgram("force\n", cps);
9076           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9077                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9078                 // when black is to move, while there might be nothing on a2 or black
9079                 // might already have the move. So send the board as if white has the move.
9080                 // But first we must change the stm of the engine, as it refused the last move
9081                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9082                 if(WhiteOnMove(forwardMostMove)) {
9083                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9084                     SendBoard(cps, forwardMostMove); // kludgeless board
9085                 } else {
9086                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9087                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9088                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9089                 }
9090           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9091             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9092                  gameMode == TwoMachinesPlay)
9093               SendToProgram("go\n", cps);
9094             return;
9095       } else
9096         if (gameMode == PlayFromGameFile) {
9097             /* Stop reading this game file */
9098             gameMode = EditGame;
9099             ModeHighlight();
9100         }
9101         /* [HGM] illegal-move claim should forfeit game when Xboard */
9102         /* only passes fully legal moves                            */
9103         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9104             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9105                                 "False illegal-move claim", GE_XBOARD );
9106             return; // do not take back move we tested as valid
9107         }
9108         currentMove = forwardMostMove-1;
9109         DisplayMove(currentMove-1); /* before DisplayMoveError */
9110         SwitchClocks(forwardMostMove-1); // [HGM] race
9111         DisplayBothClocks();
9112         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9113                 parseList[currentMove], _(cps->which));
9114         DisplayMoveError(buf1);
9115         DrawPosition(FALSE, boards[currentMove]);
9116
9117         SetUserThinkingEnables();
9118         return;
9119     }
9120     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9121         /* Program has a broken "time" command that
9122            outputs a string not ending in newline.
9123            Don't use it. */
9124         cps->sendTime = 0;
9125     }
9126
9127     /*
9128      * If chess program startup fails, exit with an error message.
9129      * Attempts to recover here are futile. [HGM] Well, we try anyway
9130      */
9131     if ((StrStr(message, "unknown host") != NULL)
9132         || (StrStr(message, "No remote directory") != NULL)
9133         || (StrStr(message, "not found") != NULL)
9134         || (StrStr(message, "No such file") != NULL)
9135         || (StrStr(message, "can't alloc") != NULL)
9136         || (StrStr(message, "Permission denied") != NULL)) {
9137
9138         cps->maybeThinking = FALSE;
9139         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9140                 _(cps->which), cps->program, cps->host, message);
9141         RemoveInputSource(cps->isr);
9142         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9143             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9144             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9145         }
9146         return;
9147     }
9148
9149     /*
9150      * Look for hint output
9151      */
9152     if (sscanf(message, "Hint: %s", buf1) == 1) {
9153         if (cps == &first && hintRequested) {
9154             hintRequested = FALSE;
9155             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9156                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9157                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9158                                     PosFlags(forwardMostMove),
9159                                     fromY, fromX, toY, toX, promoChar, buf1);
9160                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9161                 DisplayInformation(buf2);
9162             } else {
9163                 /* Hint move could not be parsed!? */
9164               snprintf(buf2, sizeof(buf2),
9165                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9166                         buf1, _(cps->which));
9167                 DisplayError(buf2, 0);
9168             }
9169         } else {
9170           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9171         }
9172         return;
9173     }
9174
9175     /*
9176      * Ignore other messages if game is not in progress
9177      */
9178     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9179         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9180
9181     /*
9182      * look for win, lose, draw, or draw offer
9183      */
9184     if (strncmp(message, "1-0", 3) == 0) {
9185         char *p, *q, *r = "";
9186         p = strchr(message, '{');
9187         if (p) {
9188             q = strchr(p, '}');
9189             if (q) {
9190                 *q = NULLCHAR;
9191                 r = p + 1;
9192             }
9193         }
9194         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9195         return;
9196     } else if (strncmp(message, "0-1", 3) == 0) {
9197         char *p, *q, *r = "";
9198         p = strchr(message, '{');
9199         if (p) {
9200             q = strchr(p, '}');
9201             if (q) {
9202                 *q = NULLCHAR;
9203                 r = p + 1;
9204             }
9205         }
9206         /* Kludge for Arasan 4.1 bug */
9207         if (strcmp(r, "Black resigns") == 0) {
9208             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9209             return;
9210         }
9211         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9212         return;
9213     } else if (strncmp(message, "1/2", 3) == 0) {
9214         char *p, *q, *r = "";
9215         p = strchr(message, '{');
9216         if (p) {
9217             q = strchr(p, '}');
9218             if (q) {
9219                 *q = NULLCHAR;
9220                 r = p + 1;
9221             }
9222         }
9223
9224         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9225         return;
9226
9227     } else if (strncmp(message, "White resign", 12) == 0) {
9228         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9229         return;
9230     } else if (strncmp(message, "Black resign", 12) == 0) {
9231         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9232         return;
9233     } else if (strncmp(message, "White matches", 13) == 0 ||
9234                strncmp(message, "Black matches", 13) == 0   ) {
9235         /* [HGM] ignore GNUShogi noises */
9236         return;
9237     } else if (strncmp(message, "White", 5) == 0 &&
9238                message[5] != '(' &&
9239                StrStr(message, "Black") == NULL) {
9240         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9241         return;
9242     } else if (strncmp(message, "Black", 5) == 0 &&
9243                message[5] != '(') {
9244         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9245         return;
9246     } else if (strcmp(message, "resign") == 0 ||
9247                strcmp(message, "computer resigns") == 0) {
9248         switch (gameMode) {
9249           case MachinePlaysBlack:
9250           case IcsPlayingBlack:
9251             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9252             break;
9253           case MachinePlaysWhite:
9254           case IcsPlayingWhite:
9255             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9256             break;
9257           case TwoMachinesPlay:
9258             if (cps->twoMachinesColor[0] == 'w')
9259               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9260             else
9261               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9262             break;
9263           default:
9264             /* can't happen */
9265             break;
9266         }
9267         return;
9268     } else if (strncmp(message, "opponent mates", 14) == 0) {
9269         switch (gameMode) {
9270           case MachinePlaysBlack:
9271           case IcsPlayingBlack:
9272             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9273             break;
9274           case MachinePlaysWhite:
9275           case IcsPlayingWhite:
9276             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9277             break;
9278           case TwoMachinesPlay:
9279             if (cps->twoMachinesColor[0] == 'w')
9280               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9281             else
9282               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9283             break;
9284           default:
9285             /* can't happen */
9286             break;
9287         }
9288         return;
9289     } else if (strncmp(message, "computer mates", 14) == 0) {
9290         switch (gameMode) {
9291           case MachinePlaysBlack:
9292           case IcsPlayingBlack:
9293             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9294             break;
9295           case MachinePlaysWhite:
9296           case IcsPlayingWhite:
9297             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9298             break;
9299           case TwoMachinesPlay:
9300             if (cps->twoMachinesColor[0] == 'w')
9301               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9302             else
9303               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9304             break;
9305           default:
9306             /* can't happen */
9307             break;
9308         }
9309         return;
9310     } else if (strncmp(message, "checkmate", 9) == 0) {
9311         if (WhiteOnMove(forwardMostMove)) {
9312             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9313         } else {
9314             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9315         }
9316         return;
9317     } else if (strstr(message, "Draw") != NULL ||
9318                strstr(message, "game is a draw") != NULL) {
9319         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9320         return;
9321     } else if (strstr(message, "offer") != NULL &&
9322                strstr(message, "draw") != NULL) {
9323 #if ZIPPY
9324         if (appData.zippyPlay && first.initDone) {
9325             /* Relay offer to ICS */
9326             SendToICS(ics_prefix);
9327             SendToICS("draw\n");
9328         }
9329 #endif
9330         cps->offeredDraw = 2; /* valid until this engine moves twice */
9331         if (gameMode == TwoMachinesPlay) {
9332             if (cps->other->offeredDraw) {
9333                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9334             /* [HGM] in two-machine mode we delay relaying draw offer      */
9335             /* until after we also have move, to see if it is really claim */
9336             }
9337         } else if (gameMode == MachinePlaysWhite ||
9338                    gameMode == MachinePlaysBlack) {
9339           if (userOfferedDraw) {
9340             DisplayInformation(_("Machine accepts your draw offer"));
9341             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9342           } else {
9343             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9344           }
9345         }
9346     }
9347
9348
9349     /*
9350      * Look for thinking output
9351      */
9352     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9353           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9354                                 ) {
9355         int plylev, mvleft, mvtot, curscore, time;
9356         char mvname[MOVE_LEN];
9357         u64 nodes; // [DM]
9358         char plyext;
9359         int ignore = FALSE;
9360         int prefixHint = FALSE;
9361         mvname[0] = NULLCHAR;
9362
9363         switch (gameMode) {
9364           case MachinePlaysBlack:
9365           case IcsPlayingBlack:
9366             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9367             break;
9368           case MachinePlaysWhite:
9369           case IcsPlayingWhite:
9370             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9371             break;
9372           case AnalyzeMode:
9373           case AnalyzeFile:
9374             break;
9375           case IcsObserving: /* [DM] icsEngineAnalyze */
9376             if (!appData.icsEngineAnalyze) ignore = TRUE;
9377             break;
9378           case TwoMachinesPlay:
9379             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9380                 ignore = TRUE;
9381             }
9382             break;
9383           default:
9384             ignore = TRUE;
9385             break;
9386         }
9387
9388         if (!ignore) {
9389             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9390             buf1[0] = NULLCHAR;
9391             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9392                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9393
9394                 if (plyext != ' ' && plyext != '\t') {
9395                     time *= 100;
9396                 }
9397
9398                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9399                 if( cps->scoreIsAbsolute &&
9400                     ( gameMode == MachinePlaysBlack ||
9401                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9402                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9403                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9404                      !WhiteOnMove(currentMove)
9405                     ) )
9406                 {
9407                     curscore = -curscore;
9408                 }
9409
9410                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9411
9412                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9413                         char buf[MSG_SIZ];
9414                         FILE *f;
9415                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9416                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9417                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9418                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9419                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9420                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9421                                 fclose(f);
9422                         }
9423                         else
9424                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9425                           DisplayError(_("failed writing PV"), 0);
9426                 }
9427
9428                 tempStats.depth = plylev;
9429                 tempStats.nodes = nodes;
9430                 tempStats.time = time;
9431                 tempStats.score = curscore;
9432                 tempStats.got_only_move = 0;
9433
9434                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9435                         int ticklen;
9436
9437                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9438                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9439                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9440                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9441                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9442                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9443                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9444                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9445                 }
9446
9447                 /* Buffer overflow protection */
9448                 if (pv[0] != NULLCHAR) {
9449                     if (strlen(pv) >= sizeof(tempStats.movelist)
9450                         && appData.debugMode) {
9451                         fprintf(debugFP,
9452                                 "PV is too long; using the first %u bytes.\n",
9453                                 (unsigned) sizeof(tempStats.movelist) - 1);
9454                     }
9455
9456                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9457                 } else {
9458                     sprintf(tempStats.movelist, " no PV\n");
9459                 }
9460
9461                 if (tempStats.seen_stat) {
9462                     tempStats.ok_to_send = 1;
9463                 }
9464
9465                 if (strchr(tempStats.movelist, '(') != NULL) {
9466                     tempStats.line_is_book = 1;
9467                     tempStats.nr_moves = 0;
9468                     tempStats.moves_left = 0;
9469                 } else {
9470                     tempStats.line_is_book = 0;
9471                 }
9472
9473                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9474                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9475
9476                 SendProgramStatsToFrontend( cps, &tempStats );
9477
9478                 /*
9479                     [AS] Protect the thinkOutput buffer from overflow... this
9480                     is only useful if buf1 hasn't overflowed first!
9481                 */
9482                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9483                          plylev,
9484                          (gameMode == TwoMachinesPlay ?
9485                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9486                          ((double) curscore) / 100.0,
9487                          prefixHint ? lastHint : "",
9488                          prefixHint ? " " : "" );
9489
9490                 if( buf1[0] != NULLCHAR ) {
9491                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9492
9493                     if( strlen(pv) > max_len ) {
9494                         if( appData.debugMode) {
9495                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9496                         }
9497                         pv[max_len+1] = '\0';
9498                     }
9499
9500                     strcat( thinkOutput, pv);
9501                 }
9502
9503                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9504                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9505                     DisplayMove(currentMove - 1);
9506                 }
9507                 return;
9508
9509             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9510                 /* crafty (9.25+) says "(only move) <move>"
9511                  * if there is only 1 legal move
9512                  */
9513                 sscanf(p, "(only move) %s", buf1);
9514                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9515                 sprintf(programStats.movelist, "%s (only move)", buf1);
9516                 programStats.depth = 1;
9517                 programStats.nr_moves = 1;
9518                 programStats.moves_left = 1;
9519                 programStats.nodes = 1;
9520                 programStats.time = 1;
9521                 programStats.got_only_move = 1;
9522
9523                 /* Not really, but we also use this member to
9524                    mean "line isn't going to change" (Crafty
9525                    isn't searching, so stats won't change) */
9526                 programStats.line_is_book = 1;
9527
9528                 SendProgramStatsToFrontend( cps, &programStats );
9529
9530                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9531                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9532                     DisplayMove(currentMove - 1);
9533                 }
9534                 return;
9535             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9536                               &time, &nodes, &plylev, &mvleft,
9537                               &mvtot, mvname) >= 5) {
9538                 /* The stat01: line is from Crafty (9.29+) in response
9539                    to the "." command */
9540                 programStats.seen_stat = 1;
9541                 cps->maybeThinking = TRUE;
9542
9543                 if (programStats.got_only_move || !appData.periodicUpdates)
9544                   return;
9545
9546                 programStats.depth = plylev;
9547                 programStats.time = time;
9548                 programStats.nodes = nodes;
9549                 programStats.moves_left = mvleft;
9550                 programStats.nr_moves = mvtot;
9551                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9552                 programStats.ok_to_send = 1;
9553                 programStats.movelist[0] = '\0';
9554
9555                 SendProgramStatsToFrontend( cps, &programStats );
9556
9557                 return;
9558
9559             } else if (strncmp(message,"++",2) == 0) {
9560                 /* Crafty 9.29+ outputs this */
9561                 programStats.got_fail = 2;
9562                 return;
9563
9564             } else if (strncmp(message,"--",2) == 0) {
9565                 /* Crafty 9.29+ outputs this */
9566                 programStats.got_fail = 1;
9567                 return;
9568
9569             } else if (thinkOutput[0] != NULLCHAR &&
9570                        strncmp(message, "    ", 4) == 0) {
9571                 unsigned message_len;
9572
9573                 p = message;
9574                 while (*p && *p == ' ') p++;
9575
9576                 message_len = strlen( p );
9577
9578                 /* [AS] Avoid buffer overflow */
9579                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9580                     strcat(thinkOutput, " ");
9581                     strcat(thinkOutput, p);
9582                 }
9583
9584                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9585                     strcat(programStats.movelist, " ");
9586                     strcat(programStats.movelist, p);
9587                 }
9588
9589                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9590                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9591                     DisplayMove(currentMove - 1);
9592                 }
9593                 return;
9594             }
9595         }
9596         else {
9597             buf1[0] = NULLCHAR;
9598
9599             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9600                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9601             {
9602                 ChessProgramStats cpstats;
9603
9604                 if (plyext != ' ' && plyext != '\t') {
9605                     time *= 100;
9606                 }
9607
9608                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9609                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9610                     curscore = -curscore;
9611                 }
9612
9613                 cpstats.depth = plylev;
9614                 cpstats.nodes = nodes;
9615                 cpstats.time = time;
9616                 cpstats.score = curscore;
9617                 cpstats.got_only_move = 0;
9618                 cpstats.movelist[0] = '\0';
9619
9620                 if (buf1[0] != NULLCHAR) {
9621                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9622                 }
9623
9624                 cpstats.ok_to_send = 0;
9625                 cpstats.line_is_book = 0;
9626                 cpstats.nr_moves = 0;
9627                 cpstats.moves_left = 0;
9628
9629                 SendProgramStatsToFrontend( cps, &cpstats );
9630             }
9631         }
9632     }
9633 }
9634
9635
9636 /* Parse a game score from the character string "game", and
9637    record it as the history of the current game.  The game
9638    score is NOT assumed to start from the standard position.
9639    The display is not updated in any way.
9640    */
9641 void
9642 ParseGameHistory (char *game)
9643 {
9644     ChessMove moveType;
9645     int fromX, fromY, toX, toY, boardIndex;
9646     char promoChar;
9647     char *p, *q;
9648     char buf[MSG_SIZ];
9649
9650     if (appData.debugMode)
9651       fprintf(debugFP, "Parsing game history: %s\n", game);
9652
9653     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9654     gameInfo.site = StrSave(appData.icsHost);
9655     gameInfo.date = PGNDate();
9656     gameInfo.round = StrSave("-");
9657
9658     /* Parse out names of players */
9659     while (*game == ' ') game++;
9660     p = buf;
9661     while (*game != ' ') *p++ = *game++;
9662     *p = NULLCHAR;
9663     gameInfo.white = StrSave(buf);
9664     while (*game == ' ') game++;
9665     p = buf;
9666     while (*game != ' ' && *game != '\n') *p++ = *game++;
9667     *p = NULLCHAR;
9668     gameInfo.black = StrSave(buf);
9669
9670     /* Parse moves */
9671     boardIndex = blackPlaysFirst ? 1 : 0;
9672     yynewstr(game);
9673     for (;;) {
9674         yyboardindex = boardIndex;
9675         moveType = (ChessMove) Myylex();
9676         switch (moveType) {
9677           case IllegalMove:             /* maybe suicide chess, etc. */
9678   if (appData.debugMode) {
9679     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9680     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9681     setbuf(debugFP, NULL);
9682   }
9683           case WhitePromotion:
9684           case BlackPromotion:
9685           case WhiteNonPromotion:
9686           case BlackNonPromotion:
9687           case NormalMove:
9688           case FirstLeg:
9689           case WhiteCapturesEnPassant:
9690           case BlackCapturesEnPassant:
9691           case WhiteKingSideCastle:
9692           case WhiteQueenSideCastle:
9693           case BlackKingSideCastle:
9694           case BlackQueenSideCastle:
9695           case WhiteKingSideCastleWild:
9696           case WhiteQueenSideCastleWild:
9697           case BlackKingSideCastleWild:
9698           case BlackQueenSideCastleWild:
9699           /* PUSH Fabien */
9700           case WhiteHSideCastleFR:
9701           case WhiteASideCastleFR:
9702           case BlackHSideCastleFR:
9703           case BlackASideCastleFR:
9704           /* POP Fabien */
9705             fromX = currentMoveString[0] - AAA;
9706             fromY = currentMoveString[1] - ONE;
9707             toX = currentMoveString[2] - AAA;
9708             toY = currentMoveString[3] - ONE;
9709             promoChar = currentMoveString[4];
9710             break;
9711           case WhiteDrop:
9712           case BlackDrop:
9713             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9714             fromX = moveType == WhiteDrop ?
9715               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9716             (int) CharToPiece(ToLower(currentMoveString[0]));
9717             fromY = DROP_RANK;
9718             toX = currentMoveString[2] - AAA;
9719             toY = currentMoveString[3] - ONE;
9720             promoChar = NULLCHAR;
9721             break;
9722           case AmbiguousMove:
9723             /* bug? */
9724             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9725   if (appData.debugMode) {
9726     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9727     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9728     setbuf(debugFP, NULL);
9729   }
9730             DisplayError(buf, 0);
9731             return;
9732           case ImpossibleMove:
9733             /* bug? */
9734             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9735   if (appData.debugMode) {
9736     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9737     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9738     setbuf(debugFP, NULL);
9739   }
9740             DisplayError(buf, 0);
9741             return;
9742           case EndOfFile:
9743             if (boardIndex < backwardMostMove) {
9744                 /* Oops, gap.  How did that happen? */
9745                 DisplayError(_("Gap in move list"), 0);
9746                 return;
9747             }
9748             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9749             if (boardIndex > forwardMostMove) {
9750                 forwardMostMove = boardIndex;
9751             }
9752             return;
9753           case ElapsedTime:
9754             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9755                 strcat(parseList[boardIndex-1], " ");
9756                 strcat(parseList[boardIndex-1], yy_text);
9757             }
9758             continue;
9759           case Comment:
9760           case PGNTag:
9761           case NAG:
9762           default:
9763             /* ignore */
9764             continue;
9765           case WhiteWins:
9766           case BlackWins:
9767           case GameIsDrawn:
9768           case GameUnfinished:
9769             if (gameMode == IcsExamining) {
9770                 if (boardIndex < backwardMostMove) {
9771                     /* Oops, gap.  How did that happen? */
9772                     return;
9773                 }
9774                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9775                 return;
9776             }
9777             gameInfo.result = moveType;
9778             p = strchr(yy_text, '{');
9779             if (p == NULL) p = strchr(yy_text, '(');
9780             if (p == NULL) {
9781                 p = yy_text;
9782                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9783             } else {
9784                 q = strchr(p, *p == '{' ? '}' : ')');
9785                 if (q != NULL) *q = NULLCHAR;
9786                 p++;
9787             }
9788             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9789             gameInfo.resultDetails = StrSave(p);
9790             continue;
9791         }
9792         if (boardIndex >= forwardMostMove &&
9793             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9794             backwardMostMove = blackPlaysFirst ? 1 : 0;
9795             return;
9796         }
9797         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9798                                  fromY, fromX, toY, toX, promoChar,
9799                                  parseList[boardIndex]);
9800         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9801         /* currentMoveString is set as a side-effect of yylex */
9802         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9803         strcat(moveList[boardIndex], "\n");
9804         boardIndex++;
9805         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9806         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9807           case MT_NONE:
9808           case MT_STALEMATE:
9809           default:
9810             break;
9811           case MT_CHECK:
9812             if(!IS_SHOGI(gameInfo.variant))
9813                 strcat(parseList[boardIndex - 1], "+");
9814             break;
9815           case MT_CHECKMATE:
9816           case MT_STAINMATE:
9817             strcat(parseList[boardIndex - 1], "#");
9818             break;
9819         }
9820     }
9821 }
9822
9823
9824 /* Apply a move to the given board  */
9825 void
9826 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9827 {
9828   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9829   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9830
9831     /* [HGM] compute & store e.p. status and castling rights for new position */
9832     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9833
9834       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9835       oldEP = (signed char)board[EP_STATUS];
9836       board[EP_STATUS] = EP_NONE;
9837
9838   if (fromY == DROP_RANK) {
9839         /* must be first */
9840         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9841             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9842             return;
9843         }
9844         piece = board[toY][toX] = (ChessSquare) fromX;
9845   } else {
9846 //      ChessSquare victim;
9847       int i;
9848
9849       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9850 //           victim = board[killY][killX],
9851            board[killY][killX] = EmptySquare,
9852            board[EP_STATUS] = EP_CAPTURE;
9853
9854       if( board[toY][toX] != EmptySquare ) {
9855            board[EP_STATUS] = EP_CAPTURE;
9856            if( (fromX != toX || fromY != toY) && // not igui!
9857                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9858                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9859                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9860            }
9861       }
9862
9863       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9864            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9865                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9866       } else
9867       if( board[fromY][fromX] == WhitePawn ) {
9868            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9869                board[EP_STATUS] = EP_PAWN_MOVE;
9870            if( toY-fromY==2) {
9871                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9872                         gameInfo.variant != VariantBerolina || toX < fromX)
9873                       board[EP_STATUS] = toX | berolina;
9874                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9875                         gameInfo.variant != VariantBerolina || toX > fromX)
9876                       board[EP_STATUS] = toX;
9877            }
9878       } else
9879       if( board[fromY][fromX] == BlackPawn ) {
9880            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9881                board[EP_STATUS] = EP_PAWN_MOVE;
9882            if( toY-fromY== -2) {
9883                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9884                         gameInfo.variant != VariantBerolina || toX < fromX)
9885                       board[EP_STATUS] = toX | berolina;
9886                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9887                         gameInfo.variant != VariantBerolina || toX > fromX)
9888                       board[EP_STATUS] = toX;
9889            }
9890        }
9891
9892        for(i=0; i<nrCastlingRights; i++) {
9893            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9894               board[CASTLING][i] == toX   && castlingRank[i] == toY
9895              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9896        }
9897
9898        if(gameInfo.variant == VariantSChess) { // update virginity
9899            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9900            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9901            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9902            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9903        }
9904
9905      if (fromX == toX && fromY == toY) return;
9906
9907      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9908      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9909      if(gameInfo.variant == VariantKnightmate)
9910          king += (int) WhiteUnicorn - (int) WhiteKing;
9911
9912     /* Code added by Tord: */
9913     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9914     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9915         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9916       board[fromY][fromX] = EmptySquare;
9917       board[toY][toX] = EmptySquare;
9918       if((toX > fromX) != (piece == WhiteRook)) {
9919         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9920       } else {
9921         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9922       }
9923     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9924                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9925       board[fromY][fromX] = EmptySquare;
9926       board[toY][toX] = EmptySquare;
9927       if((toX > fromX) != (piece == BlackRook)) {
9928         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9929       } else {
9930         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9931       }
9932     /* End of code added by Tord */
9933
9934     } else if (board[fromY][fromX] == king
9935         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9936         && toY == fromY && toX > fromX+1) {
9937         board[fromY][fromX] = EmptySquare;
9938         board[toY][toX] = king;
9939         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9940         board[fromY][BOARD_RGHT-1] = EmptySquare;
9941     } else if (board[fromY][fromX] == king
9942         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9943                && toY == fromY && toX < fromX-1) {
9944         board[fromY][fromX] = EmptySquare;
9945         board[toY][toX] = king;
9946         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9947         board[fromY][BOARD_LEFT] = EmptySquare;
9948     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9949                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9950                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9951                ) {
9952         /* white pawn promotion */
9953         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9954         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9955             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9956         board[fromY][fromX] = EmptySquare;
9957     } else if ((fromY >= BOARD_HEIGHT>>1)
9958                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9959                && (toX != fromX)
9960                && gameInfo.variant != VariantXiangqi
9961                && gameInfo.variant != VariantBerolina
9962                && (board[fromY][fromX] == WhitePawn)
9963                && (board[toY][toX] == EmptySquare)) {
9964         board[fromY][fromX] = EmptySquare;
9965         board[toY][toX] = WhitePawn;
9966         captured = board[toY - 1][toX];
9967         board[toY - 1][toX] = EmptySquare;
9968     } else if ((fromY == BOARD_HEIGHT-4)
9969                && (toX == fromX)
9970                && gameInfo.variant == VariantBerolina
9971                && (board[fromY][fromX] == WhitePawn)
9972                && (board[toY][toX] == EmptySquare)) {
9973         board[fromY][fromX] = EmptySquare;
9974         board[toY][toX] = WhitePawn;
9975         if(oldEP & EP_BEROLIN_A) {
9976                 captured = board[fromY][fromX-1];
9977                 board[fromY][fromX-1] = EmptySquare;
9978         }else{  captured = board[fromY][fromX+1];
9979                 board[fromY][fromX+1] = EmptySquare;
9980         }
9981     } else if (board[fromY][fromX] == king
9982         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9983                && toY == fromY && toX > fromX+1) {
9984         board[fromY][fromX] = EmptySquare;
9985         board[toY][toX] = king;
9986         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9987         board[fromY][BOARD_RGHT-1] = EmptySquare;
9988     } else if (board[fromY][fromX] == king
9989         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9990                && toY == fromY && toX < fromX-1) {
9991         board[fromY][fromX] = EmptySquare;
9992         board[toY][toX] = king;
9993         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9994         board[fromY][BOARD_LEFT] = EmptySquare;
9995     } else if (fromY == 7 && fromX == 3
9996                && board[fromY][fromX] == BlackKing
9997                && toY == 7 && toX == 5) {
9998         board[fromY][fromX] = EmptySquare;
9999         board[toY][toX] = BlackKing;
10000         board[fromY][7] = EmptySquare;
10001         board[toY][4] = BlackRook;
10002     } else if (fromY == 7 && fromX == 3
10003                && board[fromY][fromX] == BlackKing
10004                && toY == 7 && toX == 1) {
10005         board[fromY][fromX] = EmptySquare;
10006         board[toY][toX] = BlackKing;
10007         board[fromY][0] = EmptySquare;
10008         board[toY][2] = BlackRook;
10009     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10010                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10011                && toY < promoRank && promoChar
10012                ) {
10013         /* black pawn promotion */
10014         board[toY][toX] = CharToPiece(ToLower(promoChar));
10015         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10016             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10017         board[fromY][fromX] = EmptySquare;
10018     } else if ((fromY < BOARD_HEIGHT>>1)
10019                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10020                && (toX != fromX)
10021                && gameInfo.variant != VariantXiangqi
10022                && gameInfo.variant != VariantBerolina
10023                && (board[fromY][fromX] == BlackPawn)
10024                && (board[toY][toX] == EmptySquare)) {
10025         board[fromY][fromX] = EmptySquare;
10026         board[toY][toX] = BlackPawn;
10027         captured = board[toY + 1][toX];
10028         board[toY + 1][toX] = EmptySquare;
10029     } else if ((fromY == 3)
10030                && (toX == fromX)
10031                && gameInfo.variant == VariantBerolina
10032                && (board[fromY][fromX] == BlackPawn)
10033                && (board[toY][toX] == EmptySquare)) {
10034         board[fromY][fromX] = EmptySquare;
10035         board[toY][toX] = BlackPawn;
10036         if(oldEP & EP_BEROLIN_A) {
10037                 captured = board[fromY][fromX-1];
10038                 board[fromY][fromX-1] = EmptySquare;
10039         }else{  captured = board[fromY][fromX+1];
10040                 board[fromY][fromX+1] = EmptySquare;
10041         }
10042     } else {
10043         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10044         board[fromY][fromX] = EmptySquare;
10045         board[toY][toX] = piece;
10046     }
10047   }
10048
10049     if (gameInfo.holdingsWidth != 0) {
10050
10051       /* !!A lot more code needs to be written to support holdings  */
10052       /* [HGM] OK, so I have written it. Holdings are stored in the */
10053       /* penultimate board files, so they are automaticlly stored   */
10054       /* in the game history.                                       */
10055       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10056                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10057         /* Delete from holdings, by decreasing count */
10058         /* and erasing image if necessary            */
10059         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10060         if(p < (int) BlackPawn) { /* white drop */
10061              p -= (int)WhitePawn;
10062                  p = PieceToNumber((ChessSquare)p);
10063              if(p >= gameInfo.holdingsSize) p = 0;
10064              if(--board[p][BOARD_WIDTH-2] <= 0)
10065                   board[p][BOARD_WIDTH-1] = EmptySquare;
10066              if((int)board[p][BOARD_WIDTH-2] < 0)
10067                         board[p][BOARD_WIDTH-2] = 0;
10068         } else {                  /* black drop */
10069              p -= (int)BlackPawn;
10070                  p = PieceToNumber((ChessSquare)p);
10071              if(p >= gameInfo.holdingsSize) p = 0;
10072              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10073                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10074              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10075                         board[BOARD_HEIGHT-1-p][1] = 0;
10076         }
10077       }
10078       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10079           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10080         /* [HGM] holdings: Add to holdings, if holdings exist */
10081         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10082                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10083                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10084         }
10085         p = (int) captured;
10086         if (p >= (int) BlackPawn) {
10087           p -= (int)BlackPawn;
10088           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10089                   /* in Shogi restore piece to its original  first */
10090                   captured = (ChessSquare) (DEMOTED captured);
10091                   p = DEMOTED p;
10092           }
10093           p = PieceToNumber((ChessSquare)p);
10094           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10095           board[p][BOARD_WIDTH-2]++;
10096           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10097         } else {
10098           p -= (int)WhitePawn;
10099           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10100                   captured = (ChessSquare) (DEMOTED captured);
10101                   p = DEMOTED p;
10102           }
10103           p = PieceToNumber((ChessSquare)p);
10104           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10105           board[BOARD_HEIGHT-1-p][1]++;
10106           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10107         }
10108       }
10109     } else if (gameInfo.variant == VariantAtomic) {
10110       if (captured != EmptySquare) {
10111         int y, x;
10112         for (y = toY-1; y <= toY+1; y++) {
10113           for (x = toX-1; x <= toX+1; x++) {
10114             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10115                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10116               board[y][x] = EmptySquare;
10117             }
10118           }
10119         }
10120         board[toY][toX] = EmptySquare;
10121       }
10122     }
10123
10124     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10125         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10126     } else
10127     if(promoChar == '+') {
10128         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10129         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10130         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10131           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10132     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10133         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10134         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10135            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10136         board[toY][toX] = newPiece;
10137     }
10138     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10139                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10140         // [HGM] superchess: take promotion piece out of holdings
10141         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10142         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10143             if(!--board[k][BOARD_WIDTH-2])
10144                 board[k][BOARD_WIDTH-1] = EmptySquare;
10145         } else {
10146             if(!--board[BOARD_HEIGHT-1-k][1])
10147                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10148         }
10149     }
10150 }
10151
10152 /* Updates forwardMostMove */
10153 void
10154 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10155 {
10156     int x = toX, y = toY;
10157     char *s = parseList[forwardMostMove];
10158     ChessSquare p = boards[forwardMostMove][toY][toX];
10159 //    forwardMostMove++; // [HGM] bare: moved downstream
10160
10161     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10162     (void) CoordsToAlgebraic(boards[forwardMostMove],
10163                              PosFlags(forwardMostMove),
10164                              fromY, fromX, y, x, promoChar,
10165                              s);
10166     if(killX >= 0 && killY >= 0)
10167         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10168
10169     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10170         int timeLeft; static int lastLoadFlag=0; int king, piece;
10171         piece = boards[forwardMostMove][fromY][fromX];
10172         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10173         if(gameInfo.variant == VariantKnightmate)
10174             king += (int) WhiteUnicorn - (int) WhiteKing;
10175         if(forwardMostMove == 0) {
10176             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10177                 fprintf(serverMoves, "%s;", UserName());
10178             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10179                 fprintf(serverMoves, "%s;", second.tidy);
10180             fprintf(serverMoves, "%s;", first.tidy);
10181             if(gameMode == MachinePlaysWhite)
10182                 fprintf(serverMoves, "%s;", UserName());
10183             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10184                 fprintf(serverMoves, "%s;", second.tidy);
10185         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10186         lastLoadFlag = loadFlag;
10187         // print base move
10188         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10189         // print castling suffix
10190         if( toY == fromY && piece == king ) {
10191             if(toX-fromX > 1)
10192                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10193             if(fromX-toX >1)
10194                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10195         }
10196         // e.p. suffix
10197         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10198              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10199              boards[forwardMostMove][toY][toX] == EmptySquare
10200              && fromX != toX && fromY != toY)
10201                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10202         // promotion suffix
10203         if(promoChar != NULLCHAR) {
10204             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10205                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10206                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10207             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10208         }
10209         if(!loadFlag) {
10210                 char buf[MOVE_LEN*2], *p; int len;
10211             fprintf(serverMoves, "/%d/%d",
10212                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10213             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10214             else                      timeLeft = blackTimeRemaining/1000;
10215             fprintf(serverMoves, "/%d", timeLeft);
10216                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10217                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10218                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10219                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10220             fprintf(serverMoves, "/%s", buf);
10221         }
10222         fflush(serverMoves);
10223     }
10224
10225     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10226         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10227       return;
10228     }
10229     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10230     if (commentList[forwardMostMove+1] != NULL) {
10231         free(commentList[forwardMostMove+1]);
10232         commentList[forwardMostMove+1] = NULL;
10233     }
10234     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10235     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10236     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10237     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10238     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10239     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10240     adjustedClock = FALSE;
10241     gameInfo.result = GameUnfinished;
10242     if (gameInfo.resultDetails != NULL) {
10243         free(gameInfo.resultDetails);
10244         gameInfo.resultDetails = NULL;
10245     }
10246     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10247                               moveList[forwardMostMove - 1]);
10248     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10249       case MT_NONE:
10250       case MT_STALEMATE:
10251       default:
10252         break;
10253       case MT_CHECK:
10254         if(!IS_SHOGI(gameInfo.variant))
10255             strcat(parseList[forwardMostMove - 1], "+");
10256         break;
10257       case MT_CHECKMATE:
10258       case MT_STAINMATE:
10259         strcat(parseList[forwardMostMove - 1], "#");
10260         break;
10261     }
10262 }
10263
10264 /* Updates currentMove if not pausing */
10265 void
10266 ShowMove (int fromX, int fromY, int toX, int toY)
10267 {
10268     int instant = (gameMode == PlayFromGameFile) ?
10269         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10270     if(appData.noGUI) return;
10271     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10272         if (!instant) {
10273             if (forwardMostMove == currentMove + 1) {
10274                 AnimateMove(boards[forwardMostMove - 1],
10275                             fromX, fromY, toX, toY);
10276             }
10277         }
10278         currentMove = forwardMostMove;
10279     }
10280
10281     killX = killY = -1; // [HGM] lion: used up
10282
10283     if (instant) return;
10284
10285     DisplayMove(currentMove - 1);
10286     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10287             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10288                 SetHighlights(fromX, fromY, toX, toY);
10289             }
10290     }
10291     DrawPosition(FALSE, boards[currentMove]);
10292     DisplayBothClocks();
10293     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10294 }
10295
10296 void
10297 SendEgtPath (ChessProgramState *cps)
10298 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10299         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10300
10301         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10302
10303         while(*p) {
10304             char c, *q = name+1, *r, *s;
10305
10306             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10307             while(*p && *p != ',') *q++ = *p++;
10308             *q++ = ':'; *q = 0;
10309             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10310                 strcmp(name, ",nalimov:") == 0 ) {
10311                 // take nalimov path from the menu-changeable option first, if it is defined
10312               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10313                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10314             } else
10315             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10316                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10317                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10318                 s = r = StrStr(s, ":") + 1; // beginning of path info
10319                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10320                 c = *r; *r = 0;             // temporarily null-terminate path info
10321                     *--q = 0;               // strip of trailig ':' from name
10322                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10323                 *r = c;
10324                 SendToProgram(buf,cps);     // send egtbpath command for this format
10325             }
10326             if(*p == ',') p++; // read away comma to position for next format name
10327         }
10328 }
10329
10330 static int
10331 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10332 {
10333       int width = 8, height = 8, holdings = 0;             // most common sizes
10334       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10335       // correct the deviations default for each variant
10336       if( v == VariantXiangqi ) width = 9,  height = 10;
10337       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10338       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10339       if( v == VariantCapablanca || v == VariantCapaRandom ||
10340           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10341                                 width = 10;
10342       if( v == VariantCourier ) width = 12;
10343       if( v == VariantSuper )                            holdings = 8;
10344       if( v == VariantGreat )   width = 10,              holdings = 8;
10345       if( v == VariantSChess )                           holdings = 7;
10346       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10347       if( v == VariantChuChess) width = 10, height = 10;
10348       if( v == VariantChu )     width = 12, height = 12;
10349       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10350              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10351              holdingsSize >= 0 && holdingsSize != holdings;
10352 }
10353
10354 char variantError[MSG_SIZ];
10355
10356 char *
10357 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10358 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10359       char *p, *variant = VariantName(v);
10360       static char b[MSG_SIZ];
10361       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10362            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10363                                                holdingsSize, variant); // cook up sized variant name
10364            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10365            if(StrStr(list, b) == NULL) {
10366                // specific sized variant not known, check if general sizing allowed
10367                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10368                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10369                             boardWidth, boardHeight, holdingsSize, engine);
10370                    return NULL;
10371                }
10372                /* [HGM] here we really should compare with the maximum supported board size */
10373            }
10374       } else snprintf(b, MSG_SIZ,"%s", variant);
10375       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10376       p = StrStr(list, b);
10377       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10378       if(p == NULL) {
10379           // occurs not at all in list, or only as sub-string
10380           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10381           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10382               int l = strlen(variantError);
10383               char *q;
10384               while(p != list && p[-1] != ',') p--;
10385               q = strchr(p, ',');
10386               if(q) *q = NULLCHAR;
10387               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10388               if(q) *q= ',';
10389           }
10390           return NULL;
10391       }
10392       return b;
10393 }
10394
10395 void
10396 InitChessProgram (ChessProgramState *cps, int setup)
10397 /* setup needed to setup FRC opening position */
10398 {
10399     char buf[MSG_SIZ], *b;
10400     if (appData.noChessProgram) return;
10401     hintRequested = FALSE;
10402     bookRequested = FALSE;
10403
10404     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10405     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10406     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10407     if(cps->memSize) { /* [HGM] memory */
10408       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10409         SendToProgram(buf, cps);
10410     }
10411     SendEgtPath(cps); /* [HGM] EGT */
10412     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10413       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10414         SendToProgram(buf, cps);
10415     }
10416
10417     SendToProgram(cps->initString, cps);
10418     if (gameInfo.variant != VariantNormal &&
10419         gameInfo.variant != VariantLoadable
10420         /* [HGM] also send variant if board size non-standard */
10421         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10422
10423       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10424                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10425       if (b == NULL) {
10426         DisplayFatalError(variantError, 0, 1);
10427         return;
10428       }
10429
10430       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10431       SendToProgram(buf, cps);
10432     }
10433     currentlyInitializedVariant = gameInfo.variant;
10434
10435     /* [HGM] send opening position in FRC to first engine */
10436     if(setup) {
10437           SendToProgram("force\n", cps);
10438           SendBoard(cps, 0);
10439           /* engine is now in force mode! Set flag to wake it up after first move. */
10440           setboardSpoiledMachineBlack = 1;
10441     }
10442
10443     if (cps->sendICS) {
10444       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10445       SendToProgram(buf, cps);
10446     }
10447     cps->maybeThinking = FALSE;
10448     cps->offeredDraw = 0;
10449     if (!appData.icsActive) {
10450         SendTimeControl(cps, movesPerSession, timeControl,
10451                         timeIncrement, appData.searchDepth,
10452                         searchTime);
10453     }
10454     if (appData.showThinking
10455         // [HGM] thinking: four options require thinking output to be sent
10456         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10457                                 ) {
10458         SendToProgram("post\n", cps);
10459     }
10460     SendToProgram("hard\n", cps);
10461     if (!appData.ponderNextMove) {
10462         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10463            it without being sure what state we are in first.  "hard"
10464            is not a toggle, so that one is OK.
10465          */
10466         SendToProgram("easy\n", cps);
10467     }
10468     if (cps->usePing) {
10469       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10470       SendToProgram(buf, cps);
10471     }
10472     cps->initDone = TRUE;
10473     ClearEngineOutputPane(cps == &second);
10474 }
10475
10476
10477 void
10478 ResendOptions (ChessProgramState *cps)
10479 { // send the stored value of the options
10480   int i;
10481   char buf[MSG_SIZ];
10482   Option *opt = cps->option;
10483   for(i=0; i<cps->nrOptions; i++, opt++) {
10484       switch(opt->type) {
10485         case Spin:
10486         case Slider:
10487         case CheckBox:
10488             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10489           break;
10490         case ComboBox:
10491           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10492           break;
10493         default:
10494             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10495           break;
10496         case Button:
10497         case SaveButton:
10498           continue;
10499       }
10500       SendToProgram(buf, cps);
10501   }
10502 }
10503
10504 void
10505 StartChessProgram (ChessProgramState *cps)
10506 {
10507     char buf[MSG_SIZ];
10508     int err;
10509
10510     if (appData.noChessProgram) return;
10511     cps->initDone = FALSE;
10512
10513     if (strcmp(cps->host, "localhost") == 0) {
10514         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10515     } else if (*appData.remoteShell == NULLCHAR) {
10516         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10517     } else {
10518         if (*appData.remoteUser == NULLCHAR) {
10519           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10520                     cps->program);
10521         } else {
10522           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10523                     cps->host, appData.remoteUser, cps->program);
10524         }
10525         err = StartChildProcess(buf, "", &cps->pr);
10526     }
10527
10528     if (err != 0) {
10529       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10530         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10531         if(cps != &first) return;
10532         appData.noChessProgram = TRUE;
10533         ThawUI();
10534         SetNCPMode();
10535 //      DisplayFatalError(buf, err, 1);
10536 //      cps->pr = NoProc;
10537 //      cps->isr = NULL;
10538         return;
10539     }
10540
10541     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10542     if (cps->protocolVersion > 1) {
10543       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10544       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10545         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10546         cps->comboCnt = 0;  //                and values of combo boxes
10547       }
10548       SendToProgram(buf, cps);
10549       if(cps->reload) ResendOptions(cps);
10550     } else {
10551       SendToProgram("xboard\n", cps);
10552     }
10553 }
10554
10555 void
10556 TwoMachinesEventIfReady P((void))
10557 {
10558   static int curMess = 0;
10559   if (first.lastPing != first.lastPong) {
10560     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10561     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10562     return;
10563   }
10564   if (second.lastPing != second.lastPong) {
10565     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10566     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10567     return;
10568   }
10569   DisplayMessage("", ""); curMess = 0;
10570   TwoMachinesEvent();
10571 }
10572
10573 char *
10574 MakeName (char *template)
10575 {
10576     time_t clock;
10577     struct tm *tm;
10578     static char buf[MSG_SIZ];
10579     char *p = buf;
10580     int i;
10581
10582     clock = time((time_t *)NULL);
10583     tm = localtime(&clock);
10584
10585     while(*p++ = *template++) if(p[-1] == '%') {
10586         switch(*template++) {
10587           case 0:   *p = 0; return buf;
10588           case 'Y': i = tm->tm_year+1900; break;
10589           case 'y': i = tm->tm_year-100; break;
10590           case 'M': i = tm->tm_mon+1; break;
10591           case 'd': i = tm->tm_mday; break;
10592           case 'h': i = tm->tm_hour; break;
10593           case 'm': i = tm->tm_min; break;
10594           case 's': i = tm->tm_sec; break;
10595           default:  i = 0;
10596         }
10597         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10598     }
10599     return buf;
10600 }
10601
10602 int
10603 CountPlayers (char *p)
10604 {
10605     int n = 0;
10606     while(p = strchr(p, '\n')) p++, n++; // count participants
10607     return n;
10608 }
10609
10610 FILE *
10611 WriteTourneyFile (char *results, FILE *f)
10612 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10613     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10614     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10615         // create a file with tournament description
10616         fprintf(f, "-participants {%s}\n", appData.participants);
10617         fprintf(f, "-seedBase %d\n", appData.seedBase);
10618         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10619         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10620         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10621         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10622         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10623         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10624         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10625         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10626         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10627         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10628         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10629         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10630         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10631         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10632         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10633         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10634         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10635         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10636         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10637         fprintf(f, "-smpCores %d\n", appData.smpCores);
10638         if(searchTime > 0)
10639                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10640         else {
10641                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10642                 fprintf(f, "-tc %s\n", appData.timeControl);
10643                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10644         }
10645         fprintf(f, "-results \"%s\"\n", results);
10646     }
10647     return f;
10648 }
10649
10650 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10651
10652 void
10653 Substitute (char *participants, int expunge)
10654 {
10655     int i, changed, changes=0, nPlayers=0;
10656     char *p, *q, *r, buf[MSG_SIZ];
10657     if(participants == NULL) return;
10658     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10659     r = p = participants; q = appData.participants;
10660     while(*p && *p == *q) {
10661         if(*p == '\n') r = p+1, nPlayers++;
10662         p++; q++;
10663     }
10664     if(*p) { // difference
10665         while(*p && *p++ != '\n');
10666         while(*q && *q++ != '\n');
10667       changed = nPlayers;
10668         changes = 1 + (strcmp(p, q) != 0);
10669     }
10670     if(changes == 1) { // a single engine mnemonic was changed
10671         q = r; while(*q) nPlayers += (*q++ == '\n');
10672         p = buf; while(*r && (*p = *r++) != '\n') p++;
10673         *p = NULLCHAR;
10674         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10675         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10676         if(mnemonic[i]) { // The substitute is valid
10677             FILE *f;
10678             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10679                 flock(fileno(f), LOCK_EX);
10680                 ParseArgsFromFile(f);
10681                 fseek(f, 0, SEEK_SET);
10682                 FREE(appData.participants); appData.participants = participants;
10683                 if(expunge) { // erase results of replaced engine
10684                     int len = strlen(appData.results), w, b, dummy;
10685                     for(i=0; i<len; i++) {
10686                         Pairing(i, nPlayers, &w, &b, &dummy);
10687                         if((w == changed || b == changed) && appData.results[i] == '*') {
10688                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10689                             fclose(f);
10690                             return;
10691                         }
10692                     }
10693                     for(i=0; i<len; i++) {
10694                         Pairing(i, nPlayers, &w, &b, &dummy);
10695                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10696                     }
10697                 }
10698                 WriteTourneyFile(appData.results, f);
10699                 fclose(f); // release lock
10700                 return;
10701             }
10702         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10703     }
10704     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10705     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10706     free(participants);
10707     return;
10708 }
10709
10710 int
10711 CheckPlayers (char *participants)
10712 {
10713         int i;
10714         char buf[MSG_SIZ], *p;
10715         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10716         while(p = strchr(participants, '\n')) {
10717             *p = NULLCHAR;
10718             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10719             if(!mnemonic[i]) {
10720                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10721                 *p = '\n';
10722                 DisplayError(buf, 0);
10723                 return 1;
10724             }
10725             *p = '\n';
10726             participants = p + 1;
10727         }
10728         return 0;
10729 }
10730
10731 int
10732 CreateTourney (char *name)
10733 {
10734         FILE *f;
10735         if(matchMode && strcmp(name, appData.tourneyFile)) {
10736              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10737         }
10738         if(name[0] == NULLCHAR) {
10739             if(appData.participants[0])
10740                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10741             return 0;
10742         }
10743         f = fopen(name, "r");
10744         if(f) { // file exists
10745             ASSIGN(appData.tourneyFile, name);
10746             ParseArgsFromFile(f); // parse it
10747         } else {
10748             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10749             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10750                 DisplayError(_("Not enough participants"), 0);
10751                 return 0;
10752             }
10753             if(CheckPlayers(appData.participants)) return 0;
10754             ASSIGN(appData.tourneyFile, name);
10755             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10756             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10757         }
10758         fclose(f);
10759         appData.noChessProgram = FALSE;
10760         appData.clockMode = TRUE;
10761         SetGNUMode();
10762         return 1;
10763 }
10764
10765 int
10766 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10767 {
10768     char buf[MSG_SIZ], *p, *q;
10769     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10770     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10771     skip = !all && group[0]; // if group requested, we start in skip mode
10772     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10773         p = names; q = buf; header = 0;
10774         while(*p && *p != '\n') *q++ = *p++;
10775         *q = 0;
10776         if(*p == '\n') p++;
10777         if(buf[0] == '#') {
10778             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10779             depth++; // we must be entering a new group
10780             if(all) continue; // suppress printing group headers when complete list requested
10781             header = 1;
10782             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10783         }
10784         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10785         if(engineList[i]) free(engineList[i]);
10786         engineList[i] = strdup(buf);
10787         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10788         if(engineMnemonic[i]) free(engineMnemonic[i]);
10789         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10790             strcat(buf, " (");
10791             sscanf(q + 8, "%s", buf + strlen(buf));
10792             strcat(buf, ")");
10793         }
10794         engineMnemonic[i] = strdup(buf);
10795         i++;
10796     }
10797     engineList[i] = engineMnemonic[i] = NULL;
10798     return i;
10799 }
10800
10801 // following implemented as macro to avoid type limitations
10802 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10803
10804 void
10805 SwapEngines (int n)
10806 {   // swap settings for first engine and other engine (so far only some selected options)
10807     int h;
10808     char *p;
10809     if(n == 0) return;
10810     SWAP(directory, p)
10811     SWAP(chessProgram, p)
10812     SWAP(isUCI, h)
10813     SWAP(hasOwnBookUCI, h)
10814     SWAP(protocolVersion, h)
10815     SWAP(reuse, h)
10816     SWAP(scoreIsAbsolute, h)
10817     SWAP(timeOdds, h)
10818     SWAP(logo, p)
10819     SWAP(pgnName, p)
10820     SWAP(pvSAN, h)
10821     SWAP(engOptions, p)
10822     SWAP(engInitString, p)
10823     SWAP(computerString, p)
10824     SWAP(features, p)
10825     SWAP(fenOverride, p)
10826     SWAP(NPS, h)
10827     SWAP(accumulateTC, h)
10828     SWAP(drawDepth, h)
10829     SWAP(host, p)
10830 }
10831
10832 int
10833 GetEngineLine (char *s, int n)
10834 {
10835     int i;
10836     char buf[MSG_SIZ];
10837     extern char *icsNames;
10838     if(!s || !*s) return 0;
10839     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10840     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10841     if(!mnemonic[i]) return 0;
10842     if(n == 11) return 1; // just testing if there was a match
10843     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10844     if(n == 1) SwapEngines(n);
10845     ParseArgsFromString(buf);
10846     if(n == 1) SwapEngines(n);
10847     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10848         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10849         ParseArgsFromString(buf);
10850     }
10851     return 1;
10852 }
10853
10854 int
10855 SetPlayer (int player, char *p)
10856 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10857     int i;
10858     char buf[MSG_SIZ], *engineName;
10859     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10860     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10861     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10862     if(mnemonic[i]) {
10863         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10864         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10865         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10866         ParseArgsFromString(buf);
10867     } else { // no engine with this nickname is installed!
10868         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10869         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10870         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10871         ModeHighlight();
10872         DisplayError(buf, 0);
10873         return 0;
10874     }
10875     free(engineName);
10876     return i;
10877 }
10878
10879 char *recentEngines;
10880
10881 void
10882 RecentEngineEvent (int nr)
10883 {
10884     int n;
10885 //    SwapEngines(1); // bump first to second
10886 //    ReplaceEngine(&second, 1); // and load it there
10887     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10888     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10889     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10890         ReplaceEngine(&first, 0);
10891         FloatToFront(&appData.recentEngineList, command[n]);
10892     }
10893 }
10894
10895 int
10896 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10897 {   // determine players from game number
10898     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10899
10900     if(appData.tourneyType == 0) {
10901         roundsPerCycle = (nPlayers - 1) | 1;
10902         pairingsPerRound = nPlayers / 2;
10903     } else if(appData.tourneyType > 0) {
10904         roundsPerCycle = nPlayers - appData.tourneyType;
10905         pairingsPerRound = appData.tourneyType;
10906     }
10907     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10908     gamesPerCycle = gamesPerRound * roundsPerCycle;
10909     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10910     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10911     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10912     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10913     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10914     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10915
10916     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10917     if(appData.roundSync) *syncInterval = gamesPerRound;
10918
10919     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10920
10921     if(appData.tourneyType == 0) {
10922         if(curPairing == (nPlayers-1)/2 ) {
10923             *whitePlayer = curRound;
10924             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10925         } else {
10926             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10927             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10928             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10929             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10930         }
10931     } else if(appData.tourneyType > 1) {
10932         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10933         *whitePlayer = curRound + appData.tourneyType;
10934     } else if(appData.tourneyType > 0) {
10935         *whitePlayer = curPairing;
10936         *blackPlayer = curRound + appData.tourneyType;
10937     }
10938
10939     // take care of white/black alternation per round.
10940     // For cycles and games this is already taken care of by default, derived from matchGame!
10941     return curRound & 1;
10942 }
10943
10944 int
10945 NextTourneyGame (int nr, int *swapColors)
10946 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10947     char *p, *q;
10948     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10949     FILE *tf;
10950     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10951     tf = fopen(appData.tourneyFile, "r");
10952     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10953     ParseArgsFromFile(tf); fclose(tf);
10954     InitTimeControls(); // TC might be altered from tourney file
10955
10956     nPlayers = CountPlayers(appData.participants); // count participants
10957     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10958     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10959
10960     if(syncInterval) {
10961         p = q = appData.results;
10962         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10963         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10964             DisplayMessage(_("Waiting for other game(s)"),"");
10965             waitingForGame = TRUE;
10966             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10967             return 0;
10968         }
10969         waitingForGame = FALSE;
10970     }
10971
10972     if(appData.tourneyType < 0) {
10973         if(nr>=0 && !pairingReceived) {
10974             char buf[1<<16];
10975             if(pairing.pr == NoProc) {
10976                 if(!appData.pairingEngine[0]) {
10977                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10978                     return 0;
10979                 }
10980                 StartChessProgram(&pairing); // starts the pairing engine
10981             }
10982             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10983             SendToProgram(buf, &pairing);
10984             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10985             SendToProgram(buf, &pairing);
10986             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10987         }
10988         pairingReceived = 0;                              // ... so we continue here
10989         *swapColors = 0;
10990         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10991         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10992         matchGame = 1; roundNr = nr / syncInterval + 1;
10993     }
10994
10995     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10996
10997     // redefine engines, engine dir, etc.
10998     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10999     if(first.pr == NoProc) {
11000       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11001       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11002     }
11003     if(second.pr == NoProc) {
11004       SwapEngines(1);
11005       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11006       SwapEngines(1);         // and make that valid for second engine by swapping
11007       InitEngine(&second, 1);
11008     }
11009     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11010     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11011     return OK;
11012 }
11013
11014 void
11015 NextMatchGame ()
11016 {   // performs game initialization that does not invoke engines, and then tries to start the game
11017     int res, firstWhite, swapColors = 0;
11018     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11019     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
11020         char buf[MSG_SIZ];
11021         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11022         if(strcmp(buf, currentDebugFile)) { // name has changed
11023             FILE *f = fopen(buf, "w");
11024             if(f) { // if opening the new file failed, just keep using the old one
11025                 ASSIGN(currentDebugFile, buf);
11026                 fclose(debugFP);
11027                 debugFP = f;
11028             }
11029             if(appData.serverFileName) {
11030                 if(serverFP) fclose(serverFP);
11031                 serverFP = fopen(appData.serverFileName, "w");
11032                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11033                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11034             }
11035         }
11036     }
11037     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11038     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11039     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11040     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11041     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11042     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11043     Reset(FALSE, first.pr != NoProc);
11044     res = LoadGameOrPosition(matchGame); // setup game
11045     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11046     if(!res) return; // abort when bad game/pos file
11047     TwoMachinesEvent();
11048 }
11049
11050 void
11051 UserAdjudicationEvent (int result)
11052 {
11053     ChessMove gameResult = GameIsDrawn;
11054
11055     if( result > 0 ) {
11056         gameResult = WhiteWins;
11057     }
11058     else if( result < 0 ) {
11059         gameResult = BlackWins;
11060     }
11061
11062     if( gameMode == TwoMachinesPlay ) {
11063         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11064     }
11065 }
11066
11067
11068 // [HGM] save: calculate checksum of game to make games easily identifiable
11069 int
11070 StringCheckSum (char *s)
11071 {
11072         int i = 0;
11073         if(s==NULL) return 0;
11074         while(*s) i = i*259 + *s++;
11075         return i;
11076 }
11077
11078 int
11079 GameCheckSum ()
11080 {
11081         int i, sum=0;
11082         for(i=backwardMostMove; i<forwardMostMove; i++) {
11083                 sum += pvInfoList[i].depth;
11084                 sum += StringCheckSum(parseList[i]);
11085                 sum += StringCheckSum(commentList[i]);
11086                 sum *= 261;
11087         }
11088         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11089         return sum + StringCheckSum(commentList[i]);
11090 } // end of save patch
11091
11092 void
11093 GameEnds (ChessMove result, char *resultDetails, int whosays)
11094 {
11095     GameMode nextGameMode;
11096     int isIcsGame;
11097     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11098
11099     if(endingGame) return; /* [HGM] crash: forbid recursion */
11100     endingGame = 1;
11101     if(twoBoards) { // [HGM] dual: switch back to one board
11102         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11103         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11104     }
11105     if (appData.debugMode) {
11106       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11107               result, resultDetails ? resultDetails : "(null)", whosays);
11108     }
11109
11110     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11111
11112     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11113
11114     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11115         /* If we are playing on ICS, the server decides when the
11116            game is over, but the engine can offer to draw, claim
11117            a draw, or resign.
11118          */
11119 #if ZIPPY
11120         if (appData.zippyPlay && first.initDone) {
11121             if (result == GameIsDrawn) {
11122                 /* In case draw still needs to be claimed */
11123                 SendToICS(ics_prefix);
11124                 SendToICS("draw\n");
11125             } else if (StrCaseStr(resultDetails, "resign")) {
11126                 SendToICS(ics_prefix);
11127                 SendToICS("resign\n");
11128             }
11129         }
11130 #endif
11131         endingGame = 0; /* [HGM] crash */
11132         return;
11133     }
11134
11135     /* If we're loading the game from a file, stop */
11136     if (whosays == GE_FILE) {
11137       (void) StopLoadGameTimer();
11138       gameFileFP = NULL;
11139     }
11140
11141     /* Cancel draw offers */
11142     first.offeredDraw = second.offeredDraw = 0;
11143
11144     /* If this is an ICS game, only ICS can really say it's done;
11145        if not, anyone can. */
11146     isIcsGame = (gameMode == IcsPlayingWhite ||
11147                  gameMode == IcsPlayingBlack ||
11148                  gameMode == IcsObserving    ||
11149                  gameMode == IcsExamining);
11150
11151     if (!isIcsGame || whosays == GE_ICS) {
11152         /* OK -- not an ICS game, or ICS said it was done */
11153         StopClocks();
11154         if (!isIcsGame && !appData.noChessProgram)
11155           SetUserThinkingEnables();
11156
11157         /* [HGM] if a machine claims the game end we verify this claim */
11158         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11159             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11160                 char claimer;
11161                 ChessMove trueResult = (ChessMove) -1;
11162
11163                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11164                                             first.twoMachinesColor[0] :
11165                                             second.twoMachinesColor[0] ;
11166
11167                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11168                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11169                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11170                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11171                 } else
11172                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11173                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11174                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11175                 } else
11176                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11177                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11178                 }
11179
11180                 // now verify win claims, but not in drop games, as we don't understand those yet
11181                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11182                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11183                     (result == WhiteWins && claimer == 'w' ||
11184                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11185                       if (appData.debugMode) {
11186                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11187                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11188                       }
11189                       if(result != trueResult) {
11190                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11191                               result = claimer == 'w' ? BlackWins : WhiteWins;
11192                               resultDetails = buf;
11193                       }
11194                 } else
11195                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11196                     && (forwardMostMove <= backwardMostMove ||
11197                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11198                         (claimer=='b')==(forwardMostMove&1))
11199                                                                                   ) {
11200                       /* [HGM] verify: draws that were not flagged are false claims */
11201                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11202                       result = claimer == 'w' ? BlackWins : WhiteWins;
11203                       resultDetails = buf;
11204                 }
11205                 /* (Claiming a loss is accepted no questions asked!) */
11206             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11207                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11208                 result = GameUnfinished;
11209                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11210             }
11211             /* [HGM] bare: don't allow bare King to win */
11212             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11213                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11214                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11215                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11216                && result != GameIsDrawn)
11217             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11218                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11219                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11220                         if(p >= 0 && p <= (int)WhiteKing) k++;
11221                 }
11222                 if (appData.debugMode) {
11223                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11224                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11225                 }
11226                 if(k <= 1) {
11227                         result = GameIsDrawn;
11228                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11229                         resultDetails = buf;
11230                 }
11231             }
11232         }
11233
11234
11235         if(serverMoves != NULL && !loadFlag) { char c = '=';
11236             if(result==WhiteWins) c = '+';
11237             if(result==BlackWins) c = '-';
11238             if(resultDetails != NULL)
11239                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11240         }
11241         if (resultDetails != NULL) {
11242             gameInfo.result = result;
11243             gameInfo.resultDetails = StrSave(resultDetails);
11244
11245             /* display last move only if game was not loaded from file */
11246             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11247                 DisplayMove(currentMove - 1);
11248
11249             if (forwardMostMove != 0) {
11250                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11251                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11252                                                                 ) {
11253                     if (*appData.saveGameFile != NULLCHAR) {
11254                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11255                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11256                         else
11257                         SaveGameToFile(appData.saveGameFile, TRUE);
11258                     } else if (appData.autoSaveGames) {
11259                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11260                     }
11261                     if (*appData.savePositionFile != NULLCHAR) {
11262                         SavePositionToFile(appData.savePositionFile);
11263                     }
11264                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11265                 }
11266             }
11267
11268             /* Tell program how game ended in case it is learning */
11269             /* [HGM] Moved this to after saving the PGN, just in case */
11270             /* engine died and we got here through time loss. In that */
11271             /* case we will get a fatal error writing the pipe, which */
11272             /* would otherwise lose us the PGN.                       */
11273             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11274             /* output during GameEnds should never be fatal anymore   */
11275             if (gameMode == MachinePlaysWhite ||
11276                 gameMode == MachinePlaysBlack ||
11277                 gameMode == TwoMachinesPlay ||
11278                 gameMode == IcsPlayingWhite ||
11279                 gameMode == IcsPlayingBlack ||
11280                 gameMode == BeginningOfGame) {
11281                 char buf[MSG_SIZ];
11282                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11283                         resultDetails);
11284                 if (first.pr != NoProc) {
11285                     SendToProgram(buf, &first);
11286                 }
11287                 if (second.pr != NoProc &&
11288                     gameMode == TwoMachinesPlay) {
11289                     SendToProgram(buf, &second);
11290                 }
11291             }
11292         }
11293
11294         if (appData.icsActive) {
11295             if (appData.quietPlay &&
11296                 (gameMode == IcsPlayingWhite ||
11297                  gameMode == IcsPlayingBlack)) {
11298                 SendToICS(ics_prefix);
11299                 SendToICS("set shout 1\n");
11300             }
11301             nextGameMode = IcsIdle;
11302             ics_user_moved = FALSE;
11303             /* clean up premove.  It's ugly when the game has ended and the
11304              * premove highlights are still on the board.
11305              */
11306             if (gotPremove) {
11307               gotPremove = FALSE;
11308               ClearPremoveHighlights();
11309               DrawPosition(FALSE, boards[currentMove]);
11310             }
11311             if (whosays == GE_ICS) {
11312                 switch (result) {
11313                 case WhiteWins:
11314                     if (gameMode == IcsPlayingWhite)
11315                         PlayIcsWinSound();
11316                     else if(gameMode == IcsPlayingBlack)
11317                         PlayIcsLossSound();
11318                     break;
11319                 case BlackWins:
11320                     if (gameMode == IcsPlayingBlack)
11321                         PlayIcsWinSound();
11322                     else if(gameMode == IcsPlayingWhite)
11323                         PlayIcsLossSound();
11324                     break;
11325                 case GameIsDrawn:
11326                     PlayIcsDrawSound();
11327                     break;
11328                 default:
11329                     PlayIcsUnfinishedSound();
11330                 }
11331             }
11332             if(appData.quitNext) { ExitEvent(0); return; }
11333         } else if (gameMode == EditGame ||
11334                    gameMode == PlayFromGameFile ||
11335                    gameMode == AnalyzeMode ||
11336                    gameMode == AnalyzeFile) {
11337             nextGameMode = gameMode;
11338         } else {
11339             nextGameMode = EndOfGame;
11340         }
11341         pausing = FALSE;
11342         ModeHighlight();
11343     } else {
11344         nextGameMode = gameMode;
11345     }
11346
11347     if (appData.noChessProgram) {
11348         gameMode = nextGameMode;
11349         ModeHighlight();
11350         endingGame = 0; /* [HGM] crash */
11351         return;
11352     }
11353
11354     if (first.reuse) {
11355         /* Put first chess program into idle state */
11356         if (first.pr != NoProc &&
11357             (gameMode == MachinePlaysWhite ||
11358              gameMode == MachinePlaysBlack ||
11359              gameMode == TwoMachinesPlay ||
11360              gameMode == IcsPlayingWhite ||
11361              gameMode == IcsPlayingBlack ||
11362              gameMode == BeginningOfGame)) {
11363             SendToProgram("force\n", &first);
11364             if (first.usePing) {
11365               char buf[MSG_SIZ];
11366               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11367               SendToProgram(buf, &first);
11368             }
11369         }
11370     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11371         /* Kill off first chess program */
11372         if (first.isr != NULL)
11373           RemoveInputSource(first.isr);
11374         first.isr = NULL;
11375
11376         if (first.pr != NoProc) {
11377             ExitAnalyzeMode();
11378             DoSleep( appData.delayBeforeQuit );
11379             SendToProgram("quit\n", &first);
11380             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11381             first.reload = TRUE;
11382         }
11383         first.pr = NoProc;
11384     }
11385     if (second.reuse) {
11386         /* Put second chess program into idle state */
11387         if (second.pr != NoProc &&
11388             gameMode == TwoMachinesPlay) {
11389             SendToProgram("force\n", &second);
11390             if (second.usePing) {
11391               char buf[MSG_SIZ];
11392               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11393               SendToProgram(buf, &second);
11394             }
11395         }
11396     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11397         /* Kill off second chess program */
11398         if (second.isr != NULL)
11399           RemoveInputSource(second.isr);
11400         second.isr = NULL;
11401
11402         if (second.pr != NoProc) {
11403             DoSleep( appData.delayBeforeQuit );
11404             SendToProgram("quit\n", &second);
11405             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11406             second.reload = TRUE;
11407         }
11408         second.pr = NoProc;
11409     }
11410
11411     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11412         char resChar = '=';
11413         switch (result) {
11414         case WhiteWins:
11415           resChar = '+';
11416           if (first.twoMachinesColor[0] == 'w') {
11417             first.matchWins++;
11418           } else {
11419             second.matchWins++;
11420           }
11421           break;
11422         case BlackWins:
11423           resChar = '-';
11424           if (first.twoMachinesColor[0] == 'b') {
11425             first.matchWins++;
11426           } else {
11427             second.matchWins++;
11428           }
11429           break;
11430         case GameUnfinished:
11431           resChar = ' ';
11432         default:
11433           break;
11434         }
11435
11436         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11437         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11438             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11439             ReserveGame(nextGame, resChar); // sets nextGame
11440             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11441             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11442         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11443
11444         if (nextGame <= appData.matchGames && !abortMatch) {
11445             gameMode = nextGameMode;
11446             matchGame = nextGame; // this will be overruled in tourney mode!
11447             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11448             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11449             endingGame = 0; /* [HGM] crash */
11450             return;
11451         } else {
11452             gameMode = nextGameMode;
11453             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11454                      first.tidy, second.tidy,
11455                      first.matchWins, second.matchWins,
11456                      appData.matchGames - (first.matchWins + second.matchWins));
11457             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11458             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11459             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11460             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11461                 first.twoMachinesColor = "black\n";
11462                 second.twoMachinesColor = "white\n";
11463             } else {
11464                 first.twoMachinesColor = "white\n";
11465                 second.twoMachinesColor = "black\n";
11466             }
11467         }
11468     }
11469     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11470         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11471       ExitAnalyzeMode();
11472     gameMode = nextGameMode;
11473     ModeHighlight();
11474     endingGame = 0;  /* [HGM] crash */
11475     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11476         if(matchMode == TRUE) { // match through command line: exit with or without popup
11477             if(ranking) {
11478                 ToNrEvent(forwardMostMove);
11479                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11480                 else ExitEvent(0);
11481             } else DisplayFatalError(buf, 0, 0);
11482         } else { // match through menu; just stop, with or without popup
11483             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11484             ModeHighlight();
11485             if(ranking){
11486                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11487             } else DisplayNote(buf);
11488       }
11489       if(ranking) free(ranking);
11490     }
11491 }
11492
11493 /* Assumes program was just initialized (initString sent).
11494    Leaves program in force mode. */
11495 void
11496 FeedMovesToProgram (ChessProgramState *cps, int upto)
11497 {
11498     int i;
11499
11500     if (appData.debugMode)
11501       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11502               startedFromSetupPosition ? "position and " : "",
11503               backwardMostMove, upto, cps->which);
11504     if(currentlyInitializedVariant != gameInfo.variant) {
11505       char buf[MSG_SIZ];
11506         // [HGM] variantswitch: make engine aware of new variant
11507         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11508                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11509                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11510         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11511         SendToProgram(buf, cps);
11512         currentlyInitializedVariant = gameInfo.variant;
11513     }
11514     SendToProgram("force\n", cps);
11515     if (startedFromSetupPosition) {
11516         SendBoard(cps, backwardMostMove);
11517     if (appData.debugMode) {
11518         fprintf(debugFP, "feedMoves\n");
11519     }
11520     }
11521     for (i = backwardMostMove; i < upto; i++) {
11522         SendMoveToProgram(i, cps);
11523     }
11524 }
11525
11526
11527 int
11528 ResurrectChessProgram ()
11529 {
11530      /* The chess program may have exited.
11531         If so, restart it and feed it all the moves made so far. */
11532     static int doInit = 0;
11533
11534     if (appData.noChessProgram) return 1;
11535
11536     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11537         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11538         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11539         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11540     } else {
11541         if (first.pr != NoProc) return 1;
11542         StartChessProgram(&first);
11543     }
11544     InitChessProgram(&first, FALSE);
11545     FeedMovesToProgram(&first, currentMove);
11546
11547     if (!first.sendTime) {
11548         /* can't tell gnuchess what its clock should read,
11549            so we bow to its notion. */
11550         ResetClocks();
11551         timeRemaining[0][currentMove] = whiteTimeRemaining;
11552         timeRemaining[1][currentMove] = blackTimeRemaining;
11553     }
11554
11555     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11556                 appData.icsEngineAnalyze) && first.analysisSupport) {
11557       SendToProgram("analyze\n", &first);
11558       first.analyzing = TRUE;
11559     }
11560     return 1;
11561 }
11562
11563 /*
11564  * Button procedures
11565  */
11566 void
11567 Reset (int redraw, int init)
11568 {
11569     int i;
11570
11571     if (appData.debugMode) {
11572         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11573                 redraw, init, gameMode);
11574     }
11575     CleanupTail(); // [HGM] vari: delete any stored variations
11576     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11577     pausing = pauseExamInvalid = FALSE;
11578     startedFromSetupPosition = blackPlaysFirst = FALSE;
11579     firstMove = TRUE;
11580     whiteFlag = blackFlag = FALSE;
11581     userOfferedDraw = FALSE;
11582     hintRequested = bookRequested = FALSE;
11583     first.maybeThinking = FALSE;
11584     second.maybeThinking = FALSE;
11585     first.bookSuspend = FALSE; // [HGM] book
11586     second.bookSuspend = FALSE;
11587     thinkOutput[0] = NULLCHAR;
11588     lastHint[0] = NULLCHAR;
11589     ClearGameInfo(&gameInfo);
11590     gameInfo.variant = StringToVariant(appData.variant);
11591     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11592     ics_user_moved = ics_clock_paused = FALSE;
11593     ics_getting_history = H_FALSE;
11594     ics_gamenum = -1;
11595     white_holding[0] = black_holding[0] = NULLCHAR;
11596     ClearProgramStats();
11597     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11598
11599     ResetFrontEnd();
11600     ClearHighlights();
11601     flipView = appData.flipView;
11602     ClearPremoveHighlights();
11603     gotPremove = FALSE;
11604     alarmSounded = FALSE;
11605     killX = killY = -1; // [HGM] lion
11606
11607     GameEnds(EndOfFile, NULL, GE_PLAYER);
11608     if(appData.serverMovesName != NULL) {
11609         /* [HGM] prepare to make moves file for broadcasting */
11610         clock_t t = clock();
11611         if(serverMoves != NULL) fclose(serverMoves);
11612         serverMoves = fopen(appData.serverMovesName, "r");
11613         if(serverMoves != NULL) {
11614             fclose(serverMoves);
11615             /* delay 15 sec before overwriting, so all clients can see end */
11616             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11617         }
11618         serverMoves = fopen(appData.serverMovesName, "w");
11619     }
11620
11621     ExitAnalyzeMode();
11622     gameMode = BeginningOfGame;
11623     ModeHighlight();
11624     if(appData.icsActive) gameInfo.variant = VariantNormal;
11625     currentMove = forwardMostMove = backwardMostMove = 0;
11626     MarkTargetSquares(1);
11627     InitPosition(redraw);
11628     for (i = 0; i < MAX_MOVES; i++) {
11629         if (commentList[i] != NULL) {
11630             free(commentList[i]);
11631             commentList[i] = NULL;
11632         }
11633     }
11634     ResetClocks();
11635     timeRemaining[0][0] = whiteTimeRemaining;
11636     timeRemaining[1][0] = blackTimeRemaining;
11637
11638     if (first.pr == NoProc) {
11639         StartChessProgram(&first);
11640     }
11641     if (init) {
11642             InitChessProgram(&first, startedFromSetupPosition);
11643     }
11644     DisplayTitle("");
11645     DisplayMessage("", "");
11646     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11647     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11648     ClearMap();        // [HGM] exclude: invalidate map
11649 }
11650
11651 void
11652 AutoPlayGameLoop ()
11653 {
11654     for (;;) {
11655         if (!AutoPlayOneMove())
11656           return;
11657         if (matchMode || appData.timeDelay == 0)
11658           continue;
11659         if (appData.timeDelay < 0)
11660           return;
11661         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11662         break;
11663     }
11664 }
11665
11666 void
11667 AnalyzeNextGame()
11668 {
11669     ReloadGame(1); // next game
11670 }
11671
11672 int
11673 AutoPlayOneMove ()
11674 {
11675     int fromX, fromY, toX, toY;
11676
11677     if (appData.debugMode) {
11678       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11679     }
11680
11681     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11682       return FALSE;
11683
11684     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11685       pvInfoList[currentMove].depth = programStats.depth;
11686       pvInfoList[currentMove].score = programStats.score;
11687       pvInfoList[currentMove].time  = 0;
11688       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11689       else { // append analysis of final position as comment
11690         char buf[MSG_SIZ];
11691         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11692         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11693       }
11694       programStats.depth = 0;
11695     }
11696
11697     if (currentMove >= forwardMostMove) {
11698       if(gameMode == AnalyzeFile) {
11699           if(appData.loadGameIndex == -1) {
11700             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11701           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11702           } else {
11703           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11704         }
11705       }
11706 //      gameMode = EndOfGame;
11707 //      ModeHighlight();
11708
11709       /* [AS] Clear current move marker at the end of a game */
11710       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11711
11712       return FALSE;
11713     }
11714
11715     toX = moveList[currentMove][2] - AAA;
11716     toY = moveList[currentMove][3] - ONE;
11717
11718     if (moveList[currentMove][1] == '@') {
11719         if (appData.highlightLastMove) {
11720             SetHighlights(-1, -1, toX, toY);
11721         }
11722     } else {
11723         fromX = moveList[currentMove][0] - AAA;
11724         fromY = moveList[currentMove][1] - ONE;
11725
11726         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11727
11728         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11729
11730         if (appData.highlightLastMove) {
11731             SetHighlights(fromX, fromY, toX, toY);
11732         }
11733     }
11734     DisplayMove(currentMove);
11735     SendMoveToProgram(currentMove++, &first);
11736     DisplayBothClocks();
11737     DrawPosition(FALSE, boards[currentMove]);
11738     // [HGM] PV info: always display, routine tests if empty
11739     DisplayComment(currentMove - 1, commentList[currentMove]);
11740     return TRUE;
11741 }
11742
11743
11744 int
11745 LoadGameOneMove (ChessMove readAhead)
11746 {
11747     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11748     char promoChar = NULLCHAR;
11749     ChessMove moveType;
11750     char move[MSG_SIZ];
11751     char *p, *q;
11752
11753     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11754         gameMode != AnalyzeMode && gameMode != Training) {
11755         gameFileFP = NULL;
11756         return FALSE;
11757     }
11758
11759     yyboardindex = forwardMostMove;
11760     if (readAhead != EndOfFile) {
11761       moveType = readAhead;
11762     } else {
11763       if (gameFileFP == NULL)
11764           return FALSE;
11765       moveType = (ChessMove) Myylex();
11766     }
11767
11768     done = FALSE;
11769     switch (moveType) {
11770       case Comment:
11771         if (appData.debugMode)
11772           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11773         p = yy_text;
11774
11775         /* append the comment but don't display it */
11776         AppendComment(currentMove, p, FALSE);
11777         return TRUE;
11778
11779       case WhiteCapturesEnPassant:
11780       case BlackCapturesEnPassant:
11781       case WhitePromotion:
11782       case BlackPromotion:
11783       case WhiteNonPromotion:
11784       case BlackNonPromotion:
11785       case NormalMove:
11786       case FirstLeg:
11787       case WhiteKingSideCastle:
11788       case WhiteQueenSideCastle:
11789       case BlackKingSideCastle:
11790       case BlackQueenSideCastle:
11791       case WhiteKingSideCastleWild:
11792       case WhiteQueenSideCastleWild:
11793       case BlackKingSideCastleWild:
11794       case BlackQueenSideCastleWild:
11795       /* PUSH Fabien */
11796       case WhiteHSideCastleFR:
11797       case WhiteASideCastleFR:
11798       case BlackHSideCastleFR:
11799       case BlackASideCastleFR:
11800       /* POP Fabien */
11801         if (appData.debugMode)
11802           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11803         fromX = currentMoveString[0] - AAA;
11804         fromY = currentMoveString[1] - ONE;
11805         toX = currentMoveString[2] - AAA;
11806         toY = currentMoveString[3] - ONE;
11807         promoChar = currentMoveString[4];
11808         if(promoChar == ';') promoChar = NULLCHAR;
11809         break;
11810
11811       case WhiteDrop:
11812       case BlackDrop:
11813         if (appData.debugMode)
11814           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11815         fromX = moveType == WhiteDrop ?
11816           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11817         (int) CharToPiece(ToLower(currentMoveString[0]));
11818         fromY = DROP_RANK;
11819         toX = currentMoveString[2] - AAA;
11820         toY = currentMoveString[3] - ONE;
11821         break;
11822
11823       case WhiteWins:
11824       case BlackWins:
11825       case GameIsDrawn:
11826       case GameUnfinished:
11827         if (appData.debugMode)
11828           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11829         p = strchr(yy_text, '{');
11830         if (p == NULL) p = strchr(yy_text, '(');
11831         if (p == NULL) {
11832             p = yy_text;
11833             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11834         } else {
11835             q = strchr(p, *p == '{' ? '}' : ')');
11836             if (q != NULL) *q = NULLCHAR;
11837             p++;
11838         }
11839         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11840         GameEnds(moveType, p, GE_FILE);
11841         done = TRUE;
11842         if (cmailMsgLoaded) {
11843             ClearHighlights();
11844             flipView = WhiteOnMove(currentMove);
11845             if (moveType == GameUnfinished) flipView = !flipView;
11846             if (appData.debugMode)
11847               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11848         }
11849         break;
11850
11851       case EndOfFile:
11852         if (appData.debugMode)
11853           fprintf(debugFP, "Parser hit end of file\n");
11854         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11855           case MT_NONE:
11856           case MT_CHECK:
11857             break;
11858           case MT_CHECKMATE:
11859           case MT_STAINMATE:
11860             if (WhiteOnMove(currentMove)) {
11861                 GameEnds(BlackWins, "Black mates", GE_FILE);
11862             } else {
11863                 GameEnds(WhiteWins, "White mates", GE_FILE);
11864             }
11865             break;
11866           case MT_STALEMATE:
11867             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11868             break;
11869         }
11870         done = TRUE;
11871         break;
11872
11873       case MoveNumberOne:
11874         if (lastLoadGameStart == GNUChessGame) {
11875             /* GNUChessGames have numbers, but they aren't move numbers */
11876             if (appData.debugMode)
11877               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11878                       yy_text, (int) moveType);
11879             return LoadGameOneMove(EndOfFile); /* tail recursion */
11880         }
11881         /* else fall thru */
11882
11883       case XBoardGame:
11884       case GNUChessGame:
11885       case PGNTag:
11886         /* Reached start of next game in file */
11887         if (appData.debugMode)
11888           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11889         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11890           case MT_NONE:
11891           case MT_CHECK:
11892             break;
11893           case MT_CHECKMATE:
11894           case MT_STAINMATE:
11895             if (WhiteOnMove(currentMove)) {
11896                 GameEnds(BlackWins, "Black mates", GE_FILE);
11897             } else {
11898                 GameEnds(WhiteWins, "White mates", GE_FILE);
11899             }
11900             break;
11901           case MT_STALEMATE:
11902             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11903             break;
11904         }
11905         done = TRUE;
11906         break;
11907
11908       case PositionDiagram:     /* should not happen; ignore */
11909       case ElapsedTime:         /* ignore */
11910       case NAG:                 /* ignore */
11911         if (appData.debugMode)
11912           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11913                   yy_text, (int) moveType);
11914         return LoadGameOneMove(EndOfFile); /* tail recursion */
11915
11916       case IllegalMove:
11917         if (appData.testLegality) {
11918             if (appData.debugMode)
11919               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11920             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11921                     (forwardMostMove / 2) + 1,
11922                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11923             DisplayError(move, 0);
11924             done = TRUE;
11925         } else {
11926             if (appData.debugMode)
11927               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11928                       yy_text, currentMoveString);
11929             fromX = currentMoveString[0] - AAA;
11930             fromY = currentMoveString[1] - ONE;
11931             toX = currentMoveString[2] - AAA;
11932             toY = currentMoveString[3] - ONE;
11933             promoChar = currentMoveString[4];
11934         }
11935         break;
11936
11937       case AmbiguousMove:
11938         if (appData.debugMode)
11939           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11940         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11941                 (forwardMostMove / 2) + 1,
11942                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11943         DisplayError(move, 0);
11944         done = TRUE;
11945         break;
11946
11947       default:
11948       case ImpossibleMove:
11949         if (appData.debugMode)
11950           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11951         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11952                 (forwardMostMove / 2) + 1,
11953                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11954         DisplayError(move, 0);
11955         done = TRUE;
11956         break;
11957     }
11958
11959     if (done) {
11960         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11961             DrawPosition(FALSE, boards[currentMove]);
11962             DisplayBothClocks();
11963             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11964               DisplayComment(currentMove - 1, commentList[currentMove]);
11965         }
11966         (void) StopLoadGameTimer();
11967         gameFileFP = NULL;
11968         cmailOldMove = forwardMostMove;
11969         return FALSE;
11970     } else {
11971         /* currentMoveString is set as a side-effect of yylex */
11972
11973         thinkOutput[0] = NULLCHAR;
11974         MakeMove(fromX, fromY, toX, toY, promoChar);
11975         killX = killY = -1; // [HGM] lion: used up
11976         currentMove = forwardMostMove;
11977         return TRUE;
11978     }
11979 }
11980
11981 /* Load the nth game from the given file */
11982 int
11983 LoadGameFromFile (char *filename, int n, char *title, int useList)
11984 {
11985     FILE *f;
11986     char buf[MSG_SIZ];
11987
11988     if (strcmp(filename, "-") == 0) {
11989         f = stdin;
11990         title = "stdin";
11991     } else {
11992         f = fopen(filename, "rb");
11993         if (f == NULL) {
11994           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11995             DisplayError(buf, errno);
11996             return FALSE;
11997         }
11998     }
11999     if (fseek(f, 0, 0) == -1) {
12000         /* f is not seekable; probably a pipe */
12001         useList = FALSE;
12002     }
12003     if (useList && n == 0) {
12004         int error = GameListBuild(f);
12005         if (error) {
12006             DisplayError(_("Cannot build game list"), error);
12007         } else if (!ListEmpty(&gameList) &&
12008                    ((ListGame *) gameList.tailPred)->number > 1) {
12009             GameListPopUp(f, title);
12010             return TRUE;
12011         }
12012         GameListDestroy();
12013         n = 1;
12014     }
12015     if (n == 0) n = 1;
12016     return LoadGame(f, n, title, FALSE);
12017 }
12018
12019
12020 void
12021 MakeRegisteredMove ()
12022 {
12023     int fromX, fromY, toX, toY;
12024     char promoChar;
12025     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12026         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12027           case CMAIL_MOVE:
12028           case CMAIL_DRAW:
12029             if (appData.debugMode)
12030               fprintf(debugFP, "Restoring %s for game %d\n",
12031                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12032
12033             thinkOutput[0] = NULLCHAR;
12034             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12035             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12036             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12037             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12038             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12039             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12040             MakeMove(fromX, fromY, toX, toY, promoChar);
12041             ShowMove(fromX, fromY, toX, toY);
12042
12043             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12044               case MT_NONE:
12045               case MT_CHECK:
12046                 break;
12047
12048               case MT_CHECKMATE:
12049               case MT_STAINMATE:
12050                 if (WhiteOnMove(currentMove)) {
12051                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12052                 } else {
12053                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12054                 }
12055                 break;
12056
12057               case MT_STALEMATE:
12058                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12059                 break;
12060             }
12061
12062             break;
12063
12064           case CMAIL_RESIGN:
12065             if (WhiteOnMove(currentMove)) {
12066                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12067             } else {
12068                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12069             }
12070             break;
12071
12072           case CMAIL_ACCEPT:
12073             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12074             break;
12075
12076           default:
12077             break;
12078         }
12079     }
12080
12081     return;
12082 }
12083
12084 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12085 int
12086 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12087 {
12088     int retVal;
12089
12090     if (gameNumber > nCmailGames) {
12091         DisplayError(_("No more games in this message"), 0);
12092         return FALSE;
12093     }
12094     if (f == lastLoadGameFP) {
12095         int offset = gameNumber - lastLoadGameNumber;
12096         if (offset == 0) {
12097             cmailMsg[0] = NULLCHAR;
12098             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12099                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12100                 nCmailMovesRegistered--;
12101             }
12102             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12103             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12104                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12105             }
12106         } else {
12107             if (! RegisterMove()) return FALSE;
12108         }
12109     }
12110
12111     retVal = LoadGame(f, gameNumber, title, useList);
12112
12113     /* Make move registered during previous look at this game, if any */
12114     MakeRegisteredMove();
12115
12116     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12117         commentList[currentMove]
12118           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12119         DisplayComment(currentMove - 1, commentList[currentMove]);
12120     }
12121
12122     return retVal;
12123 }
12124
12125 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12126 int
12127 ReloadGame (int offset)
12128 {
12129     int gameNumber = lastLoadGameNumber + offset;
12130     if (lastLoadGameFP == NULL) {
12131         DisplayError(_("No game has been loaded yet"), 0);
12132         return FALSE;
12133     }
12134     if (gameNumber <= 0) {
12135         DisplayError(_("Can't back up any further"), 0);
12136         return FALSE;
12137     }
12138     if (cmailMsgLoaded) {
12139         return CmailLoadGame(lastLoadGameFP, gameNumber,
12140                              lastLoadGameTitle, lastLoadGameUseList);
12141     } else {
12142         return LoadGame(lastLoadGameFP, gameNumber,
12143                         lastLoadGameTitle, lastLoadGameUseList);
12144     }
12145 }
12146
12147 int keys[EmptySquare+1];
12148
12149 int
12150 PositionMatches (Board b1, Board b2)
12151 {
12152     int r, f, sum=0;
12153     switch(appData.searchMode) {
12154         case 1: return CompareWithRights(b1, b2);
12155         case 2:
12156             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12157                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12158             }
12159             return TRUE;
12160         case 3:
12161             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12162               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12163                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12164             }
12165             return sum==0;
12166         case 4:
12167             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12168                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12169             }
12170             return sum==0;
12171     }
12172     return TRUE;
12173 }
12174
12175 #define Q_PROMO  4
12176 #define Q_EP     3
12177 #define Q_BCASTL 2
12178 #define Q_WCASTL 1
12179
12180 int pieceList[256], quickBoard[256];
12181 ChessSquare pieceType[256] = { EmptySquare };
12182 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12183 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12184 int soughtTotal, turn;
12185 Boolean epOK, flipSearch;
12186
12187 typedef struct {
12188     unsigned char piece, to;
12189 } Move;
12190
12191 #define DSIZE (250000)
12192
12193 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12194 Move *moveDatabase = initialSpace;
12195 unsigned int movePtr, dataSize = DSIZE;
12196
12197 int
12198 MakePieceList (Board board, int *counts)
12199 {
12200     int r, f, n=Q_PROMO, total=0;
12201     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12202     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12203         int sq = f + (r<<4);
12204         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12205             quickBoard[sq] = ++n;
12206             pieceList[n] = sq;
12207             pieceType[n] = board[r][f];
12208             counts[board[r][f]]++;
12209             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12210             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12211             total++;
12212         }
12213     }
12214     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12215     return total;
12216 }
12217
12218 void
12219 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12220 {
12221     int sq = fromX + (fromY<<4);
12222     int piece = quickBoard[sq], rook;
12223     quickBoard[sq] = 0;
12224     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12225     if(piece == pieceList[1] && fromY == toY) {
12226       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12227         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12228         moveDatabase[movePtr++].piece = Q_WCASTL;
12229         quickBoard[sq] = piece;
12230         piece = quickBoard[from]; quickBoard[from] = 0;
12231         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12232       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12233         quickBoard[sq] = 0; // remove Rook
12234         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12235         moveDatabase[movePtr++].piece = Q_WCASTL;
12236         quickBoard[sq] = pieceList[1]; // put King
12237         piece = rook;
12238         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12239       }
12240     } else
12241     if(piece == pieceList[2] && fromY == toY) {
12242       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12243         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12244         moveDatabase[movePtr++].piece = Q_BCASTL;
12245         quickBoard[sq] = piece;
12246         piece = quickBoard[from]; quickBoard[from] = 0;
12247         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12248       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12249         quickBoard[sq] = 0; // remove Rook
12250         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12251         moveDatabase[movePtr++].piece = Q_BCASTL;
12252         quickBoard[sq] = pieceList[2]; // put King
12253         piece = rook;
12254         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12255       }
12256     } else
12257     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12258         quickBoard[(fromY<<4)+toX] = 0;
12259         moveDatabase[movePtr].piece = Q_EP;
12260         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12261         moveDatabase[movePtr].to = sq;
12262     } else
12263     if(promoPiece != pieceType[piece]) {
12264         moveDatabase[movePtr++].piece = Q_PROMO;
12265         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12266     }
12267     moveDatabase[movePtr].piece = piece;
12268     quickBoard[sq] = piece;
12269     movePtr++;
12270 }
12271
12272 int
12273 PackGame (Board board)
12274 {
12275     Move *newSpace = NULL;
12276     moveDatabase[movePtr].piece = 0; // terminate previous game
12277     if(movePtr > dataSize) {
12278         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12279         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12280         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12281         if(newSpace) {
12282             int i;
12283             Move *p = moveDatabase, *q = newSpace;
12284             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12285             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12286             moveDatabase = newSpace;
12287         } else { // calloc failed, we must be out of memory. Too bad...
12288             dataSize = 0; // prevent calloc events for all subsequent games
12289             return 0;     // and signal this one isn't cached
12290         }
12291     }
12292     movePtr++;
12293     MakePieceList(board, counts);
12294     return movePtr;
12295 }
12296
12297 int
12298 QuickCompare (Board board, int *minCounts, int *maxCounts)
12299 {   // compare according to search mode
12300     int r, f;
12301     switch(appData.searchMode)
12302     {
12303       case 1: // exact position match
12304         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12305         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12306             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12307         }
12308         break;
12309       case 2: // can have extra material on empty squares
12310         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12311             if(board[r][f] == EmptySquare) continue;
12312             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12313         }
12314         break;
12315       case 3: // material with exact Pawn structure
12316         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12317             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12318             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12319         } // fall through to material comparison
12320       case 4: // exact material
12321         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12322         break;
12323       case 6: // material range with given imbalance
12324         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12325         // fall through to range comparison
12326       case 5: // material range
12327         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12328     }
12329     return TRUE;
12330 }
12331
12332 int
12333 QuickScan (Board board, Move *move)
12334 {   // reconstruct game,and compare all positions in it
12335     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12336     do {
12337         int piece = move->piece;
12338         int to = move->to, from = pieceList[piece];
12339         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12340           if(!piece) return -1;
12341           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12342             piece = (++move)->piece;
12343             from = pieceList[piece];
12344             counts[pieceType[piece]]--;
12345             pieceType[piece] = (ChessSquare) move->to;
12346             counts[move->to]++;
12347           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12348             counts[pieceType[quickBoard[to]]]--;
12349             quickBoard[to] = 0; total--;
12350             move++;
12351             continue;
12352           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12353             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12354             from  = pieceList[piece]; // so this must be King
12355             quickBoard[from] = 0;
12356             pieceList[piece] = to;
12357             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12358             quickBoard[from] = 0; // rook
12359             quickBoard[to] = piece;
12360             to = move->to; piece = move->piece;
12361             goto aftercastle;
12362           }
12363         }
12364         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12365         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12366         quickBoard[from] = 0;
12367       aftercastle:
12368         quickBoard[to] = piece;
12369         pieceList[piece] = to;
12370         cnt++; turn ^= 3;
12371         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12372            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12373            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12374                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12375           ) {
12376             static int lastCounts[EmptySquare+1];
12377             int i;
12378             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12379             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12380         } else stretch = 0;
12381         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12382         move++;
12383     } while(1);
12384 }
12385
12386 void
12387 InitSearch ()
12388 {
12389     int r, f;
12390     flipSearch = FALSE;
12391     CopyBoard(soughtBoard, boards[currentMove]);
12392     soughtTotal = MakePieceList(soughtBoard, maxSought);
12393     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12394     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12395     CopyBoard(reverseBoard, boards[currentMove]);
12396     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12397         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12398         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12399         reverseBoard[r][f] = piece;
12400     }
12401     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12402     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12403     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12404                  || (boards[currentMove][CASTLING][2] == NoRights ||
12405                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12406                  && (boards[currentMove][CASTLING][5] == NoRights ||
12407                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12408       ) {
12409         flipSearch = TRUE;
12410         CopyBoard(flipBoard, soughtBoard);
12411         CopyBoard(rotateBoard, reverseBoard);
12412         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12413             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12414             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12415         }
12416     }
12417     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12418     if(appData.searchMode >= 5) {
12419         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12420         MakePieceList(soughtBoard, minSought);
12421         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12422     }
12423     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12424         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12425 }
12426
12427 GameInfo dummyInfo;
12428 static int creatingBook;
12429
12430 int
12431 GameContainsPosition (FILE *f, ListGame *lg)
12432 {
12433     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12434     int fromX, fromY, toX, toY;
12435     char promoChar;
12436     static int initDone=FALSE;
12437
12438     // weed out games based on numerical tag comparison
12439     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12440     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12441     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12442     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12443     if(!initDone) {
12444         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12445         initDone = TRUE;
12446     }
12447     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12448     else CopyBoard(boards[scratch], initialPosition); // default start position
12449     if(lg->moves) {
12450         turn = btm + 1;
12451         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12452         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12453     }
12454     if(btm) plyNr++;
12455     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12456     fseek(f, lg->offset, 0);
12457     yynewfile(f);
12458     while(1) {
12459         yyboardindex = scratch;
12460         quickFlag = plyNr+1;
12461         next = Myylex();
12462         quickFlag = 0;
12463         switch(next) {
12464             case PGNTag:
12465                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12466             default:
12467                 continue;
12468
12469             case XBoardGame:
12470             case GNUChessGame:
12471                 if(plyNr) return -1; // after we have seen moves, this is for new game
12472               continue;
12473
12474             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12475             case ImpossibleMove:
12476             case WhiteWins: // game ends here with these four
12477             case BlackWins:
12478             case GameIsDrawn:
12479             case GameUnfinished:
12480                 return -1;
12481
12482             case IllegalMove:
12483                 if(appData.testLegality) return -1;
12484             case WhiteCapturesEnPassant:
12485             case BlackCapturesEnPassant:
12486             case WhitePromotion:
12487             case BlackPromotion:
12488             case WhiteNonPromotion:
12489             case BlackNonPromotion:
12490             case NormalMove:
12491             case FirstLeg:
12492             case WhiteKingSideCastle:
12493             case WhiteQueenSideCastle:
12494             case BlackKingSideCastle:
12495             case BlackQueenSideCastle:
12496             case WhiteKingSideCastleWild:
12497             case WhiteQueenSideCastleWild:
12498             case BlackKingSideCastleWild:
12499             case BlackQueenSideCastleWild:
12500             case WhiteHSideCastleFR:
12501             case WhiteASideCastleFR:
12502             case BlackHSideCastleFR:
12503             case BlackASideCastleFR:
12504                 fromX = currentMoveString[0] - AAA;
12505                 fromY = currentMoveString[1] - ONE;
12506                 toX = currentMoveString[2] - AAA;
12507                 toY = currentMoveString[3] - ONE;
12508                 promoChar = currentMoveString[4];
12509                 break;
12510             case WhiteDrop:
12511             case BlackDrop:
12512                 fromX = next == WhiteDrop ?
12513                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12514                   (int) CharToPiece(ToLower(currentMoveString[0]));
12515                 fromY = DROP_RANK;
12516                 toX = currentMoveString[2] - AAA;
12517                 toY = currentMoveString[3] - ONE;
12518                 promoChar = 0;
12519                 break;
12520         }
12521         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12522         plyNr++;
12523         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12524         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12525         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12526         if(appData.findMirror) {
12527             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12528             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12529         }
12530     }
12531 }
12532
12533 /* Load the nth game from open file f */
12534 int
12535 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12536 {
12537     ChessMove cm;
12538     char buf[MSG_SIZ];
12539     int gn = gameNumber;
12540     ListGame *lg = NULL;
12541     int numPGNTags = 0;
12542     int err, pos = -1;
12543     GameMode oldGameMode;
12544     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12545
12546     if (appData.debugMode)
12547         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12548
12549     if (gameMode == Training )
12550         SetTrainingModeOff();
12551
12552     oldGameMode = gameMode;
12553     if (gameMode != BeginningOfGame) {
12554       Reset(FALSE, TRUE);
12555     }
12556     killX = killY = -1; // [HGM] lion: in case we did not Reset
12557
12558     gameFileFP = f;
12559     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12560         fclose(lastLoadGameFP);
12561     }
12562
12563     if (useList) {
12564         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12565
12566         if (lg) {
12567             fseek(f, lg->offset, 0);
12568             GameListHighlight(gameNumber);
12569             pos = lg->position;
12570             gn = 1;
12571         }
12572         else {
12573             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12574               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12575             else
12576             DisplayError(_("Game number out of range"), 0);
12577             return FALSE;
12578         }
12579     } else {
12580         GameListDestroy();
12581         if (fseek(f, 0, 0) == -1) {
12582             if (f == lastLoadGameFP ?
12583                 gameNumber == lastLoadGameNumber + 1 :
12584                 gameNumber == 1) {
12585                 gn = 1;
12586             } else {
12587                 DisplayError(_("Can't seek on game file"), 0);
12588                 return FALSE;
12589             }
12590         }
12591     }
12592     lastLoadGameFP = f;
12593     lastLoadGameNumber = gameNumber;
12594     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12595     lastLoadGameUseList = useList;
12596
12597     yynewfile(f);
12598
12599     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12600       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12601                 lg->gameInfo.black);
12602             DisplayTitle(buf);
12603     } else if (*title != NULLCHAR) {
12604         if (gameNumber > 1) {
12605           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12606             DisplayTitle(buf);
12607         } else {
12608             DisplayTitle(title);
12609         }
12610     }
12611
12612     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12613         gameMode = PlayFromGameFile;
12614         ModeHighlight();
12615     }
12616
12617     currentMove = forwardMostMove = backwardMostMove = 0;
12618     CopyBoard(boards[0], initialPosition);
12619     StopClocks();
12620
12621     /*
12622      * Skip the first gn-1 games in the file.
12623      * Also skip over anything that precedes an identifiable
12624      * start of game marker, to avoid being confused by
12625      * garbage at the start of the file.  Currently
12626      * recognized start of game markers are the move number "1",
12627      * the pattern "gnuchess .* game", the pattern
12628      * "^[#;%] [^ ]* game file", and a PGN tag block.
12629      * A game that starts with one of the latter two patterns
12630      * will also have a move number 1, possibly
12631      * following a position diagram.
12632      * 5-4-02: Let's try being more lenient and allowing a game to
12633      * start with an unnumbered move.  Does that break anything?
12634      */
12635     cm = lastLoadGameStart = EndOfFile;
12636     while (gn > 0) {
12637         yyboardindex = forwardMostMove;
12638         cm = (ChessMove) Myylex();
12639         switch (cm) {
12640           case EndOfFile:
12641             if (cmailMsgLoaded) {
12642                 nCmailGames = CMAIL_MAX_GAMES - gn;
12643             } else {
12644                 Reset(TRUE, TRUE);
12645                 DisplayError(_("Game not found in file"), 0);
12646             }
12647             return FALSE;
12648
12649           case GNUChessGame:
12650           case XBoardGame:
12651             gn--;
12652             lastLoadGameStart = cm;
12653             break;
12654
12655           case MoveNumberOne:
12656             switch (lastLoadGameStart) {
12657               case GNUChessGame:
12658               case XBoardGame:
12659               case PGNTag:
12660                 break;
12661               case MoveNumberOne:
12662               case EndOfFile:
12663                 gn--;           /* count this game */
12664                 lastLoadGameStart = cm;
12665                 break;
12666               default:
12667                 /* impossible */
12668                 break;
12669             }
12670             break;
12671
12672           case PGNTag:
12673             switch (lastLoadGameStart) {
12674               case GNUChessGame:
12675               case PGNTag:
12676               case MoveNumberOne:
12677               case EndOfFile:
12678                 gn--;           /* count this game */
12679                 lastLoadGameStart = cm;
12680                 break;
12681               case XBoardGame:
12682                 lastLoadGameStart = cm; /* game counted already */
12683                 break;
12684               default:
12685                 /* impossible */
12686                 break;
12687             }
12688             if (gn > 0) {
12689                 do {
12690                     yyboardindex = forwardMostMove;
12691                     cm = (ChessMove) Myylex();
12692                 } while (cm == PGNTag || cm == Comment);
12693             }
12694             break;
12695
12696           case WhiteWins:
12697           case BlackWins:
12698           case GameIsDrawn:
12699             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12700                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12701                     != CMAIL_OLD_RESULT) {
12702                     nCmailResults ++ ;
12703                     cmailResult[  CMAIL_MAX_GAMES
12704                                 - gn - 1] = CMAIL_OLD_RESULT;
12705                 }
12706             }
12707             break;
12708
12709           case NormalMove:
12710           case FirstLeg:
12711             /* Only a NormalMove can be at the start of a game
12712              * without a position diagram. */
12713             if (lastLoadGameStart == EndOfFile ) {
12714               gn--;
12715               lastLoadGameStart = MoveNumberOne;
12716             }
12717             break;
12718
12719           default:
12720             break;
12721         }
12722     }
12723
12724     if (appData.debugMode)
12725       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12726
12727     if (cm == XBoardGame) {
12728         /* Skip any header junk before position diagram and/or move 1 */
12729         for (;;) {
12730             yyboardindex = forwardMostMove;
12731             cm = (ChessMove) Myylex();
12732
12733             if (cm == EndOfFile ||
12734                 cm == GNUChessGame || cm == XBoardGame) {
12735                 /* Empty game; pretend end-of-file and handle later */
12736                 cm = EndOfFile;
12737                 break;
12738             }
12739
12740             if (cm == MoveNumberOne || cm == PositionDiagram ||
12741                 cm == PGNTag || cm == Comment)
12742               break;
12743         }
12744     } else if (cm == GNUChessGame) {
12745         if (gameInfo.event != NULL) {
12746             free(gameInfo.event);
12747         }
12748         gameInfo.event = StrSave(yy_text);
12749     }
12750
12751     startedFromSetupPosition = FALSE;
12752     while (cm == PGNTag) {
12753         if (appData.debugMode)
12754           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12755         err = ParsePGNTag(yy_text, &gameInfo);
12756         if (!err) numPGNTags++;
12757
12758         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12759         if(gameInfo.variant != oldVariant) {
12760             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12761             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12762             InitPosition(TRUE);
12763             oldVariant = gameInfo.variant;
12764             if (appData.debugMode)
12765               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12766         }
12767
12768
12769         if (gameInfo.fen != NULL) {
12770           Board initial_position;
12771           startedFromSetupPosition = TRUE;
12772           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12773             Reset(TRUE, TRUE);
12774             DisplayError(_("Bad FEN position in file"), 0);
12775             return FALSE;
12776           }
12777           CopyBoard(boards[0], initial_position);
12778           if (blackPlaysFirst) {
12779             currentMove = forwardMostMove = backwardMostMove = 1;
12780             CopyBoard(boards[1], initial_position);
12781             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12782             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12783             timeRemaining[0][1] = whiteTimeRemaining;
12784             timeRemaining[1][1] = blackTimeRemaining;
12785             if (commentList[0] != NULL) {
12786               commentList[1] = commentList[0];
12787               commentList[0] = NULL;
12788             }
12789           } else {
12790             currentMove = forwardMostMove = backwardMostMove = 0;
12791           }
12792           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12793           {   int i;
12794               initialRulePlies = FENrulePlies;
12795               for( i=0; i< nrCastlingRights; i++ )
12796                   initialRights[i] = initial_position[CASTLING][i];
12797           }
12798           yyboardindex = forwardMostMove;
12799           free(gameInfo.fen);
12800           gameInfo.fen = NULL;
12801         }
12802
12803         yyboardindex = forwardMostMove;
12804         cm = (ChessMove) Myylex();
12805
12806         /* Handle comments interspersed among the tags */
12807         while (cm == Comment) {
12808             char *p;
12809             if (appData.debugMode)
12810               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12811             p = yy_text;
12812             AppendComment(currentMove, p, FALSE);
12813             yyboardindex = forwardMostMove;
12814             cm = (ChessMove) Myylex();
12815         }
12816     }
12817
12818     /* don't rely on existence of Event tag since if game was
12819      * pasted from clipboard the Event tag may not exist
12820      */
12821     if (numPGNTags > 0){
12822         char *tags;
12823         if (gameInfo.variant == VariantNormal) {
12824           VariantClass v = StringToVariant(gameInfo.event);
12825           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12826           if(v < VariantShogi) gameInfo.variant = v;
12827         }
12828         if (!matchMode) {
12829           if( appData.autoDisplayTags ) {
12830             tags = PGNTags(&gameInfo);
12831             TagsPopUp(tags, CmailMsg());
12832             free(tags);
12833           }
12834         }
12835     } else {
12836         /* Make something up, but don't display it now */
12837         SetGameInfo();
12838         TagsPopDown();
12839     }
12840
12841     if (cm == PositionDiagram) {
12842         int i, j;
12843         char *p;
12844         Board initial_position;
12845
12846         if (appData.debugMode)
12847           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12848
12849         if (!startedFromSetupPosition) {
12850             p = yy_text;
12851             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12852               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12853                 switch (*p) {
12854                   case '{':
12855                   case '[':
12856                   case '-':
12857                   case ' ':
12858                   case '\t':
12859                   case '\n':
12860                   case '\r':
12861                     break;
12862                   default:
12863                     initial_position[i][j++] = CharToPiece(*p);
12864                     break;
12865                 }
12866             while (*p == ' ' || *p == '\t' ||
12867                    *p == '\n' || *p == '\r') p++;
12868
12869             if (strncmp(p, "black", strlen("black"))==0)
12870               blackPlaysFirst = TRUE;
12871             else
12872               blackPlaysFirst = FALSE;
12873             startedFromSetupPosition = TRUE;
12874
12875             CopyBoard(boards[0], initial_position);
12876             if (blackPlaysFirst) {
12877                 currentMove = forwardMostMove = backwardMostMove = 1;
12878                 CopyBoard(boards[1], initial_position);
12879                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12880                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12881                 timeRemaining[0][1] = whiteTimeRemaining;
12882                 timeRemaining[1][1] = blackTimeRemaining;
12883                 if (commentList[0] != NULL) {
12884                     commentList[1] = commentList[0];
12885                     commentList[0] = NULL;
12886                 }
12887             } else {
12888                 currentMove = forwardMostMove = backwardMostMove = 0;
12889             }
12890         }
12891         yyboardindex = forwardMostMove;
12892         cm = (ChessMove) Myylex();
12893     }
12894
12895   if(!creatingBook) {
12896     if (first.pr == NoProc) {
12897         StartChessProgram(&first);
12898     }
12899     InitChessProgram(&first, FALSE);
12900     SendToProgram("force\n", &first);
12901     if (startedFromSetupPosition) {
12902         SendBoard(&first, forwardMostMove);
12903     if (appData.debugMode) {
12904         fprintf(debugFP, "Load Game\n");
12905     }
12906         DisplayBothClocks();
12907     }
12908   }
12909
12910     /* [HGM] server: flag to write setup moves in broadcast file as one */
12911     loadFlag = appData.suppressLoadMoves;
12912
12913     while (cm == Comment) {
12914         char *p;
12915         if (appData.debugMode)
12916           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12917         p = yy_text;
12918         AppendComment(currentMove, p, FALSE);
12919         yyboardindex = forwardMostMove;
12920         cm = (ChessMove) Myylex();
12921     }
12922
12923     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12924         cm == WhiteWins || cm == BlackWins ||
12925         cm == GameIsDrawn || cm == GameUnfinished) {
12926         DisplayMessage("", _("No moves in game"));
12927         if (cmailMsgLoaded) {
12928             if (appData.debugMode)
12929               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12930             ClearHighlights();
12931             flipView = FALSE;
12932         }
12933         DrawPosition(FALSE, boards[currentMove]);
12934         DisplayBothClocks();
12935         gameMode = EditGame;
12936         ModeHighlight();
12937         gameFileFP = NULL;
12938         cmailOldMove = 0;
12939         return TRUE;
12940     }
12941
12942     // [HGM] PV info: routine tests if comment empty
12943     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12944         DisplayComment(currentMove - 1, commentList[currentMove]);
12945     }
12946     if (!matchMode && appData.timeDelay != 0)
12947       DrawPosition(FALSE, boards[currentMove]);
12948
12949     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12950       programStats.ok_to_send = 1;
12951     }
12952
12953     /* if the first token after the PGN tags is a move
12954      * and not move number 1, retrieve it from the parser
12955      */
12956     if (cm != MoveNumberOne)
12957         LoadGameOneMove(cm);
12958
12959     /* load the remaining moves from the file */
12960     while (LoadGameOneMove(EndOfFile)) {
12961       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12962       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12963     }
12964
12965     /* rewind to the start of the game */
12966     currentMove = backwardMostMove;
12967
12968     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12969
12970     if (oldGameMode == AnalyzeFile) {
12971       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12972       AnalyzeFileEvent();
12973     } else
12974     if (oldGameMode == AnalyzeMode) {
12975       AnalyzeFileEvent();
12976     }
12977
12978     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12979         long int w, b; // [HGM] adjourn: restore saved clock times
12980         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12981         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12982             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12983             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12984         }
12985     }
12986
12987     if(creatingBook) return TRUE;
12988     if (!matchMode && pos > 0) {
12989         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12990     } else
12991     if (matchMode || appData.timeDelay == 0) {
12992       ToEndEvent();
12993     } else if (appData.timeDelay > 0) {
12994       AutoPlayGameLoop();
12995     }
12996
12997     if (appData.debugMode)
12998         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12999
13000     loadFlag = 0; /* [HGM] true game starts */
13001     return TRUE;
13002 }
13003
13004 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13005 int
13006 ReloadPosition (int offset)
13007 {
13008     int positionNumber = lastLoadPositionNumber + offset;
13009     if (lastLoadPositionFP == NULL) {
13010         DisplayError(_("No position has been loaded yet"), 0);
13011         return FALSE;
13012     }
13013     if (positionNumber <= 0) {
13014         DisplayError(_("Can't back up any further"), 0);
13015         return FALSE;
13016     }
13017     return LoadPosition(lastLoadPositionFP, positionNumber,
13018                         lastLoadPositionTitle);
13019 }
13020
13021 /* Load the nth position from the given file */
13022 int
13023 LoadPositionFromFile (char *filename, int n, char *title)
13024 {
13025     FILE *f;
13026     char buf[MSG_SIZ];
13027
13028     if (strcmp(filename, "-") == 0) {
13029         return LoadPosition(stdin, n, "stdin");
13030     } else {
13031         f = fopen(filename, "rb");
13032         if (f == NULL) {
13033             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13034             DisplayError(buf, errno);
13035             return FALSE;
13036         } else {
13037             return LoadPosition(f, n, title);
13038         }
13039     }
13040 }
13041
13042 /* Load the nth position from the given open file, and close it */
13043 int
13044 LoadPosition (FILE *f, int positionNumber, char *title)
13045 {
13046     char *p, line[MSG_SIZ];
13047     Board initial_position;
13048     int i, j, fenMode, pn;
13049
13050     if (gameMode == Training )
13051         SetTrainingModeOff();
13052
13053     if (gameMode != BeginningOfGame) {
13054         Reset(FALSE, TRUE);
13055     }
13056     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13057         fclose(lastLoadPositionFP);
13058     }
13059     if (positionNumber == 0) positionNumber = 1;
13060     lastLoadPositionFP = f;
13061     lastLoadPositionNumber = positionNumber;
13062     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13063     if (first.pr == NoProc && !appData.noChessProgram) {
13064       StartChessProgram(&first);
13065       InitChessProgram(&first, FALSE);
13066     }
13067     pn = positionNumber;
13068     if (positionNumber < 0) {
13069         /* Negative position number means to seek to that byte offset */
13070         if (fseek(f, -positionNumber, 0) == -1) {
13071             DisplayError(_("Can't seek on position file"), 0);
13072             return FALSE;
13073         };
13074         pn = 1;
13075     } else {
13076         if (fseek(f, 0, 0) == -1) {
13077             if (f == lastLoadPositionFP ?
13078                 positionNumber == lastLoadPositionNumber + 1 :
13079                 positionNumber == 1) {
13080                 pn = 1;
13081             } else {
13082                 DisplayError(_("Can't seek on position file"), 0);
13083                 return FALSE;
13084             }
13085         }
13086     }
13087     /* See if this file is FEN or old-style xboard */
13088     if (fgets(line, MSG_SIZ, f) == NULL) {
13089         DisplayError(_("Position not found in file"), 0);
13090         return FALSE;
13091     }
13092     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13093     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13094
13095     if (pn >= 2) {
13096         if (fenMode || line[0] == '#') pn--;
13097         while (pn > 0) {
13098             /* skip positions before number pn */
13099             if (fgets(line, MSG_SIZ, f) == NULL) {
13100                 Reset(TRUE, TRUE);
13101                 DisplayError(_("Position not found in file"), 0);
13102                 return FALSE;
13103             }
13104             if (fenMode || line[0] == '#') pn--;
13105         }
13106     }
13107
13108     if (fenMode) {
13109         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13110             DisplayError(_("Bad FEN position in file"), 0);
13111             return FALSE;
13112         }
13113     } else {
13114         (void) fgets(line, MSG_SIZ, f);
13115         (void) fgets(line, MSG_SIZ, f);
13116
13117         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13118             (void) fgets(line, MSG_SIZ, f);
13119             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13120                 if (*p == ' ')
13121                   continue;
13122                 initial_position[i][j++] = CharToPiece(*p);
13123             }
13124         }
13125
13126         blackPlaysFirst = FALSE;
13127         if (!feof(f)) {
13128             (void) fgets(line, MSG_SIZ, f);
13129             if (strncmp(line, "black", strlen("black"))==0)
13130               blackPlaysFirst = TRUE;
13131         }
13132     }
13133     startedFromSetupPosition = TRUE;
13134
13135     CopyBoard(boards[0], initial_position);
13136     if (blackPlaysFirst) {
13137         currentMove = forwardMostMove = backwardMostMove = 1;
13138         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13139         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13140         CopyBoard(boards[1], initial_position);
13141         DisplayMessage("", _("Black to play"));
13142     } else {
13143         currentMove = forwardMostMove = backwardMostMove = 0;
13144         DisplayMessage("", _("White to play"));
13145     }
13146     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13147     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13148         SendToProgram("force\n", &first);
13149         SendBoard(&first, forwardMostMove);
13150     }
13151     if (appData.debugMode) {
13152 int i, j;
13153   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13154   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13155         fprintf(debugFP, "Load Position\n");
13156     }
13157
13158     if (positionNumber > 1) {
13159       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13160         DisplayTitle(line);
13161     } else {
13162         DisplayTitle(title);
13163     }
13164     gameMode = EditGame;
13165     ModeHighlight();
13166     ResetClocks();
13167     timeRemaining[0][1] = whiteTimeRemaining;
13168     timeRemaining[1][1] = blackTimeRemaining;
13169     DrawPosition(FALSE, boards[currentMove]);
13170
13171     return TRUE;
13172 }
13173
13174
13175 void
13176 CopyPlayerNameIntoFileName (char **dest, char *src)
13177 {
13178     while (*src != NULLCHAR && *src != ',') {
13179         if (*src == ' ') {
13180             *(*dest)++ = '_';
13181             src++;
13182         } else {
13183             *(*dest)++ = *src++;
13184         }
13185     }
13186 }
13187
13188 char *
13189 DefaultFileName (char *ext)
13190 {
13191     static char def[MSG_SIZ];
13192     char *p;
13193
13194     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13195         p = def;
13196         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13197         *p++ = '-';
13198         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13199         *p++ = '.';
13200         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13201     } else {
13202         def[0] = NULLCHAR;
13203     }
13204     return def;
13205 }
13206
13207 /* Save the current game to the given file */
13208 int
13209 SaveGameToFile (char *filename, int append)
13210 {
13211     FILE *f;
13212     char buf[MSG_SIZ];
13213     int result, i, t,tot=0;
13214
13215     if (strcmp(filename, "-") == 0) {
13216         return SaveGame(stdout, 0, NULL);
13217     } else {
13218         for(i=0; i<10; i++) { // upto 10 tries
13219              f = fopen(filename, append ? "a" : "w");
13220              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13221              if(f || errno != 13) break;
13222              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13223              tot += t;
13224         }
13225         if (f == NULL) {
13226             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13227             DisplayError(buf, errno);
13228             return FALSE;
13229         } else {
13230             safeStrCpy(buf, lastMsg, MSG_SIZ);
13231             DisplayMessage(_("Waiting for access to save file"), "");
13232             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13233             DisplayMessage(_("Saving game"), "");
13234             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13235             result = SaveGame(f, 0, NULL);
13236             DisplayMessage(buf, "");
13237             return result;
13238         }
13239     }
13240 }
13241
13242 char *
13243 SavePart (char *str)
13244 {
13245     static char buf[MSG_SIZ];
13246     char *p;
13247
13248     p = strchr(str, ' ');
13249     if (p == NULL) return str;
13250     strncpy(buf, str, p - str);
13251     buf[p - str] = NULLCHAR;
13252     return buf;
13253 }
13254
13255 #define PGN_MAX_LINE 75
13256
13257 #define PGN_SIDE_WHITE  0
13258 #define PGN_SIDE_BLACK  1
13259
13260 static int
13261 FindFirstMoveOutOfBook (int side)
13262 {
13263     int result = -1;
13264
13265     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13266         int index = backwardMostMove;
13267         int has_book_hit = 0;
13268
13269         if( (index % 2) != side ) {
13270             index++;
13271         }
13272
13273         while( index < forwardMostMove ) {
13274             /* Check to see if engine is in book */
13275             int depth = pvInfoList[index].depth;
13276             int score = pvInfoList[index].score;
13277             int in_book = 0;
13278
13279             if( depth <= 2 ) {
13280                 in_book = 1;
13281             }
13282             else if( score == 0 && depth == 63 ) {
13283                 in_book = 1; /* Zappa */
13284             }
13285             else if( score == 2 && depth == 99 ) {
13286                 in_book = 1; /* Abrok */
13287             }
13288
13289             has_book_hit += in_book;
13290
13291             if( ! in_book ) {
13292                 result = index;
13293
13294                 break;
13295             }
13296
13297             index += 2;
13298         }
13299     }
13300
13301     return result;
13302 }
13303
13304 void
13305 GetOutOfBookInfo (char * buf)
13306 {
13307     int oob[2];
13308     int i;
13309     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13310
13311     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13312     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13313
13314     *buf = '\0';
13315
13316     if( oob[0] >= 0 || oob[1] >= 0 ) {
13317         for( i=0; i<2; i++ ) {
13318             int idx = oob[i];
13319
13320             if( idx >= 0 ) {
13321                 if( i > 0 && oob[0] >= 0 ) {
13322                     strcat( buf, "   " );
13323                 }
13324
13325                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13326                 sprintf( buf+strlen(buf), "%s%.2f",
13327                     pvInfoList[idx].score >= 0 ? "+" : "",
13328                     pvInfoList[idx].score / 100.0 );
13329             }
13330         }
13331     }
13332 }
13333
13334 /* Save game in PGN style and close the file */
13335 int
13336 SaveGamePGN (FILE *f)
13337 {
13338     int i, offset, linelen, newblock;
13339 //    char *movetext;
13340     char numtext[32];
13341     int movelen, numlen, blank;
13342     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13343
13344     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13345
13346     PrintPGNTags(f, &gameInfo);
13347
13348     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13349
13350     if (backwardMostMove > 0 || startedFromSetupPosition) {
13351         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13352         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13353         fprintf(f, "\n{--------------\n");
13354         PrintPosition(f, backwardMostMove);
13355         fprintf(f, "--------------}\n");
13356         free(fen);
13357     }
13358     else {
13359         /* [AS] Out of book annotation */
13360         if( appData.saveOutOfBookInfo ) {
13361             char buf[64];
13362
13363             GetOutOfBookInfo( buf );
13364
13365             if( buf[0] != '\0' ) {
13366                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13367             }
13368         }
13369
13370         fprintf(f, "\n");
13371     }
13372
13373     i = backwardMostMove;
13374     linelen = 0;
13375     newblock = TRUE;
13376
13377     while (i < forwardMostMove) {
13378         /* Print comments preceding this move */
13379         if (commentList[i] != NULL) {
13380             if (linelen > 0) fprintf(f, "\n");
13381             fprintf(f, "%s", commentList[i]);
13382             linelen = 0;
13383             newblock = TRUE;
13384         }
13385
13386         /* Format move number */
13387         if ((i % 2) == 0)
13388           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13389         else
13390           if (newblock)
13391             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13392           else
13393             numtext[0] = NULLCHAR;
13394
13395         numlen = strlen(numtext);
13396         newblock = FALSE;
13397
13398         /* Print move number */
13399         blank = linelen > 0 && numlen > 0;
13400         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13401             fprintf(f, "\n");
13402             linelen = 0;
13403             blank = 0;
13404         }
13405         if (blank) {
13406             fprintf(f, " ");
13407             linelen++;
13408         }
13409         fprintf(f, "%s", numtext);
13410         linelen += numlen;
13411
13412         /* Get move */
13413         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13414         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13415
13416         /* Print move */
13417         blank = linelen > 0 && movelen > 0;
13418         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13419             fprintf(f, "\n");
13420             linelen = 0;
13421             blank = 0;
13422         }
13423         if (blank) {
13424             fprintf(f, " ");
13425             linelen++;
13426         }
13427         fprintf(f, "%s", move_buffer);
13428         linelen += movelen;
13429
13430         /* [AS] Add PV info if present */
13431         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13432             /* [HGM] add time */
13433             char buf[MSG_SIZ]; int seconds;
13434
13435             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13436
13437             if( seconds <= 0)
13438               buf[0] = 0;
13439             else
13440               if( seconds < 30 )
13441                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13442               else
13443                 {
13444                   seconds = (seconds + 4)/10; // round to full seconds
13445                   if( seconds < 60 )
13446                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13447                   else
13448                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13449                 }
13450
13451             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13452                       pvInfoList[i].score >= 0 ? "+" : "",
13453                       pvInfoList[i].score / 100.0,
13454                       pvInfoList[i].depth,
13455                       buf );
13456
13457             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13458
13459             /* Print score/depth */
13460             blank = linelen > 0 && movelen > 0;
13461             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13462                 fprintf(f, "\n");
13463                 linelen = 0;
13464                 blank = 0;
13465             }
13466             if (blank) {
13467                 fprintf(f, " ");
13468                 linelen++;
13469             }
13470             fprintf(f, "%s", move_buffer);
13471             linelen += movelen;
13472         }
13473
13474         i++;
13475     }
13476
13477     /* Start a new line */
13478     if (linelen > 0) fprintf(f, "\n");
13479
13480     /* Print comments after last move */
13481     if (commentList[i] != NULL) {
13482         fprintf(f, "%s\n", commentList[i]);
13483     }
13484
13485     /* Print result */
13486     if (gameInfo.resultDetails != NULL &&
13487         gameInfo.resultDetails[0] != NULLCHAR) {
13488         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13489         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13490            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13491             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13492         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13493     } else {
13494         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13495     }
13496
13497     fclose(f);
13498     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13499     return TRUE;
13500 }
13501
13502 /* Save game in old style and close the file */
13503 int
13504 SaveGameOldStyle (FILE *f)
13505 {
13506     int i, offset;
13507     time_t tm;
13508
13509     tm = time((time_t *) NULL);
13510
13511     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13512     PrintOpponents(f);
13513
13514     if (backwardMostMove > 0 || startedFromSetupPosition) {
13515         fprintf(f, "\n[--------------\n");
13516         PrintPosition(f, backwardMostMove);
13517         fprintf(f, "--------------]\n");
13518     } else {
13519         fprintf(f, "\n");
13520     }
13521
13522     i = backwardMostMove;
13523     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13524
13525     while (i < forwardMostMove) {
13526         if (commentList[i] != NULL) {
13527             fprintf(f, "[%s]\n", commentList[i]);
13528         }
13529
13530         if ((i % 2) == 1) {
13531             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13532             i++;
13533         } else {
13534             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13535             i++;
13536             if (commentList[i] != NULL) {
13537                 fprintf(f, "\n");
13538                 continue;
13539             }
13540             if (i >= forwardMostMove) {
13541                 fprintf(f, "\n");
13542                 break;
13543             }
13544             fprintf(f, "%s\n", parseList[i]);
13545             i++;
13546         }
13547     }
13548
13549     if (commentList[i] != NULL) {
13550         fprintf(f, "[%s]\n", commentList[i]);
13551     }
13552
13553     /* This isn't really the old style, but it's close enough */
13554     if (gameInfo.resultDetails != NULL &&
13555         gameInfo.resultDetails[0] != NULLCHAR) {
13556         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13557                 gameInfo.resultDetails);
13558     } else {
13559         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13560     }
13561
13562     fclose(f);
13563     return TRUE;
13564 }
13565
13566 /* Save the current game to open file f and close the file */
13567 int
13568 SaveGame (FILE *f, int dummy, char *dummy2)
13569 {
13570     if (gameMode == EditPosition) EditPositionDone(TRUE);
13571     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13572     if (appData.oldSaveStyle)
13573       return SaveGameOldStyle(f);
13574     else
13575       return SaveGamePGN(f);
13576 }
13577
13578 /* Save the current position to the given file */
13579 int
13580 SavePositionToFile (char *filename)
13581 {
13582     FILE *f;
13583     char buf[MSG_SIZ];
13584
13585     if (strcmp(filename, "-") == 0) {
13586         return SavePosition(stdout, 0, NULL);
13587     } else {
13588         f = fopen(filename, "a");
13589         if (f == NULL) {
13590             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13591             DisplayError(buf, errno);
13592             return FALSE;
13593         } else {
13594             safeStrCpy(buf, lastMsg, MSG_SIZ);
13595             DisplayMessage(_("Waiting for access to save file"), "");
13596             flock(fileno(f), LOCK_EX); // [HGM] lock
13597             DisplayMessage(_("Saving position"), "");
13598             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13599             SavePosition(f, 0, NULL);
13600             DisplayMessage(buf, "");
13601             return TRUE;
13602         }
13603     }
13604 }
13605
13606 /* Save the current position to the given open file and close the file */
13607 int
13608 SavePosition (FILE *f, int dummy, char *dummy2)
13609 {
13610     time_t tm;
13611     char *fen;
13612
13613     if (gameMode == EditPosition) EditPositionDone(TRUE);
13614     if (appData.oldSaveStyle) {
13615         tm = time((time_t *) NULL);
13616
13617         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13618         PrintOpponents(f);
13619         fprintf(f, "[--------------\n");
13620         PrintPosition(f, currentMove);
13621         fprintf(f, "--------------]\n");
13622     } else {
13623         fen = PositionToFEN(currentMove, NULL, 1);
13624         fprintf(f, "%s\n", fen);
13625         free(fen);
13626     }
13627     fclose(f);
13628     return TRUE;
13629 }
13630
13631 void
13632 ReloadCmailMsgEvent (int unregister)
13633 {
13634 #if !WIN32
13635     static char *inFilename = NULL;
13636     static char *outFilename;
13637     int i;
13638     struct stat inbuf, outbuf;
13639     int status;
13640
13641     /* Any registered moves are unregistered if unregister is set, */
13642     /* i.e. invoked by the signal handler */
13643     if (unregister) {
13644         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13645             cmailMoveRegistered[i] = FALSE;
13646             if (cmailCommentList[i] != NULL) {
13647                 free(cmailCommentList[i]);
13648                 cmailCommentList[i] = NULL;
13649             }
13650         }
13651         nCmailMovesRegistered = 0;
13652     }
13653
13654     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13655         cmailResult[i] = CMAIL_NOT_RESULT;
13656     }
13657     nCmailResults = 0;
13658
13659     if (inFilename == NULL) {
13660         /* Because the filenames are static they only get malloced once  */
13661         /* and they never get freed                                      */
13662         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13663         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13664
13665         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13666         sprintf(outFilename, "%s.out", appData.cmailGameName);
13667     }
13668
13669     status = stat(outFilename, &outbuf);
13670     if (status < 0) {
13671         cmailMailedMove = FALSE;
13672     } else {
13673         status = stat(inFilename, &inbuf);
13674         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13675     }
13676
13677     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13678        counts the games, notes how each one terminated, etc.
13679
13680        It would be nice to remove this kludge and instead gather all
13681        the information while building the game list.  (And to keep it
13682        in the game list nodes instead of having a bunch of fixed-size
13683        parallel arrays.)  Note this will require getting each game's
13684        termination from the PGN tags, as the game list builder does
13685        not process the game moves.  --mann
13686        */
13687     cmailMsgLoaded = TRUE;
13688     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13689
13690     /* Load first game in the file or popup game menu */
13691     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13692
13693 #endif /* !WIN32 */
13694     return;
13695 }
13696
13697 int
13698 RegisterMove ()
13699 {
13700     FILE *f;
13701     char string[MSG_SIZ];
13702
13703     if (   cmailMailedMove
13704         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13705         return TRUE;            /* Allow free viewing  */
13706     }
13707
13708     /* Unregister move to ensure that we don't leave RegisterMove        */
13709     /* with the move registered when the conditions for registering no   */
13710     /* longer hold                                                       */
13711     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13712         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13713         nCmailMovesRegistered --;
13714
13715         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13716           {
13717               free(cmailCommentList[lastLoadGameNumber - 1]);
13718               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13719           }
13720     }
13721
13722     if (cmailOldMove == -1) {
13723         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13724         return FALSE;
13725     }
13726
13727     if (currentMove > cmailOldMove + 1) {
13728         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13729         return FALSE;
13730     }
13731
13732     if (currentMove < cmailOldMove) {
13733         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13734         return FALSE;
13735     }
13736
13737     if (forwardMostMove > currentMove) {
13738         /* Silently truncate extra moves */
13739         TruncateGame();
13740     }
13741
13742     if (   (currentMove == cmailOldMove + 1)
13743         || (   (currentMove == cmailOldMove)
13744             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13745                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13746         if (gameInfo.result != GameUnfinished) {
13747             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13748         }
13749
13750         if (commentList[currentMove] != NULL) {
13751             cmailCommentList[lastLoadGameNumber - 1]
13752               = StrSave(commentList[currentMove]);
13753         }
13754         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13755
13756         if (appData.debugMode)
13757           fprintf(debugFP, "Saving %s for game %d\n",
13758                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13759
13760         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13761
13762         f = fopen(string, "w");
13763         if (appData.oldSaveStyle) {
13764             SaveGameOldStyle(f); /* also closes the file */
13765
13766             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13767             f = fopen(string, "w");
13768             SavePosition(f, 0, NULL); /* also closes the file */
13769         } else {
13770             fprintf(f, "{--------------\n");
13771             PrintPosition(f, currentMove);
13772             fprintf(f, "--------------}\n\n");
13773
13774             SaveGame(f, 0, NULL); /* also closes the file*/
13775         }
13776
13777         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13778         nCmailMovesRegistered ++;
13779     } else if (nCmailGames == 1) {
13780         DisplayError(_("You have not made a move yet"), 0);
13781         return FALSE;
13782     }
13783
13784     return TRUE;
13785 }
13786
13787 void
13788 MailMoveEvent ()
13789 {
13790 #if !WIN32
13791     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13792     FILE *commandOutput;
13793     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13794     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13795     int nBuffers;
13796     int i;
13797     int archived;
13798     char *arcDir;
13799
13800     if (! cmailMsgLoaded) {
13801         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13802         return;
13803     }
13804
13805     if (nCmailGames == nCmailResults) {
13806         DisplayError(_("No unfinished games"), 0);
13807         return;
13808     }
13809
13810 #if CMAIL_PROHIBIT_REMAIL
13811     if (cmailMailedMove) {
13812       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);
13813         DisplayError(msg, 0);
13814         return;
13815     }
13816 #endif
13817
13818     if (! (cmailMailedMove || RegisterMove())) return;
13819
13820     if (   cmailMailedMove
13821         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13822       snprintf(string, MSG_SIZ, partCommandString,
13823                appData.debugMode ? " -v" : "", appData.cmailGameName);
13824         commandOutput = popen(string, "r");
13825
13826         if (commandOutput == NULL) {
13827             DisplayError(_("Failed to invoke cmail"), 0);
13828         } else {
13829             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13830                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13831             }
13832             if (nBuffers > 1) {
13833                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13834                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13835                 nBytes = MSG_SIZ - 1;
13836             } else {
13837                 (void) memcpy(msg, buffer, nBytes);
13838             }
13839             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13840
13841             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13842                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13843
13844                 archived = TRUE;
13845                 for (i = 0; i < nCmailGames; i ++) {
13846                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13847                         archived = FALSE;
13848                     }
13849                 }
13850                 if (   archived
13851                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13852                         != NULL)) {
13853                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13854                            arcDir,
13855                            appData.cmailGameName,
13856                            gameInfo.date);
13857                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13858                     cmailMsgLoaded = FALSE;
13859                 }
13860             }
13861
13862             DisplayInformation(msg);
13863             pclose(commandOutput);
13864         }
13865     } else {
13866         if ((*cmailMsg) != '\0') {
13867             DisplayInformation(cmailMsg);
13868         }
13869     }
13870
13871     return;
13872 #endif /* !WIN32 */
13873 }
13874
13875 char *
13876 CmailMsg ()
13877 {
13878 #if WIN32
13879     return NULL;
13880 #else
13881     int  prependComma = 0;
13882     char number[5];
13883     char string[MSG_SIZ];       /* Space for game-list */
13884     int  i;
13885
13886     if (!cmailMsgLoaded) return "";
13887
13888     if (cmailMailedMove) {
13889       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13890     } else {
13891         /* Create a list of games left */
13892       snprintf(string, MSG_SIZ, "[");
13893         for (i = 0; i < nCmailGames; i ++) {
13894             if (! (   cmailMoveRegistered[i]
13895                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13896                 if (prependComma) {
13897                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13898                 } else {
13899                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13900                     prependComma = 1;
13901                 }
13902
13903                 strcat(string, number);
13904             }
13905         }
13906         strcat(string, "]");
13907
13908         if (nCmailMovesRegistered + nCmailResults == 0) {
13909             switch (nCmailGames) {
13910               case 1:
13911                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13912                 break;
13913
13914               case 2:
13915                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13916                 break;
13917
13918               default:
13919                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13920                          nCmailGames);
13921                 break;
13922             }
13923         } else {
13924             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13925               case 1:
13926                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13927                          string);
13928                 break;
13929
13930               case 0:
13931                 if (nCmailResults == nCmailGames) {
13932                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13933                 } else {
13934                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13935                 }
13936                 break;
13937
13938               default:
13939                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13940                          string);
13941             }
13942         }
13943     }
13944     return cmailMsg;
13945 #endif /* WIN32 */
13946 }
13947
13948 void
13949 ResetGameEvent ()
13950 {
13951     if (gameMode == Training)
13952       SetTrainingModeOff();
13953
13954     Reset(TRUE, TRUE);
13955     cmailMsgLoaded = FALSE;
13956     if (appData.icsActive) {
13957       SendToICS(ics_prefix);
13958       SendToICS("refresh\n");
13959     }
13960 }
13961
13962 void
13963 ExitEvent (int status)
13964 {
13965     exiting++;
13966     if (exiting > 2) {
13967       /* Give up on clean exit */
13968       exit(status);
13969     }
13970     if (exiting > 1) {
13971       /* Keep trying for clean exit */
13972       return;
13973     }
13974
13975     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
13976     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13977
13978     if (telnetISR != NULL) {
13979       RemoveInputSource(telnetISR);
13980     }
13981     if (icsPR != NoProc) {
13982       DestroyChildProcess(icsPR, TRUE);
13983     }
13984
13985     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13986     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13987
13988     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13989     /* make sure this other one finishes before killing it!                  */
13990     if(endingGame) { int count = 0;
13991         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13992         while(endingGame && count++ < 10) DoSleep(1);
13993         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13994     }
13995
13996     /* Kill off chess programs */
13997     if (first.pr != NoProc) {
13998         ExitAnalyzeMode();
13999
14000         DoSleep( appData.delayBeforeQuit );
14001         SendToProgram("quit\n", &first);
14002         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14003     }
14004     if (second.pr != NoProc) {
14005         DoSleep( appData.delayBeforeQuit );
14006         SendToProgram("quit\n", &second);
14007         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14008     }
14009     if (first.isr != NULL) {
14010         RemoveInputSource(first.isr);
14011     }
14012     if (second.isr != NULL) {
14013         RemoveInputSource(second.isr);
14014     }
14015
14016     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14017     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14018
14019     ShutDownFrontEnd();
14020     exit(status);
14021 }
14022
14023 void
14024 PauseEngine (ChessProgramState *cps)
14025 {
14026     SendToProgram("pause\n", cps);
14027     cps->pause = 2;
14028 }
14029
14030 void
14031 UnPauseEngine (ChessProgramState *cps)
14032 {
14033     SendToProgram("resume\n", cps);
14034     cps->pause = 1;
14035 }
14036
14037 void
14038 PauseEvent ()
14039 {
14040     if (appData.debugMode)
14041         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14042     if (pausing) {
14043         pausing = FALSE;
14044         ModeHighlight();
14045         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14046             StartClocks();
14047             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14048                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14049                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14050             }
14051             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14052             HandleMachineMove(stashedInputMove, stalledEngine);
14053             stalledEngine = NULL;
14054             return;
14055         }
14056         if (gameMode == MachinePlaysWhite ||
14057             gameMode == TwoMachinesPlay   ||
14058             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14059             if(first.pause)  UnPauseEngine(&first);
14060             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14061             if(second.pause) UnPauseEngine(&second);
14062             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14063             StartClocks();
14064         } else {
14065             DisplayBothClocks();
14066         }
14067         if (gameMode == PlayFromGameFile) {
14068             if (appData.timeDelay >= 0)
14069                 AutoPlayGameLoop();
14070         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14071             Reset(FALSE, TRUE);
14072             SendToICS(ics_prefix);
14073             SendToICS("refresh\n");
14074         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14075             ForwardInner(forwardMostMove);
14076         }
14077         pauseExamInvalid = FALSE;
14078     } else {
14079         switch (gameMode) {
14080           default:
14081             return;
14082           case IcsExamining:
14083             pauseExamForwardMostMove = forwardMostMove;
14084             pauseExamInvalid = FALSE;
14085             /* fall through */
14086           case IcsObserving:
14087           case IcsPlayingWhite:
14088           case IcsPlayingBlack:
14089             pausing = TRUE;
14090             ModeHighlight();
14091             return;
14092           case PlayFromGameFile:
14093             (void) StopLoadGameTimer();
14094             pausing = TRUE;
14095             ModeHighlight();
14096             break;
14097           case BeginningOfGame:
14098             if (appData.icsActive) return;
14099             /* else fall through */
14100           case MachinePlaysWhite:
14101           case MachinePlaysBlack:
14102           case TwoMachinesPlay:
14103             if (forwardMostMove == 0)
14104               return;           /* don't pause if no one has moved */
14105             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14106                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14107                 if(onMove->pause) {           // thinking engine can be paused
14108                     PauseEngine(onMove);      // do it
14109                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14110                         PauseEngine(onMove->other);
14111                     else
14112                         SendToProgram("easy\n", onMove->other);
14113                     StopClocks();
14114                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14115             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14116                 if(first.pause) {
14117                     PauseEngine(&first);
14118                     StopClocks();
14119                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14120             } else { // human on move, pause pondering by either method
14121                 if(first.pause)
14122                     PauseEngine(&first);
14123                 else if(appData.ponderNextMove)
14124                     SendToProgram("easy\n", &first);
14125                 StopClocks();
14126             }
14127             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14128           case AnalyzeMode:
14129             pausing = TRUE;
14130             ModeHighlight();
14131             break;
14132         }
14133     }
14134 }
14135
14136 void
14137 EditCommentEvent ()
14138 {
14139     char title[MSG_SIZ];
14140
14141     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14142       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14143     } else {
14144       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14145                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14146                parseList[currentMove - 1]);
14147     }
14148
14149     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14150 }
14151
14152
14153 void
14154 EditTagsEvent ()
14155 {
14156     char *tags = PGNTags(&gameInfo);
14157     bookUp = FALSE;
14158     EditTagsPopUp(tags, NULL);
14159     free(tags);
14160 }
14161
14162 void
14163 ToggleSecond ()
14164 {
14165   if(second.analyzing) {
14166     SendToProgram("exit\n", &second);
14167     second.analyzing = FALSE;
14168   } else {
14169     if (second.pr == NoProc) StartChessProgram(&second);
14170     InitChessProgram(&second, FALSE);
14171     FeedMovesToProgram(&second, currentMove);
14172
14173     SendToProgram("analyze\n", &second);
14174     second.analyzing = TRUE;
14175   }
14176 }
14177
14178 /* Toggle ShowThinking */
14179 void
14180 ToggleShowThinking()
14181 {
14182   appData.showThinking = !appData.showThinking;
14183   ShowThinkingEvent();
14184 }
14185
14186 int
14187 AnalyzeModeEvent ()
14188 {
14189     char buf[MSG_SIZ];
14190
14191     if (!first.analysisSupport) {
14192       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14193       DisplayError(buf, 0);
14194       return 0;
14195     }
14196     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14197     if (appData.icsActive) {
14198         if (gameMode != IcsObserving) {
14199           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14200             DisplayError(buf, 0);
14201             /* secure check */
14202             if (appData.icsEngineAnalyze) {
14203                 if (appData.debugMode)
14204                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14205                 ExitAnalyzeMode();
14206                 ModeHighlight();
14207             }
14208             return 0;
14209         }
14210         /* if enable, user wants to disable icsEngineAnalyze */
14211         if (appData.icsEngineAnalyze) {
14212                 ExitAnalyzeMode();
14213                 ModeHighlight();
14214                 return 0;
14215         }
14216         appData.icsEngineAnalyze = TRUE;
14217         if (appData.debugMode)
14218             fprintf(debugFP, "ICS engine analyze starting... \n");
14219     }
14220
14221     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14222     if (appData.noChessProgram || gameMode == AnalyzeMode)
14223       return 0;
14224
14225     if (gameMode != AnalyzeFile) {
14226         if (!appData.icsEngineAnalyze) {
14227                EditGameEvent();
14228                if (gameMode != EditGame) return 0;
14229         }
14230         if (!appData.showThinking) ToggleShowThinking();
14231         ResurrectChessProgram();
14232         SendToProgram("analyze\n", &first);
14233         first.analyzing = TRUE;
14234         /*first.maybeThinking = TRUE;*/
14235         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14236         EngineOutputPopUp();
14237     }
14238     if (!appData.icsEngineAnalyze) {
14239         gameMode = AnalyzeMode;
14240         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14241     }
14242     pausing = FALSE;
14243     ModeHighlight();
14244     SetGameInfo();
14245
14246     StartAnalysisClock();
14247     GetTimeMark(&lastNodeCountTime);
14248     lastNodeCount = 0;
14249     return 1;
14250 }
14251
14252 void
14253 AnalyzeFileEvent ()
14254 {
14255     if (appData.noChessProgram || gameMode == AnalyzeFile)
14256       return;
14257
14258     if (!first.analysisSupport) {
14259       char buf[MSG_SIZ];
14260       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14261       DisplayError(buf, 0);
14262       return;
14263     }
14264
14265     if (gameMode != AnalyzeMode) {
14266         keepInfo = 1; // mere annotating should not alter PGN tags
14267         EditGameEvent();
14268         keepInfo = 0;
14269         if (gameMode != EditGame) return;
14270         if (!appData.showThinking) ToggleShowThinking();
14271         ResurrectChessProgram();
14272         SendToProgram("analyze\n", &first);
14273         first.analyzing = TRUE;
14274         /*first.maybeThinking = TRUE;*/
14275         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14276         EngineOutputPopUp();
14277     }
14278     gameMode = AnalyzeFile;
14279     pausing = FALSE;
14280     ModeHighlight();
14281
14282     StartAnalysisClock();
14283     GetTimeMark(&lastNodeCountTime);
14284     lastNodeCount = 0;
14285     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14286     AnalysisPeriodicEvent(1);
14287 }
14288
14289 void
14290 MachineWhiteEvent ()
14291 {
14292     char buf[MSG_SIZ];
14293     char *bookHit = NULL;
14294
14295     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14296       return;
14297
14298
14299     if (gameMode == PlayFromGameFile ||
14300         gameMode == TwoMachinesPlay  ||
14301         gameMode == Training         ||
14302         gameMode == AnalyzeMode      ||
14303         gameMode == EndOfGame)
14304         EditGameEvent();
14305
14306     if (gameMode == EditPosition)
14307         EditPositionDone(TRUE);
14308
14309     if (!WhiteOnMove(currentMove)) {
14310         DisplayError(_("It is not White's turn"), 0);
14311         return;
14312     }
14313
14314     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14315       ExitAnalyzeMode();
14316
14317     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14318         gameMode == AnalyzeFile)
14319         TruncateGame();
14320
14321     ResurrectChessProgram();    /* in case it isn't running */
14322     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14323         gameMode = MachinePlaysWhite;
14324         ResetClocks();
14325     } else
14326     gameMode = MachinePlaysWhite;
14327     pausing = FALSE;
14328     ModeHighlight();
14329     SetGameInfo();
14330     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14331     DisplayTitle(buf);
14332     if (first.sendName) {
14333       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14334       SendToProgram(buf, &first);
14335     }
14336     if (first.sendTime) {
14337       if (first.useColors) {
14338         SendToProgram("black\n", &first); /*gnu kludge*/
14339       }
14340       SendTimeRemaining(&first, TRUE);
14341     }
14342     if (first.useColors) {
14343       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14344     }
14345     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14346     SetMachineThinkingEnables();
14347     first.maybeThinking = TRUE;
14348     StartClocks();
14349     firstMove = FALSE;
14350
14351     if (appData.autoFlipView && !flipView) {
14352       flipView = !flipView;
14353       DrawPosition(FALSE, NULL);
14354       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14355     }
14356
14357     if(bookHit) { // [HGM] book: simulate book reply
14358         static char bookMove[MSG_SIZ]; // a bit generous?
14359
14360         programStats.nodes = programStats.depth = programStats.time =
14361         programStats.score = programStats.got_only_move = 0;
14362         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14363
14364         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14365         strcat(bookMove, bookHit);
14366         HandleMachineMove(bookMove, &first);
14367     }
14368 }
14369
14370 void
14371 MachineBlackEvent ()
14372 {
14373   char buf[MSG_SIZ];
14374   char *bookHit = NULL;
14375
14376     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14377         return;
14378
14379
14380     if (gameMode == PlayFromGameFile ||
14381         gameMode == TwoMachinesPlay  ||
14382         gameMode == Training         ||
14383         gameMode == AnalyzeMode      ||
14384         gameMode == EndOfGame)
14385         EditGameEvent();
14386
14387     if (gameMode == EditPosition)
14388         EditPositionDone(TRUE);
14389
14390     if (WhiteOnMove(currentMove)) {
14391         DisplayError(_("It is not Black's turn"), 0);
14392         return;
14393     }
14394
14395     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14396       ExitAnalyzeMode();
14397
14398     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14399         gameMode == AnalyzeFile)
14400         TruncateGame();
14401
14402     ResurrectChessProgram();    /* in case it isn't running */
14403     gameMode = MachinePlaysBlack;
14404     pausing = FALSE;
14405     ModeHighlight();
14406     SetGameInfo();
14407     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14408     DisplayTitle(buf);
14409     if (first.sendName) {
14410       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14411       SendToProgram(buf, &first);
14412     }
14413     if (first.sendTime) {
14414       if (first.useColors) {
14415         SendToProgram("white\n", &first); /*gnu kludge*/
14416       }
14417       SendTimeRemaining(&first, FALSE);
14418     }
14419     if (first.useColors) {
14420       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14421     }
14422     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14423     SetMachineThinkingEnables();
14424     first.maybeThinking = TRUE;
14425     StartClocks();
14426
14427     if (appData.autoFlipView && flipView) {
14428       flipView = !flipView;
14429       DrawPosition(FALSE, NULL);
14430       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14431     }
14432     if(bookHit) { // [HGM] book: simulate book reply
14433         static char bookMove[MSG_SIZ]; // a bit generous?
14434
14435         programStats.nodes = programStats.depth = programStats.time =
14436         programStats.score = programStats.got_only_move = 0;
14437         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14438
14439         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14440         strcat(bookMove, bookHit);
14441         HandleMachineMove(bookMove, &first);
14442     }
14443 }
14444
14445
14446 void
14447 DisplayTwoMachinesTitle ()
14448 {
14449     char buf[MSG_SIZ];
14450     if (appData.matchGames > 0) {
14451         if(appData.tourneyFile[0]) {
14452           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14453                    gameInfo.white, _("vs."), gameInfo.black,
14454                    nextGame+1, appData.matchGames+1,
14455                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14456         } else
14457         if (first.twoMachinesColor[0] == 'w') {
14458           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14459                    gameInfo.white, _("vs."),  gameInfo.black,
14460                    first.matchWins, second.matchWins,
14461                    matchGame - 1 - (first.matchWins + second.matchWins));
14462         } else {
14463           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14464                    gameInfo.white, _("vs."), gameInfo.black,
14465                    second.matchWins, first.matchWins,
14466                    matchGame - 1 - (first.matchWins + second.matchWins));
14467         }
14468     } else {
14469       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14470     }
14471     DisplayTitle(buf);
14472 }
14473
14474 void
14475 SettingsMenuIfReady ()
14476 {
14477   if (second.lastPing != second.lastPong) {
14478     DisplayMessage("", _("Waiting for second chess program"));
14479     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14480     return;
14481   }
14482   ThawUI();
14483   DisplayMessage("", "");
14484   SettingsPopUp(&second);
14485 }
14486
14487 int
14488 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14489 {
14490     char buf[MSG_SIZ];
14491     if (cps->pr == NoProc) {
14492         StartChessProgram(cps);
14493         if (cps->protocolVersion == 1) {
14494           retry();
14495           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14496         } else {
14497           /* kludge: allow timeout for initial "feature" command */
14498           if(retry != TwoMachinesEventIfReady) FreezeUI();
14499           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14500           DisplayMessage("", buf);
14501           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14502         }
14503         return 1;
14504     }
14505     return 0;
14506 }
14507
14508 void
14509 TwoMachinesEvent P((void))
14510 {
14511     int i;
14512     char buf[MSG_SIZ];
14513     ChessProgramState *onmove;
14514     char *bookHit = NULL;
14515     static int stalling = 0;
14516     TimeMark now;
14517     long wait;
14518
14519     if (appData.noChessProgram) return;
14520
14521     switch (gameMode) {
14522       case TwoMachinesPlay:
14523         return;
14524       case MachinePlaysWhite:
14525       case MachinePlaysBlack:
14526         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14527             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14528             return;
14529         }
14530         /* fall through */
14531       case BeginningOfGame:
14532       case PlayFromGameFile:
14533       case EndOfGame:
14534         EditGameEvent();
14535         if (gameMode != EditGame) return;
14536         break;
14537       case EditPosition:
14538         EditPositionDone(TRUE);
14539         break;
14540       case AnalyzeMode:
14541       case AnalyzeFile:
14542         ExitAnalyzeMode();
14543         break;
14544       case EditGame:
14545       default:
14546         break;
14547     }
14548
14549 //    forwardMostMove = currentMove;
14550     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14551     startingEngine = TRUE;
14552
14553     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14554
14555     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14556     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14557       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14558       return;
14559     }
14560     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14561
14562     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14563                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14564         startingEngine = FALSE;
14565         DisplayError("second engine does not play this", 0);
14566         return;
14567     }
14568
14569     if(!stalling) {
14570       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14571       SendToProgram("force\n", &second);
14572       stalling = 1;
14573       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14574       return;
14575     }
14576     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14577     if(appData.matchPause>10000 || appData.matchPause<10)
14578                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14579     wait = SubtractTimeMarks(&now, &pauseStart);
14580     if(wait < appData.matchPause) {
14581         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14582         return;
14583     }
14584     // we are now committed to starting the game
14585     stalling = 0;
14586     DisplayMessage("", "");
14587     if (startedFromSetupPosition) {
14588         SendBoard(&second, backwardMostMove);
14589     if (appData.debugMode) {
14590         fprintf(debugFP, "Two Machines\n");
14591     }
14592     }
14593     for (i = backwardMostMove; i < forwardMostMove; i++) {
14594         SendMoveToProgram(i, &second);
14595     }
14596
14597     gameMode = TwoMachinesPlay;
14598     pausing = startingEngine = FALSE;
14599     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14600     SetGameInfo();
14601     DisplayTwoMachinesTitle();
14602     firstMove = TRUE;
14603     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14604         onmove = &first;
14605     } else {
14606         onmove = &second;
14607     }
14608     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14609     SendToProgram(first.computerString, &first);
14610     if (first.sendName) {
14611       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14612       SendToProgram(buf, &first);
14613     }
14614     SendToProgram(second.computerString, &second);
14615     if (second.sendName) {
14616       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14617       SendToProgram(buf, &second);
14618     }
14619
14620     ResetClocks();
14621     if (!first.sendTime || !second.sendTime) {
14622         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14623         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14624     }
14625     if (onmove->sendTime) {
14626       if (onmove->useColors) {
14627         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14628       }
14629       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14630     }
14631     if (onmove->useColors) {
14632       SendToProgram(onmove->twoMachinesColor, onmove);
14633     }
14634     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14635 //    SendToProgram("go\n", onmove);
14636     onmove->maybeThinking = TRUE;
14637     SetMachineThinkingEnables();
14638
14639     StartClocks();
14640
14641     if(bookHit) { // [HGM] book: simulate book reply
14642         static char bookMove[MSG_SIZ]; // a bit generous?
14643
14644         programStats.nodes = programStats.depth = programStats.time =
14645         programStats.score = programStats.got_only_move = 0;
14646         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14647
14648         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14649         strcat(bookMove, bookHit);
14650         savedMessage = bookMove; // args for deferred call
14651         savedState = onmove;
14652         ScheduleDelayedEvent(DeferredBookMove, 1);
14653     }
14654 }
14655
14656 void
14657 TrainingEvent ()
14658 {
14659     if (gameMode == Training) {
14660       SetTrainingModeOff();
14661       gameMode = PlayFromGameFile;
14662       DisplayMessage("", _("Training mode off"));
14663     } else {
14664       gameMode = Training;
14665       animateTraining = appData.animate;
14666
14667       /* make sure we are not already at the end of the game */
14668       if (currentMove < forwardMostMove) {
14669         SetTrainingModeOn();
14670         DisplayMessage("", _("Training mode on"));
14671       } else {
14672         gameMode = PlayFromGameFile;
14673         DisplayError(_("Already at end of game"), 0);
14674       }
14675     }
14676     ModeHighlight();
14677 }
14678
14679 void
14680 IcsClientEvent ()
14681 {
14682     if (!appData.icsActive) return;
14683     switch (gameMode) {
14684       case IcsPlayingWhite:
14685       case IcsPlayingBlack:
14686       case IcsObserving:
14687       case IcsIdle:
14688       case BeginningOfGame:
14689       case IcsExamining:
14690         return;
14691
14692       case EditGame:
14693         break;
14694
14695       case EditPosition:
14696         EditPositionDone(TRUE);
14697         break;
14698
14699       case AnalyzeMode:
14700       case AnalyzeFile:
14701         ExitAnalyzeMode();
14702         break;
14703
14704       default:
14705         EditGameEvent();
14706         break;
14707     }
14708
14709     gameMode = IcsIdle;
14710     ModeHighlight();
14711     return;
14712 }
14713
14714 void
14715 EditGameEvent ()
14716 {
14717     int i;
14718
14719     switch (gameMode) {
14720       case Training:
14721         SetTrainingModeOff();
14722         break;
14723       case MachinePlaysWhite:
14724       case MachinePlaysBlack:
14725       case BeginningOfGame:
14726         SendToProgram("force\n", &first);
14727         SetUserThinkingEnables();
14728         break;
14729       case PlayFromGameFile:
14730         (void) StopLoadGameTimer();
14731         if (gameFileFP != NULL) {
14732             gameFileFP = NULL;
14733         }
14734         break;
14735       case EditPosition:
14736         EditPositionDone(TRUE);
14737         break;
14738       case AnalyzeMode:
14739       case AnalyzeFile:
14740         ExitAnalyzeMode();
14741         SendToProgram("force\n", &first);
14742         break;
14743       case TwoMachinesPlay:
14744         GameEnds(EndOfFile, NULL, GE_PLAYER);
14745         ResurrectChessProgram();
14746         SetUserThinkingEnables();
14747         break;
14748       case EndOfGame:
14749         ResurrectChessProgram();
14750         break;
14751       case IcsPlayingBlack:
14752       case IcsPlayingWhite:
14753         DisplayError(_("Warning: You are still playing a game"), 0);
14754         break;
14755       case IcsObserving:
14756         DisplayError(_("Warning: You are still observing a game"), 0);
14757         break;
14758       case IcsExamining:
14759         DisplayError(_("Warning: You are still examining a game"), 0);
14760         break;
14761       case IcsIdle:
14762         break;
14763       case EditGame:
14764       default:
14765         return;
14766     }
14767
14768     pausing = FALSE;
14769     StopClocks();
14770     first.offeredDraw = second.offeredDraw = 0;
14771
14772     if (gameMode == PlayFromGameFile) {
14773         whiteTimeRemaining = timeRemaining[0][currentMove];
14774         blackTimeRemaining = timeRemaining[1][currentMove];
14775         DisplayTitle("");
14776     }
14777
14778     if (gameMode == MachinePlaysWhite ||
14779         gameMode == MachinePlaysBlack ||
14780         gameMode == TwoMachinesPlay ||
14781         gameMode == EndOfGame) {
14782         i = forwardMostMove;
14783         while (i > currentMove) {
14784             SendToProgram("undo\n", &first);
14785             i--;
14786         }
14787         if(!adjustedClock) {
14788         whiteTimeRemaining = timeRemaining[0][currentMove];
14789         blackTimeRemaining = timeRemaining[1][currentMove];
14790         DisplayBothClocks();
14791         }
14792         if (whiteFlag || blackFlag) {
14793             whiteFlag = blackFlag = 0;
14794         }
14795         DisplayTitle("");
14796     }
14797
14798     gameMode = EditGame;
14799     ModeHighlight();
14800     SetGameInfo();
14801 }
14802
14803
14804 void
14805 EditPositionEvent ()
14806 {
14807     if (gameMode == EditPosition) {
14808         EditGameEvent();
14809         return;
14810     }
14811
14812     EditGameEvent();
14813     if (gameMode != EditGame) return;
14814
14815     gameMode = EditPosition;
14816     ModeHighlight();
14817     SetGameInfo();
14818     if (currentMove > 0)
14819       CopyBoard(boards[0], boards[currentMove]);
14820
14821     blackPlaysFirst = !WhiteOnMove(currentMove);
14822     ResetClocks();
14823     currentMove = forwardMostMove = backwardMostMove = 0;
14824     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14825     DisplayMove(-1);
14826     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14827 }
14828
14829 void
14830 ExitAnalyzeMode ()
14831 {
14832     /* [DM] icsEngineAnalyze - possible call from other functions */
14833     if (appData.icsEngineAnalyze) {
14834         appData.icsEngineAnalyze = FALSE;
14835
14836         DisplayMessage("",_("Close ICS engine analyze..."));
14837     }
14838     if (first.analysisSupport && first.analyzing) {
14839       SendToBoth("exit\n");
14840       first.analyzing = second.analyzing = FALSE;
14841     }
14842     thinkOutput[0] = NULLCHAR;
14843 }
14844
14845 void
14846 EditPositionDone (Boolean fakeRights)
14847 {
14848     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14849
14850     startedFromSetupPosition = TRUE;
14851     InitChessProgram(&first, FALSE);
14852     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14853       boards[0][EP_STATUS] = EP_NONE;
14854       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14855       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14856         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14857         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14858       } else boards[0][CASTLING][2] = NoRights;
14859       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14860         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14861         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14862       } else boards[0][CASTLING][5] = NoRights;
14863       if(gameInfo.variant == VariantSChess) {
14864         int i;
14865         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14866           boards[0][VIRGIN][i] = 0;
14867           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14868           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14869         }
14870       }
14871     }
14872     SendToProgram("force\n", &first);
14873     if (blackPlaysFirst) {
14874         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14875         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14876         currentMove = forwardMostMove = backwardMostMove = 1;
14877         CopyBoard(boards[1], boards[0]);
14878     } else {
14879         currentMove = forwardMostMove = backwardMostMove = 0;
14880     }
14881     SendBoard(&first, forwardMostMove);
14882     if (appData.debugMode) {
14883         fprintf(debugFP, "EditPosDone\n");
14884     }
14885     DisplayTitle("");
14886     DisplayMessage("", "");
14887     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14888     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14889     gameMode = EditGame;
14890     ModeHighlight();
14891     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14892     ClearHighlights(); /* [AS] */
14893 }
14894
14895 /* Pause for `ms' milliseconds */
14896 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14897 void
14898 TimeDelay (long ms)
14899 {
14900     TimeMark m1, m2;
14901
14902     GetTimeMark(&m1);
14903     do {
14904         GetTimeMark(&m2);
14905     } while (SubtractTimeMarks(&m2, &m1) < ms);
14906 }
14907
14908 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14909 void
14910 SendMultiLineToICS (char *buf)
14911 {
14912     char temp[MSG_SIZ+1], *p;
14913     int len;
14914
14915     len = strlen(buf);
14916     if (len > MSG_SIZ)
14917       len = MSG_SIZ;
14918
14919     strncpy(temp, buf, len);
14920     temp[len] = 0;
14921
14922     p = temp;
14923     while (*p) {
14924         if (*p == '\n' || *p == '\r')
14925           *p = ' ';
14926         ++p;
14927     }
14928
14929     strcat(temp, "\n");
14930     SendToICS(temp);
14931     SendToPlayer(temp, strlen(temp));
14932 }
14933
14934 void
14935 SetWhiteToPlayEvent ()
14936 {
14937     if (gameMode == EditPosition) {
14938         blackPlaysFirst = FALSE;
14939         DisplayBothClocks();    /* works because currentMove is 0 */
14940     } else if (gameMode == IcsExamining) {
14941         SendToICS(ics_prefix);
14942         SendToICS("tomove white\n");
14943     }
14944 }
14945
14946 void
14947 SetBlackToPlayEvent ()
14948 {
14949     if (gameMode == EditPosition) {
14950         blackPlaysFirst = TRUE;
14951         currentMove = 1;        /* kludge */
14952         DisplayBothClocks();
14953         currentMove = 0;
14954     } else if (gameMode == IcsExamining) {
14955         SendToICS(ics_prefix);
14956         SendToICS("tomove black\n");
14957     }
14958 }
14959
14960 void
14961 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14962 {
14963     char buf[MSG_SIZ];
14964     ChessSquare piece = boards[0][y][x];
14965     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14966     static int lastVariant;
14967
14968     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14969
14970     switch (selection) {
14971       case ClearBoard:
14972         CopyBoard(currentBoard, boards[0]);
14973         CopyBoard(menuBoard, initialPosition);
14974         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14975             SendToICS(ics_prefix);
14976             SendToICS("bsetup clear\n");
14977         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14978             SendToICS(ics_prefix);
14979             SendToICS("clearboard\n");
14980         } else {
14981             int nonEmpty = 0;
14982             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14983                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14984                 for (y = 0; y < BOARD_HEIGHT; y++) {
14985                     if (gameMode == IcsExamining) {
14986                         if (boards[currentMove][y][x] != EmptySquare) {
14987                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14988                                     AAA + x, ONE + y);
14989                             SendToICS(buf);
14990                         }
14991                     } else {
14992                         if(boards[0][y][x] != p) nonEmpty++;
14993                         boards[0][y][x] = p;
14994                     }
14995                 }
14996                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14997             }
14998             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14999                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15000                     ChessSquare p = menuBoard[0][x];
15001                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
15002                     p = menuBoard[BOARD_HEIGHT-1][x];
15003                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
15004                 }
15005                 DisplayMessage("Clicking clock again restores position", "");
15006                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15007                 if(!nonEmpty) { // asked to clear an empty board
15008                     CopyBoard(boards[0], menuBoard);
15009                 } else
15010                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15011                     CopyBoard(boards[0], initialPosition);
15012                 } else
15013                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15014                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15015                     CopyBoard(boards[0], erasedBoard);
15016                 } else
15017                     CopyBoard(erasedBoard, currentBoard);
15018
15019             }
15020         }
15021         if (gameMode == EditPosition) {
15022             DrawPosition(FALSE, boards[0]);
15023         }
15024         break;
15025
15026       case WhitePlay:
15027         SetWhiteToPlayEvent();
15028         break;
15029
15030       case BlackPlay:
15031         SetBlackToPlayEvent();
15032         break;
15033
15034       case EmptySquare:
15035         if (gameMode == IcsExamining) {
15036             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15037             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15038             SendToICS(buf);
15039         } else {
15040             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15041                 if(x == BOARD_LEFT-2) {
15042                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15043                     boards[0][y][1] = 0;
15044                 } else
15045                 if(x == BOARD_RGHT+1) {
15046                     if(y >= gameInfo.holdingsSize) break;
15047                     boards[0][y][BOARD_WIDTH-2] = 0;
15048                 } else break;
15049             }
15050             boards[0][y][x] = EmptySquare;
15051             DrawPosition(FALSE, boards[0]);
15052         }
15053         break;
15054
15055       case PromotePiece:
15056         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15057            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15058             selection = (ChessSquare) (PROMOTED piece);
15059         } else if(piece == EmptySquare) selection = WhiteSilver;
15060         else selection = (ChessSquare)((int)piece - 1);
15061         goto defaultlabel;
15062
15063       case DemotePiece:
15064         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15065            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15066             selection = (ChessSquare) (DEMOTED piece);
15067         } else if(piece == EmptySquare) selection = BlackSilver;
15068         else selection = (ChessSquare)((int)piece + 1);
15069         goto defaultlabel;
15070
15071       case WhiteQueen:
15072       case BlackQueen:
15073         if(gameInfo.variant == VariantShatranj ||
15074            gameInfo.variant == VariantXiangqi  ||
15075            gameInfo.variant == VariantCourier  ||
15076            gameInfo.variant == VariantASEAN    ||
15077            gameInfo.variant == VariantMakruk     )
15078             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15079         goto defaultlabel;
15080
15081       case WhiteKing:
15082       case BlackKing:
15083         if(gameInfo.variant == VariantXiangqi)
15084             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15085         if(gameInfo.variant == VariantKnightmate)
15086             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15087       default:
15088         defaultlabel:
15089         if (gameMode == IcsExamining) {
15090             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15091             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15092                      PieceToChar(selection), AAA + x, ONE + y);
15093             SendToICS(buf);
15094         } else {
15095             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15096                 int n;
15097                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15098                     n = PieceToNumber(selection - BlackPawn);
15099                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15100                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15101                     boards[0][BOARD_HEIGHT-1-n][1]++;
15102                 } else
15103                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15104                     n = PieceToNumber(selection);
15105                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15106                     boards[0][n][BOARD_WIDTH-1] = selection;
15107                     boards[0][n][BOARD_WIDTH-2]++;
15108                 }
15109             } else
15110             boards[0][y][x] = selection;
15111             DrawPosition(TRUE, boards[0]);
15112             ClearHighlights();
15113             fromX = fromY = -1;
15114         }
15115         break;
15116     }
15117 }
15118
15119
15120 void
15121 DropMenuEvent (ChessSquare selection, int x, int y)
15122 {
15123     ChessMove moveType;
15124
15125     switch (gameMode) {
15126       case IcsPlayingWhite:
15127       case MachinePlaysBlack:
15128         if (!WhiteOnMove(currentMove)) {
15129             DisplayMoveError(_("It is Black's turn"));
15130             return;
15131         }
15132         moveType = WhiteDrop;
15133         break;
15134       case IcsPlayingBlack:
15135       case MachinePlaysWhite:
15136         if (WhiteOnMove(currentMove)) {
15137             DisplayMoveError(_("It is White's turn"));
15138             return;
15139         }
15140         moveType = BlackDrop;
15141         break;
15142       case EditGame:
15143         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15144         break;
15145       default:
15146         return;
15147     }
15148
15149     if (moveType == BlackDrop && selection < BlackPawn) {
15150       selection = (ChessSquare) ((int) selection
15151                                  + (int) BlackPawn - (int) WhitePawn);
15152     }
15153     if (boards[currentMove][y][x] != EmptySquare) {
15154         DisplayMoveError(_("That square is occupied"));
15155         return;
15156     }
15157
15158     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15159 }
15160
15161 void
15162 AcceptEvent ()
15163 {
15164     /* Accept a pending offer of any kind from opponent */
15165
15166     if (appData.icsActive) {
15167         SendToICS(ics_prefix);
15168         SendToICS("accept\n");
15169     } else if (cmailMsgLoaded) {
15170         if (currentMove == cmailOldMove &&
15171             commentList[cmailOldMove] != NULL &&
15172             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15173                    "Black offers a draw" : "White offers a draw")) {
15174             TruncateGame();
15175             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15176             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15177         } else {
15178             DisplayError(_("There is no pending offer on this move"), 0);
15179             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15180         }
15181     } else {
15182         /* Not used for offers from chess program */
15183     }
15184 }
15185
15186 void
15187 DeclineEvent ()
15188 {
15189     /* Decline a pending offer of any kind from opponent */
15190
15191     if (appData.icsActive) {
15192         SendToICS(ics_prefix);
15193         SendToICS("decline\n");
15194     } else if (cmailMsgLoaded) {
15195         if (currentMove == cmailOldMove &&
15196             commentList[cmailOldMove] != NULL &&
15197             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15198                    "Black offers a draw" : "White offers a draw")) {
15199 #ifdef NOTDEF
15200             AppendComment(cmailOldMove, "Draw declined", TRUE);
15201             DisplayComment(cmailOldMove - 1, "Draw declined");
15202 #endif /*NOTDEF*/
15203         } else {
15204             DisplayError(_("There is no pending offer on this move"), 0);
15205         }
15206     } else {
15207         /* Not used for offers from chess program */
15208     }
15209 }
15210
15211 void
15212 RematchEvent ()
15213 {
15214     /* Issue ICS rematch command */
15215     if (appData.icsActive) {
15216         SendToICS(ics_prefix);
15217         SendToICS("rematch\n");
15218     }
15219 }
15220
15221 void
15222 CallFlagEvent ()
15223 {
15224     /* Call your opponent's flag (claim a win on time) */
15225     if (appData.icsActive) {
15226         SendToICS(ics_prefix);
15227         SendToICS("flag\n");
15228     } else {
15229         switch (gameMode) {
15230           default:
15231             return;
15232           case MachinePlaysWhite:
15233             if (whiteFlag) {
15234                 if (blackFlag)
15235                   GameEnds(GameIsDrawn, "Both players ran out of time",
15236                            GE_PLAYER);
15237                 else
15238                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15239             } else {
15240                 DisplayError(_("Your opponent is not out of time"), 0);
15241             }
15242             break;
15243           case MachinePlaysBlack:
15244             if (blackFlag) {
15245                 if (whiteFlag)
15246                   GameEnds(GameIsDrawn, "Both players ran out of time",
15247                            GE_PLAYER);
15248                 else
15249                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15250             } else {
15251                 DisplayError(_("Your opponent is not out of time"), 0);
15252             }
15253             break;
15254         }
15255     }
15256 }
15257
15258 void
15259 ClockClick (int which)
15260 {       // [HGM] code moved to back-end from winboard.c
15261         if(which) { // black clock
15262           if (gameMode == EditPosition || gameMode == IcsExamining) {
15263             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15264             SetBlackToPlayEvent();
15265           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15266           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15267           } else if (shiftKey) {
15268             AdjustClock(which, -1);
15269           } else if (gameMode == IcsPlayingWhite ||
15270                      gameMode == MachinePlaysBlack) {
15271             CallFlagEvent();
15272           }
15273         } else { // white clock
15274           if (gameMode == EditPosition || gameMode == IcsExamining) {
15275             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15276             SetWhiteToPlayEvent();
15277           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15278           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15279           } else if (shiftKey) {
15280             AdjustClock(which, -1);
15281           } else if (gameMode == IcsPlayingBlack ||
15282                    gameMode == MachinePlaysWhite) {
15283             CallFlagEvent();
15284           }
15285         }
15286 }
15287
15288 void
15289 DrawEvent ()
15290 {
15291     /* Offer draw or accept pending draw offer from opponent */
15292
15293     if (appData.icsActive) {
15294         /* Note: tournament rules require draw offers to be
15295            made after you make your move but before you punch
15296            your clock.  Currently ICS doesn't let you do that;
15297            instead, you immediately punch your clock after making
15298            a move, but you can offer a draw at any time. */
15299
15300         SendToICS(ics_prefix);
15301         SendToICS("draw\n");
15302         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15303     } else if (cmailMsgLoaded) {
15304         if (currentMove == cmailOldMove &&
15305             commentList[cmailOldMove] != NULL &&
15306             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15307                    "Black offers a draw" : "White offers a draw")) {
15308             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15309             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15310         } else if (currentMove == cmailOldMove + 1) {
15311             char *offer = WhiteOnMove(cmailOldMove) ?
15312               "White offers a draw" : "Black offers a draw";
15313             AppendComment(currentMove, offer, TRUE);
15314             DisplayComment(currentMove - 1, offer);
15315             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15316         } else {
15317             DisplayError(_("You must make your move before offering a draw"), 0);
15318             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15319         }
15320     } else if (first.offeredDraw) {
15321         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15322     } else {
15323         if (first.sendDrawOffers) {
15324             SendToProgram("draw\n", &first);
15325             userOfferedDraw = TRUE;
15326         }
15327     }
15328 }
15329
15330 void
15331 AdjournEvent ()
15332 {
15333     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15334
15335     if (appData.icsActive) {
15336         SendToICS(ics_prefix);
15337         SendToICS("adjourn\n");
15338     } else {
15339         /* Currently GNU Chess doesn't offer or accept Adjourns */
15340     }
15341 }
15342
15343
15344 void
15345 AbortEvent ()
15346 {
15347     /* Offer Abort or accept pending Abort offer from opponent */
15348
15349     if (appData.icsActive) {
15350         SendToICS(ics_prefix);
15351         SendToICS("abort\n");
15352     } else {
15353         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15354     }
15355 }
15356
15357 void
15358 ResignEvent ()
15359 {
15360     /* Resign.  You can do this even if it's not your turn. */
15361
15362     if (appData.icsActive) {
15363         SendToICS(ics_prefix);
15364         SendToICS("resign\n");
15365     } else {
15366         switch (gameMode) {
15367           case MachinePlaysWhite:
15368             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15369             break;
15370           case MachinePlaysBlack:
15371             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15372             break;
15373           case EditGame:
15374             if (cmailMsgLoaded) {
15375                 TruncateGame();
15376                 if (WhiteOnMove(cmailOldMove)) {
15377                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15378                 } else {
15379                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15380                 }
15381                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15382             }
15383             break;
15384           default:
15385             break;
15386         }
15387     }
15388 }
15389
15390
15391 void
15392 StopObservingEvent ()
15393 {
15394     /* Stop observing current games */
15395     SendToICS(ics_prefix);
15396     SendToICS("unobserve\n");
15397 }
15398
15399 void
15400 StopExaminingEvent ()
15401 {
15402     /* Stop observing current game */
15403     SendToICS(ics_prefix);
15404     SendToICS("unexamine\n");
15405 }
15406
15407 void
15408 ForwardInner (int target)
15409 {
15410     int limit; int oldSeekGraphUp = seekGraphUp;
15411
15412     if (appData.debugMode)
15413         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15414                 target, currentMove, forwardMostMove);
15415
15416     if (gameMode == EditPosition)
15417       return;
15418
15419     seekGraphUp = FALSE;
15420     MarkTargetSquares(1);
15421
15422     if (gameMode == PlayFromGameFile && !pausing)
15423       PauseEvent();
15424
15425     if (gameMode == IcsExamining && pausing)
15426       limit = pauseExamForwardMostMove;
15427     else
15428       limit = forwardMostMove;
15429
15430     if (target > limit) target = limit;
15431
15432     if (target > 0 && moveList[target - 1][0]) {
15433         int fromX, fromY, toX, toY;
15434         toX = moveList[target - 1][2] - AAA;
15435         toY = moveList[target - 1][3] - ONE;
15436         if (moveList[target - 1][1] == '@') {
15437             if (appData.highlightLastMove) {
15438                 SetHighlights(-1, -1, toX, toY);
15439             }
15440         } else {
15441             fromX = moveList[target - 1][0] - AAA;
15442             fromY = moveList[target - 1][1] - ONE;
15443             if (target == currentMove + 1) {
15444                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15445             }
15446             if (appData.highlightLastMove) {
15447                 SetHighlights(fromX, fromY, toX, toY);
15448             }
15449         }
15450     }
15451     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15452         gameMode == Training || gameMode == PlayFromGameFile ||
15453         gameMode == AnalyzeFile) {
15454         while (currentMove < target) {
15455             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15456             SendMoveToProgram(currentMove++, &first);
15457         }
15458     } else {
15459         currentMove = target;
15460     }
15461
15462     if (gameMode == EditGame || gameMode == EndOfGame) {
15463         whiteTimeRemaining = timeRemaining[0][currentMove];
15464         blackTimeRemaining = timeRemaining[1][currentMove];
15465     }
15466     DisplayBothClocks();
15467     DisplayMove(currentMove - 1);
15468     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15469     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15470     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15471         DisplayComment(currentMove - 1, commentList[currentMove]);
15472     }
15473     ClearMap(); // [HGM] exclude: invalidate map
15474 }
15475
15476
15477 void
15478 ForwardEvent ()
15479 {
15480     if (gameMode == IcsExamining && !pausing) {
15481         SendToICS(ics_prefix);
15482         SendToICS("forward\n");
15483     } else {
15484         ForwardInner(currentMove + 1);
15485     }
15486 }
15487
15488 void
15489 ToEndEvent ()
15490 {
15491     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15492         /* to optimze, we temporarily turn off analysis mode while we feed
15493          * the remaining moves to the engine. Otherwise we get analysis output
15494          * after each move.
15495          */
15496         if (first.analysisSupport) {
15497           SendToProgram("exit\nforce\n", &first);
15498           first.analyzing = FALSE;
15499         }
15500     }
15501
15502     if (gameMode == IcsExamining && !pausing) {
15503         SendToICS(ics_prefix);
15504         SendToICS("forward 999999\n");
15505     } else {
15506         ForwardInner(forwardMostMove);
15507     }
15508
15509     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15510         /* we have fed all the moves, so reactivate analysis mode */
15511         SendToProgram("analyze\n", &first);
15512         first.analyzing = TRUE;
15513         /*first.maybeThinking = TRUE;*/
15514         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15515     }
15516 }
15517
15518 void
15519 BackwardInner (int target)
15520 {
15521     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15522
15523     if (appData.debugMode)
15524         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15525                 target, currentMove, forwardMostMove);
15526
15527     if (gameMode == EditPosition) return;
15528     seekGraphUp = FALSE;
15529     MarkTargetSquares(1);
15530     if (currentMove <= backwardMostMove) {
15531         ClearHighlights();
15532         DrawPosition(full_redraw, boards[currentMove]);
15533         return;
15534     }
15535     if (gameMode == PlayFromGameFile && !pausing)
15536       PauseEvent();
15537
15538     if (moveList[target][0]) {
15539         int fromX, fromY, toX, toY;
15540         toX = moveList[target][2] - AAA;
15541         toY = moveList[target][3] - ONE;
15542         if (moveList[target][1] == '@') {
15543             if (appData.highlightLastMove) {
15544                 SetHighlights(-1, -1, toX, toY);
15545             }
15546         } else {
15547             fromX = moveList[target][0] - AAA;
15548             fromY = moveList[target][1] - ONE;
15549             if (target == currentMove - 1) {
15550                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15551             }
15552             if (appData.highlightLastMove) {
15553                 SetHighlights(fromX, fromY, toX, toY);
15554             }
15555         }
15556     }
15557     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15558         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15559         while (currentMove > target) {
15560             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15561                 // null move cannot be undone. Reload program with move history before it.
15562                 int i;
15563                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15564                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15565                 }
15566                 SendBoard(&first, i);
15567               if(second.analyzing) SendBoard(&second, i);
15568                 for(currentMove=i; currentMove<target; currentMove++) {
15569                     SendMoveToProgram(currentMove, &first);
15570                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15571                 }
15572                 break;
15573             }
15574             SendToBoth("undo\n");
15575             currentMove--;
15576         }
15577     } else {
15578         currentMove = target;
15579     }
15580
15581     if (gameMode == EditGame || gameMode == EndOfGame) {
15582         whiteTimeRemaining = timeRemaining[0][currentMove];
15583         blackTimeRemaining = timeRemaining[1][currentMove];
15584     }
15585     DisplayBothClocks();
15586     DisplayMove(currentMove - 1);
15587     DrawPosition(full_redraw, boards[currentMove]);
15588     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15589     // [HGM] PV info: routine tests if comment empty
15590     DisplayComment(currentMove - 1, commentList[currentMove]);
15591     ClearMap(); // [HGM] exclude: invalidate map
15592 }
15593
15594 void
15595 BackwardEvent ()
15596 {
15597     if (gameMode == IcsExamining && !pausing) {
15598         SendToICS(ics_prefix);
15599         SendToICS("backward\n");
15600     } else {
15601         BackwardInner(currentMove - 1);
15602     }
15603 }
15604
15605 void
15606 ToStartEvent ()
15607 {
15608     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15609         /* to optimize, we temporarily turn off analysis mode while we undo
15610          * all the moves. Otherwise we get analysis output after each undo.
15611          */
15612         if (first.analysisSupport) {
15613           SendToProgram("exit\nforce\n", &first);
15614           first.analyzing = FALSE;
15615         }
15616     }
15617
15618     if (gameMode == IcsExamining && !pausing) {
15619         SendToICS(ics_prefix);
15620         SendToICS("backward 999999\n");
15621     } else {
15622         BackwardInner(backwardMostMove);
15623     }
15624
15625     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15626         /* we have fed all the moves, so reactivate analysis mode */
15627         SendToProgram("analyze\n", &first);
15628         first.analyzing = TRUE;
15629         /*first.maybeThinking = TRUE;*/
15630         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15631     }
15632 }
15633
15634 void
15635 ToNrEvent (int to)
15636 {
15637   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15638   if (to >= forwardMostMove) to = forwardMostMove;
15639   if (to <= backwardMostMove) to = backwardMostMove;
15640   if (to < currentMove) {
15641     BackwardInner(to);
15642   } else {
15643     ForwardInner(to);
15644   }
15645 }
15646
15647 void
15648 RevertEvent (Boolean annotate)
15649 {
15650     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15651         return;
15652     }
15653     if (gameMode != IcsExamining) {
15654         DisplayError(_("You are not examining a game"), 0);
15655         return;
15656     }
15657     if (pausing) {
15658         DisplayError(_("You can't revert while pausing"), 0);
15659         return;
15660     }
15661     SendToICS(ics_prefix);
15662     SendToICS("revert\n");
15663 }
15664
15665 void
15666 RetractMoveEvent ()
15667 {
15668     switch (gameMode) {
15669       case MachinePlaysWhite:
15670       case MachinePlaysBlack:
15671         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15672             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15673             return;
15674         }
15675         if (forwardMostMove < 2) return;
15676         currentMove = forwardMostMove = forwardMostMove - 2;
15677         whiteTimeRemaining = timeRemaining[0][currentMove];
15678         blackTimeRemaining = timeRemaining[1][currentMove];
15679         DisplayBothClocks();
15680         DisplayMove(currentMove - 1);
15681         ClearHighlights();/*!! could figure this out*/
15682         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15683         SendToProgram("remove\n", &first);
15684         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15685         break;
15686
15687       case BeginningOfGame:
15688       default:
15689         break;
15690
15691       case IcsPlayingWhite:
15692       case IcsPlayingBlack:
15693         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15694             SendToICS(ics_prefix);
15695             SendToICS("takeback 2\n");
15696         } else {
15697             SendToICS(ics_prefix);
15698             SendToICS("takeback 1\n");
15699         }
15700         break;
15701     }
15702 }
15703
15704 void
15705 MoveNowEvent ()
15706 {
15707     ChessProgramState *cps;
15708
15709     switch (gameMode) {
15710       case MachinePlaysWhite:
15711         if (!WhiteOnMove(forwardMostMove)) {
15712             DisplayError(_("It is your turn"), 0);
15713             return;
15714         }
15715         cps = &first;
15716         break;
15717       case MachinePlaysBlack:
15718         if (WhiteOnMove(forwardMostMove)) {
15719             DisplayError(_("It is your turn"), 0);
15720             return;
15721         }
15722         cps = &first;
15723         break;
15724       case TwoMachinesPlay:
15725         if (WhiteOnMove(forwardMostMove) ==
15726             (first.twoMachinesColor[0] == 'w')) {
15727             cps = &first;
15728         } else {
15729             cps = &second;
15730         }
15731         break;
15732       case BeginningOfGame:
15733       default:
15734         return;
15735     }
15736     SendToProgram("?\n", cps);
15737 }
15738
15739 void
15740 TruncateGameEvent ()
15741 {
15742     EditGameEvent();
15743     if (gameMode != EditGame) return;
15744     TruncateGame();
15745 }
15746
15747 void
15748 TruncateGame ()
15749 {
15750     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15751     if (forwardMostMove > currentMove) {
15752         if (gameInfo.resultDetails != NULL) {
15753             free(gameInfo.resultDetails);
15754             gameInfo.resultDetails = NULL;
15755             gameInfo.result = GameUnfinished;
15756         }
15757         forwardMostMove = currentMove;
15758         HistorySet(parseList, backwardMostMove, forwardMostMove,
15759                    currentMove-1);
15760     }
15761 }
15762
15763 void
15764 HintEvent ()
15765 {
15766     if (appData.noChessProgram) return;
15767     switch (gameMode) {
15768       case MachinePlaysWhite:
15769         if (WhiteOnMove(forwardMostMove)) {
15770             DisplayError(_("Wait until your turn."), 0);
15771             return;
15772         }
15773         break;
15774       case BeginningOfGame:
15775       case MachinePlaysBlack:
15776         if (!WhiteOnMove(forwardMostMove)) {
15777             DisplayError(_("Wait until your turn."), 0);
15778             return;
15779         }
15780         break;
15781       default:
15782         DisplayError(_("No hint available"), 0);
15783         return;
15784     }
15785     SendToProgram("hint\n", &first);
15786     hintRequested = TRUE;
15787 }
15788
15789 void
15790 CreateBookEvent ()
15791 {
15792     ListGame * lg = (ListGame *) gameList.head;
15793     FILE *f, *g;
15794     int nItem;
15795     static int secondTime = FALSE;
15796
15797     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15798         DisplayError(_("Game list not loaded or empty"), 0);
15799         return;
15800     }
15801
15802     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15803         fclose(g);
15804         secondTime++;
15805         DisplayNote(_("Book file exists! Try again for overwrite."));
15806         return;
15807     }
15808
15809     creatingBook = TRUE;
15810     secondTime = FALSE;
15811
15812     /* Get list size */
15813     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15814         LoadGame(f, nItem, "", TRUE);
15815         AddGameToBook(TRUE);
15816         lg = (ListGame *) lg->node.succ;
15817     }
15818
15819     creatingBook = FALSE;
15820     FlushBook();
15821 }
15822
15823 void
15824 BookEvent ()
15825 {
15826     if (appData.noChessProgram) return;
15827     switch (gameMode) {
15828       case MachinePlaysWhite:
15829         if (WhiteOnMove(forwardMostMove)) {
15830             DisplayError(_("Wait until your turn."), 0);
15831             return;
15832         }
15833         break;
15834       case BeginningOfGame:
15835       case MachinePlaysBlack:
15836         if (!WhiteOnMove(forwardMostMove)) {
15837             DisplayError(_("Wait until your turn."), 0);
15838             return;
15839         }
15840         break;
15841       case EditPosition:
15842         EditPositionDone(TRUE);
15843         break;
15844       case TwoMachinesPlay:
15845         return;
15846       default:
15847         break;
15848     }
15849     SendToProgram("bk\n", &first);
15850     bookOutput[0] = NULLCHAR;
15851     bookRequested = TRUE;
15852 }
15853
15854 void
15855 AboutGameEvent ()
15856 {
15857     char *tags = PGNTags(&gameInfo);
15858     TagsPopUp(tags, CmailMsg());
15859     free(tags);
15860 }
15861
15862 /* end button procedures */
15863
15864 void
15865 PrintPosition (FILE *fp, int move)
15866 {
15867     int i, j;
15868
15869     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15870         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15871             char c = PieceToChar(boards[move][i][j]);
15872             fputc(c == 'x' ? '.' : c, fp);
15873             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15874         }
15875     }
15876     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15877       fprintf(fp, "white to play\n");
15878     else
15879       fprintf(fp, "black to play\n");
15880 }
15881
15882 void
15883 PrintOpponents (FILE *fp)
15884 {
15885     if (gameInfo.white != NULL) {
15886         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15887     } else {
15888         fprintf(fp, "\n");
15889     }
15890 }
15891
15892 /* Find last component of program's own name, using some heuristics */
15893 void
15894 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15895 {
15896     char *p, *q, c;
15897     int local = (strcmp(host, "localhost") == 0);
15898     while (!local && (p = strchr(prog, ';')) != NULL) {
15899         p++;
15900         while (*p == ' ') p++;
15901         prog = p;
15902     }
15903     if (*prog == '"' || *prog == '\'') {
15904         q = strchr(prog + 1, *prog);
15905     } else {
15906         q = strchr(prog, ' ');
15907     }
15908     if (q == NULL) q = prog + strlen(prog);
15909     p = q;
15910     while (p >= prog && *p != '/' && *p != '\\') p--;
15911     p++;
15912     if(p == prog && *p == '"') p++;
15913     c = *q; *q = 0;
15914     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15915     memcpy(buf, p, q - p);
15916     buf[q - p] = NULLCHAR;
15917     if (!local) {
15918         strcat(buf, "@");
15919         strcat(buf, host);
15920     }
15921 }
15922
15923 char *
15924 TimeControlTagValue ()
15925 {
15926     char buf[MSG_SIZ];
15927     if (!appData.clockMode) {
15928       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15929     } else if (movesPerSession > 0) {
15930       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15931     } else if (timeIncrement == 0) {
15932       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15933     } else {
15934       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15935     }
15936     return StrSave(buf);
15937 }
15938
15939 void
15940 SetGameInfo ()
15941 {
15942     /* This routine is used only for certain modes */
15943     VariantClass v = gameInfo.variant;
15944     ChessMove r = GameUnfinished;
15945     char *p = NULL;
15946
15947     if(keepInfo) return;
15948
15949     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15950         r = gameInfo.result;
15951         p = gameInfo.resultDetails;
15952         gameInfo.resultDetails = NULL;
15953     }
15954     ClearGameInfo(&gameInfo);
15955     gameInfo.variant = v;
15956
15957     switch (gameMode) {
15958       case MachinePlaysWhite:
15959         gameInfo.event = StrSave( appData.pgnEventHeader );
15960         gameInfo.site = StrSave(HostName());
15961         gameInfo.date = PGNDate();
15962         gameInfo.round = StrSave("-");
15963         gameInfo.white = StrSave(first.tidy);
15964         gameInfo.black = StrSave(UserName());
15965         gameInfo.timeControl = TimeControlTagValue();
15966         break;
15967
15968       case MachinePlaysBlack:
15969         gameInfo.event = StrSave( appData.pgnEventHeader );
15970         gameInfo.site = StrSave(HostName());
15971         gameInfo.date = PGNDate();
15972         gameInfo.round = StrSave("-");
15973         gameInfo.white = StrSave(UserName());
15974         gameInfo.black = StrSave(first.tidy);
15975         gameInfo.timeControl = TimeControlTagValue();
15976         break;
15977
15978       case TwoMachinesPlay:
15979         gameInfo.event = StrSave( appData.pgnEventHeader );
15980         gameInfo.site = StrSave(HostName());
15981         gameInfo.date = PGNDate();
15982         if (roundNr > 0) {
15983             char buf[MSG_SIZ];
15984             snprintf(buf, MSG_SIZ, "%d", roundNr);
15985             gameInfo.round = StrSave(buf);
15986         } else {
15987             gameInfo.round = StrSave("-");
15988         }
15989         if (first.twoMachinesColor[0] == 'w') {
15990             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15991             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15992         } else {
15993             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15994             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15995         }
15996         gameInfo.timeControl = TimeControlTagValue();
15997         break;
15998
15999       case EditGame:
16000         gameInfo.event = StrSave("Edited game");
16001         gameInfo.site = StrSave(HostName());
16002         gameInfo.date = PGNDate();
16003         gameInfo.round = StrSave("-");
16004         gameInfo.white = StrSave("-");
16005         gameInfo.black = StrSave("-");
16006         gameInfo.result = r;
16007         gameInfo.resultDetails = p;
16008         break;
16009
16010       case EditPosition:
16011         gameInfo.event = StrSave("Edited position");
16012         gameInfo.site = StrSave(HostName());
16013         gameInfo.date = PGNDate();
16014         gameInfo.round = StrSave("-");
16015         gameInfo.white = StrSave("-");
16016         gameInfo.black = StrSave("-");
16017         break;
16018
16019       case IcsPlayingWhite:
16020       case IcsPlayingBlack:
16021       case IcsObserving:
16022       case IcsExamining:
16023         break;
16024
16025       case PlayFromGameFile:
16026         gameInfo.event = StrSave("Game from non-PGN file");
16027         gameInfo.site = StrSave(HostName());
16028         gameInfo.date = PGNDate();
16029         gameInfo.round = StrSave("-");
16030         gameInfo.white = StrSave("?");
16031         gameInfo.black = StrSave("?");
16032         break;
16033
16034       default:
16035         break;
16036     }
16037 }
16038
16039 void
16040 ReplaceComment (int index, char *text)
16041 {
16042     int len;
16043     char *p;
16044     float score;
16045
16046     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16047        pvInfoList[index-1].depth == len &&
16048        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16049        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16050     while (*text == '\n') text++;
16051     len = strlen(text);
16052     while (len > 0 && text[len - 1] == '\n') len--;
16053
16054     if (commentList[index] != NULL)
16055       free(commentList[index]);
16056
16057     if (len == 0) {
16058         commentList[index] = NULL;
16059         return;
16060     }
16061   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16062       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16063       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16064     commentList[index] = (char *) malloc(len + 2);
16065     strncpy(commentList[index], text, len);
16066     commentList[index][len] = '\n';
16067     commentList[index][len + 1] = NULLCHAR;
16068   } else {
16069     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16070     char *p;
16071     commentList[index] = (char *) malloc(len + 7);
16072     safeStrCpy(commentList[index], "{\n", 3);
16073     safeStrCpy(commentList[index]+2, text, len+1);
16074     commentList[index][len+2] = NULLCHAR;
16075     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16076     strcat(commentList[index], "\n}\n");
16077   }
16078 }
16079
16080 void
16081 CrushCRs (char *text)
16082 {
16083   char *p = text;
16084   char *q = text;
16085   char ch;
16086
16087   do {
16088     ch = *p++;
16089     if (ch == '\r') continue;
16090     *q++ = ch;
16091   } while (ch != '\0');
16092 }
16093
16094 void
16095 AppendComment (int index, char *text, Boolean addBraces)
16096 /* addBraces  tells if we should add {} */
16097 {
16098     int oldlen, len;
16099     char *old;
16100
16101 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16102     if(addBraces == 3) addBraces = 0; else // force appending literally
16103     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16104
16105     CrushCRs(text);
16106     while (*text == '\n') text++;
16107     len = strlen(text);
16108     while (len > 0 && text[len - 1] == '\n') len--;
16109     text[len] = NULLCHAR;
16110
16111     if (len == 0) return;
16112
16113     if (commentList[index] != NULL) {
16114       Boolean addClosingBrace = addBraces;
16115         old = commentList[index];
16116         oldlen = strlen(old);
16117         while(commentList[index][oldlen-1] ==  '\n')
16118           commentList[index][--oldlen] = NULLCHAR;
16119         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16120         safeStrCpy(commentList[index], old, oldlen + len + 6);
16121         free(old);
16122         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16123         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16124           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16125           while (*text == '\n') { text++; len--; }
16126           commentList[index][--oldlen] = NULLCHAR;
16127       }
16128         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16129         else          strcat(commentList[index], "\n");
16130         strcat(commentList[index], text);
16131         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16132         else          strcat(commentList[index], "\n");
16133     } else {
16134         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16135         if(addBraces)
16136           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16137         else commentList[index][0] = NULLCHAR;
16138         strcat(commentList[index], text);
16139         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16140         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16141     }
16142 }
16143
16144 static char *
16145 FindStr (char * text, char * sub_text)
16146 {
16147     char * result = strstr( text, sub_text );
16148
16149     if( result != NULL ) {
16150         result += strlen( sub_text );
16151     }
16152
16153     return result;
16154 }
16155
16156 /* [AS] Try to extract PV info from PGN comment */
16157 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16158 char *
16159 GetInfoFromComment (int index, char * text)
16160 {
16161     char * sep = text, *p;
16162
16163     if( text != NULL && index > 0 ) {
16164         int score = 0;
16165         int depth = 0;
16166         int time = -1, sec = 0, deci;
16167         char * s_eval = FindStr( text, "[%eval " );
16168         char * s_emt = FindStr( text, "[%emt " );
16169 #if 0
16170         if( s_eval != NULL || s_emt != NULL ) {
16171 #else
16172         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16173 #endif
16174             /* New style */
16175             char delim;
16176
16177             if( s_eval != NULL ) {
16178                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16179                     return text;
16180                 }
16181
16182                 if( delim != ']' ) {
16183                     return text;
16184                 }
16185             }
16186
16187             if( s_emt != NULL ) {
16188             }
16189                 return text;
16190         }
16191         else {
16192             /* We expect something like: [+|-]nnn.nn/dd */
16193             int score_lo = 0;
16194
16195             if(*text != '{') return text; // [HGM] braces: must be normal comment
16196
16197             sep = strchr( text, '/' );
16198             if( sep == NULL || sep < (text+4) ) {
16199                 return text;
16200             }
16201
16202             p = text;
16203             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16204             if(p[1] == '(') { // comment starts with PV
16205                p = strchr(p, ')'); // locate end of PV
16206                if(p == NULL || sep < p+5) return text;
16207                // at this point we have something like "{(.*) +0.23/6 ..."
16208                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16209                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16210                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16211             }
16212             time = -1; sec = -1; deci = -1;
16213             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16214                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16215                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16216                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16217                 return text;
16218             }
16219
16220             if( score_lo < 0 || score_lo >= 100 ) {
16221                 return text;
16222             }
16223
16224             if(sec >= 0) time = 600*time + 10*sec; else
16225             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16226
16227             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16228
16229             /* [HGM] PV time: now locate end of PV info */
16230             while( *++sep >= '0' && *sep <= '9'); // strip depth
16231             if(time >= 0)
16232             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16233             if(sec >= 0)
16234             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16235             if(deci >= 0)
16236             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16237             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16238         }
16239
16240         if( depth <= 0 ) {
16241             return text;
16242         }
16243
16244         if( time < 0 ) {
16245             time = -1;
16246         }
16247
16248         pvInfoList[index-1].depth = depth;
16249         pvInfoList[index-1].score = score;
16250         pvInfoList[index-1].time  = 10*time; // centi-sec
16251         if(*sep == '}') *sep = 0; else *--sep = '{';
16252         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16253     }
16254     return sep;
16255 }
16256
16257 void
16258 SendToProgram (char *message, ChessProgramState *cps)
16259 {
16260     int count, outCount, error;
16261     char buf[MSG_SIZ];
16262
16263     if (cps->pr == NoProc) return;
16264     Attention(cps);
16265
16266     if (appData.debugMode) {
16267         TimeMark now;
16268         GetTimeMark(&now);
16269         fprintf(debugFP, "%ld >%-6s: %s",
16270                 SubtractTimeMarks(&now, &programStartTime),
16271                 cps->which, message);
16272         if(serverFP)
16273             fprintf(serverFP, "%ld >%-6s: %s",
16274                 SubtractTimeMarks(&now, &programStartTime),
16275                 cps->which, message), fflush(serverFP);
16276     }
16277
16278     count = strlen(message);
16279     outCount = OutputToProcess(cps->pr, message, count, &error);
16280     if (outCount < count && !exiting
16281                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16282       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16283       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16284         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16285             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16286                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16287                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16288                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16289             } else {
16290                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16291                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16292                 gameInfo.result = res;
16293             }
16294             gameInfo.resultDetails = StrSave(buf);
16295         }
16296         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16297         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16298     }
16299 }
16300
16301 void
16302 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16303 {
16304     char *end_str;
16305     char buf[MSG_SIZ];
16306     ChessProgramState *cps = (ChessProgramState *)closure;
16307
16308     if (isr != cps->isr) return; /* Killed intentionally */
16309     if (count <= 0) {
16310         if (count == 0) {
16311             RemoveInputSource(cps->isr);
16312             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16313                     _(cps->which), cps->program);
16314             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16315             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16316                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16317                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16318                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16319                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16320                 } else {
16321                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16322                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16323                     gameInfo.result = res;
16324                 }
16325                 gameInfo.resultDetails = StrSave(buf);
16326             }
16327             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16328             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16329         } else {
16330             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16331                     _(cps->which), cps->program);
16332             RemoveInputSource(cps->isr);
16333
16334             /* [AS] Program is misbehaving badly... kill it */
16335             if( count == -2 ) {
16336                 DestroyChildProcess( cps->pr, 9 );
16337                 cps->pr = NoProc;
16338             }
16339
16340             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16341         }
16342         return;
16343     }
16344
16345     if ((end_str = strchr(message, '\r')) != NULL)
16346       *end_str = NULLCHAR;
16347     if ((end_str = strchr(message, '\n')) != NULL)
16348       *end_str = NULLCHAR;
16349
16350     if (appData.debugMode) {
16351         TimeMark now; int print = 1;
16352         char *quote = ""; char c; int i;
16353
16354         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16355                 char start = message[0];
16356                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16357                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16358                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16359                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16360                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16361                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16362                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16363                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16364                    sscanf(message, "hint: %c", &c)!=1 &&
16365                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16366                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16367                     print = (appData.engineComments >= 2);
16368                 }
16369                 message[0] = start; // restore original message
16370         }
16371         if(print) {
16372                 GetTimeMark(&now);
16373                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16374                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16375                         quote,
16376                         message);
16377                 if(serverFP)
16378                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16379                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16380                         quote,
16381                         message), fflush(serverFP);
16382         }
16383     }
16384
16385     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16386     if (appData.icsEngineAnalyze) {
16387         if (strstr(message, "whisper") != NULL ||
16388              strstr(message, "kibitz") != NULL ||
16389             strstr(message, "tellics") != NULL) return;
16390     }
16391
16392     HandleMachineMove(message, cps);
16393 }
16394
16395
16396 void
16397 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16398 {
16399     char buf[MSG_SIZ];
16400     int seconds;
16401
16402     if( timeControl_2 > 0 ) {
16403         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16404             tc = timeControl_2;
16405         }
16406     }
16407     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16408     inc /= cps->timeOdds;
16409     st  /= cps->timeOdds;
16410
16411     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16412
16413     if (st > 0) {
16414       /* Set exact time per move, normally using st command */
16415       if (cps->stKludge) {
16416         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16417         seconds = st % 60;
16418         if (seconds == 0) {
16419           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16420         } else {
16421           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16422         }
16423       } else {
16424         snprintf(buf, MSG_SIZ, "st %d\n", st);
16425       }
16426     } else {
16427       /* Set conventional or incremental time control, using level command */
16428       if (seconds == 0) {
16429         /* Note old gnuchess bug -- minutes:seconds used to not work.
16430            Fixed in later versions, but still avoid :seconds
16431            when seconds is 0. */
16432         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16433       } else {
16434         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16435                  seconds, inc/1000.);
16436       }
16437     }
16438     SendToProgram(buf, cps);
16439
16440     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16441     /* Orthogonally, limit search to given depth */
16442     if (sd > 0) {
16443       if (cps->sdKludge) {
16444         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16445       } else {
16446         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16447       }
16448       SendToProgram(buf, cps);
16449     }
16450
16451     if(cps->nps >= 0) { /* [HGM] nps */
16452         if(cps->supportsNPS == FALSE)
16453           cps->nps = -1; // don't use if engine explicitly says not supported!
16454         else {
16455           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16456           SendToProgram(buf, cps);
16457         }
16458     }
16459 }
16460
16461 ChessProgramState *
16462 WhitePlayer ()
16463 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16464 {
16465     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16466        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16467         return &second;
16468     return &first;
16469 }
16470
16471 void
16472 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16473 {
16474     char message[MSG_SIZ];
16475     long time, otime;
16476
16477     /* Note: this routine must be called when the clocks are stopped
16478        or when they have *just* been set or switched; otherwise
16479        it will be off by the time since the current tick started.
16480     */
16481     if (machineWhite) {
16482         time = whiteTimeRemaining / 10;
16483         otime = blackTimeRemaining / 10;
16484     } else {
16485         time = blackTimeRemaining / 10;
16486         otime = whiteTimeRemaining / 10;
16487     }
16488     /* [HGM] translate opponent's time by time-odds factor */
16489     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16490
16491     if (time <= 0) time = 1;
16492     if (otime <= 0) otime = 1;
16493
16494     snprintf(message, MSG_SIZ, "time %ld\n", time);
16495     SendToProgram(message, cps);
16496
16497     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16498     SendToProgram(message, cps);
16499 }
16500
16501 char *
16502 EngineDefinedVariant (ChessProgramState *cps, int n)
16503 {   // return name of n-th unknown variant that engine supports
16504     static char buf[MSG_SIZ];
16505     char *p, *s = cps->variants;
16506     if(!s) return NULL;
16507     do { // parse string from variants feature
16508       VariantClass v;
16509         p = strchr(s, ',');
16510         if(p) *p = NULLCHAR;
16511       v = StringToVariant(s);
16512       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16513         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16514             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16515         }
16516         if(p) *p++ = ',';
16517         if(n < 0) return buf;
16518     } while(s = p);
16519     return NULL;
16520 }
16521
16522 int
16523 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16524 {
16525   char buf[MSG_SIZ];
16526   int len = strlen(name);
16527   int val;
16528
16529   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16530     (*p) += len + 1;
16531     sscanf(*p, "%d", &val);
16532     *loc = (val != 0);
16533     while (**p && **p != ' ')
16534       (*p)++;
16535     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16536     SendToProgram(buf, cps);
16537     return TRUE;
16538   }
16539   return FALSE;
16540 }
16541
16542 int
16543 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16544 {
16545   char buf[MSG_SIZ];
16546   int len = strlen(name);
16547   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16548     (*p) += len + 1;
16549     sscanf(*p, "%d", loc);
16550     while (**p && **p != ' ') (*p)++;
16551     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16552     SendToProgram(buf, cps);
16553     return TRUE;
16554   }
16555   return FALSE;
16556 }
16557
16558 int
16559 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16560 {
16561   char buf[MSG_SIZ];
16562   int len = strlen(name);
16563   if (strncmp((*p), name, len) == 0
16564       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16565     (*p) += len + 2;
16566     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16567     sscanf(*p, "%[^\"]", *loc);
16568     while (**p && **p != '\"') (*p)++;
16569     if (**p == '\"') (*p)++;
16570     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16571     SendToProgram(buf, cps);
16572     return TRUE;
16573   }
16574   return FALSE;
16575 }
16576
16577 int
16578 ParseOption (Option *opt, ChessProgramState *cps)
16579 // [HGM] options: process the string that defines an engine option, and determine
16580 // name, type, default value, and allowed value range
16581 {
16582         char *p, *q, buf[MSG_SIZ];
16583         int n, min = (-1)<<31, max = 1<<31, def;
16584
16585         if(p = strstr(opt->name, " -spin ")) {
16586             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16587             if(max < min) max = min; // enforce consistency
16588             if(def < min) def = min;
16589             if(def > max) def = max;
16590             opt->value = def;
16591             opt->min = min;
16592             opt->max = max;
16593             opt->type = Spin;
16594         } else if((p = strstr(opt->name, " -slider "))) {
16595             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16596             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16597             if(max < min) max = min; // enforce consistency
16598             if(def < min) def = min;
16599             if(def > max) def = max;
16600             opt->value = def;
16601             opt->min = min;
16602             opt->max = max;
16603             opt->type = Spin; // Slider;
16604         } else if((p = strstr(opt->name, " -string "))) {
16605             opt->textValue = p+9;
16606             opt->type = TextBox;
16607         } else if((p = strstr(opt->name, " -file "))) {
16608             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16609             opt->textValue = p+7;
16610             opt->type = FileName; // FileName;
16611         } else if((p = strstr(opt->name, " -path "))) {
16612             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16613             opt->textValue = p+7;
16614             opt->type = PathName; // PathName;
16615         } else if(p = strstr(opt->name, " -check ")) {
16616             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16617             opt->value = (def != 0);
16618             opt->type = CheckBox;
16619         } else if(p = strstr(opt->name, " -combo ")) {
16620             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16621             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16622             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16623             opt->value = n = 0;
16624             while(q = StrStr(q, " /// ")) {
16625                 n++; *q = 0;    // count choices, and null-terminate each of them
16626                 q += 5;
16627                 if(*q == '*') { // remember default, which is marked with * prefix
16628                     q++;
16629                     opt->value = n;
16630                 }
16631                 cps->comboList[cps->comboCnt++] = q;
16632             }
16633             cps->comboList[cps->comboCnt++] = NULL;
16634             opt->max = n + 1;
16635             opt->type = ComboBox;
16636         } else if(p = strstr(opt->name, " -button")) {
16637             opt->type = Button;
16638         } else if(p = strstr(opt->name, " -save")) {
16639             opt->type = SaveButton;
16640         } else return FALSE;
16641         *p = 0; // terminate option name
16642         // now look if the command-line options define a setting for this engine option.
16643         if(cps->optionSettings && cps->optionSettings[0])
16644             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16645         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16646           snprintf(buf, MSG_SIZ, "option %s", p);
16647                 if(p = strstr(buf, ",")) *p = 0;
16648                 if(q = strchr(buf, '=')) switch(opt->type) {
16649                     case ComboBox:
16650                         for(n=0; n<opt->max; n++)
16651                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16652                         break;
16653                     case TextBox:
16654                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16655                         break;
16656                     case Spin:
16657                     case CheckBox:
16658                         opt->value = atoi(q+1);
16659                     default:
16660                         break;
16661                 }
16662                 strcat(buf, "\n");
16663                 SendToProgram(buf, cps);
16664         }
16665         return TRUE;
16666 }
16667
16668 void
16669 FeatureDone (ChessProgramState *cps, int val)
16670 {
16671   DelayedEventCallback cb = GetDelayedEvent();
16672   if ((cb == InitBackEnd3 && cps == &first) ||
16673       (cb == SettingsMenuIfReady && cps == &second) ||
16674       (cb == LoadEngine) ||
16675       (cb == TwoMachinesEventIfReady)) {
16676     CancelDelayedEvent();
16677     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16678   }
16679   cps->initDone = val;
16680   if(val) cps->reload = FALSE;
16681 }
16682
16683 /* Parse feature command from engine */
16684 void
16685 ParseFeatures (char *args, ChessProgramState *cps)
16686 {
16687   char *p = args;
16688   char *q = NULL;
16689   int val;
16690   char buf[MSG_SIZ];
16691
16692   for (;;) {
16693     while (*p == ' ') p++;
16694     if (*p == NULLCHAR) return;
16695
16696     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16697     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16698     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16699     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16700     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16701     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16702     if (BoolFeature(&p, "reuse", &val, cps)) {
16703       /* Engine can disable reuse, but can't enable it if user said no */
16704       if (!val) cps->reuse = FALSE;
16705       continue;
16706     }
16707     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16708     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16709       if (gameMode == TwoMachinesPlay) {
16710         DisplayTwoMachinesTitle();
16711       } else {
16712         DisplayTitle("");
16713       }
16714       continue;
16715     }
16716     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16717     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16718     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16719     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16720     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16721     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16722     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16723     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16724     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16725     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16726     if (IntFeature(&p, "done", &val, cps)) {
16727       FeatureDone(cps, val);
16728       continue;
16729     }
16730     /* Added by Tord: */
16731     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16732     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16733     /* End of additions by Tord */
16734
16735     /* [HGM] added features: */
16736     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16737     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16738     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16739     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16740     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16741     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16742     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16743     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16744         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16745         FREE(cps->option[cps->nrOptions].name);
16746         cps->option[cps->nrOptions].name = q; q = NULL;
16747         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16748           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16749             SendToProgram(buf, cps);
16750             continue;
16751         }
16752         if(cps->nrOptions >= MAX_OPTIONS) {
16753             cps->nrOptions--;
16754             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16755             DisplayError(buf, 0);
16756         }
16757         continue;
16758     }
16759     /* End of additions by HGM */
16760
16761     /* unknown feature: complain and skip */
16762     q = p;
16763     while (*q && *q != '=') q++;
16764     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16765     SendToProgram(buf, cps);
16766     p = q;
16767     if (*p == '=') {
16768       p++;
16769       if (*p == '\"') {
16770         p++;
16771         while (*p && *p != '\"') p++;
16772         if (*p == '\"') p++;
16773       } else {
16774         while (*p && *p != ' ') p++;
16775       }
16776     }
16777   }
16778
16779 }
16780
16781 void
16782 PeriodicUpdatesEvent (int newState)
16783 {
16784     if (newState == appData.periodicUpdates)
16785       return;
16786
16787     appData.periodicUpdates=newState;
16788
16789     /* Display type changes, so update it now */
16790 //    DisplayAnalysis();
16791
16792     /* Get the ball rolling again... */
16793     if (newState) {
16794         AnalysisPeriodicEvent(1);
16795         StartAnalysisClock();
16796     }
16797 }
16798
16799 void
16800 PonderNextMoveEvent (int newState)
16801 {
16802     if (newState == appData.ponderNextMove) return;
16803     if (gameMode == EditPosition) EditPositionDone(TRUE);
16804     if (newState) {
16805         SendToProgram("hard\n", &first);
16806         if (gameMode == TwoMachinesPlay) {
16807             SendToProgram("hard\n", &second);
16808         }
16809     } else {
16810         SendToProgram("easy\n", &first);
16811         thinkOutput[0] = NULLCHAR;
16812         if (gameMode == TwoMachinesPlay) {
16813             SendToProgram("easy\n", &second);
16814         }
16815     }
16816     appData.ponderNextMove = newState;
16817 }
16818
16819 void
16820 NewSettingEvent (int option, int *feature, char *command, int value)
16821 {
16822     char buf[MSG_SIZ];
16823
16824     if (gameMode == EditPosition) EditPositionDone(TRUE);
16825     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16826     if(feature == NULL || *feature) SendToProgram(buf, &first);
16827     if (gameMode == TwoMachinesPlay) {
16828         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16829     }
16830 }
16831
16832 void
16833 ShowThinkingEvent ()
16834 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16835 {
16836     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16837     int newState = appData.showThinking
16838         // [HGM] thinking: other features now need thinking output as well
16839         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16840
16841     if (oldState == newState) return;
16842     oldState = newState;
16843     if (gameMode == EditPosition) EditPositionDone(TRUE);
16844     if (oldState) {
16845         SendToProgram("post\n", &first);
16846         if (gameMode == TwoMachinesPlay) {
16847             SendToProgram("post\n", &second);
16848         }
16849     } else {
16850         SendToProgram("nopost\n", &first);
16851         thinkOutput[0] = NULLCHAR;
16852         if (gameMode == TwoMachinesPlay) {
16853             SendToProgram("nopost\n", &second);
16854         }
16855     }
16856 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16857 }
16858
16859 void
16860 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16861 {
16862   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16863   if (pr == NoProc) return;
16864   AskQuestion(title, question, replyPrefix, pr);
16865 }
16866
16867 void
16868 TypeInEvent (char firstChar)
16869 {
16870     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16871         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16872         gameMode == AnalyzeMode || gameMode == EditGame ||
16873         gameMode == EditPosition || gameMode == IcsExamining ||
16874         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16875         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16876                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16877                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16878         gameMode == Training) PopUpMoveDialog(firstChar);
16879 }
16880
16881 void
16882 TypeInDoneEvent (char *move)
16883 {
16884         Board board;
16885         int n, fromX, fromY, toX, toY;
16886         char promoChar;
16887         ChessMove moveType;
16888
16889         // [HGM] FENedit
16890         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16891                 EditPositionPasteFEN(move);
16892                 return;
16893         }
16894         // [HGM] movenum: allow move number to be typed in any mode
16895         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16896           ToNrEvent(2*n-1);
16897           return;
16898         }
16899         // undocumented kludge: allow command-line option to be typed in!
16900         // (potentially fatal, and does not implement the effect of the option.)
16901         // should only be used for options that are values on which future decisions will be made,
16902         // and definitely not on options that would be used during initialization.
16903         if(strstr(move, "!!! -") == move) {
16904             ParseArgsFromString(move+4);
16905             return;
16906         }
16907
16908       if (gameMode != EditGame && currentMove != forwardMostMove &&
16909         gameMode != Training) {
16910         DisplayMoveError(_("Displayed move is not current"));
16911       } else {
16912         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16913           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16914         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16915         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16916           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16917           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16918         } else {
16919           DisplayMoveError(_("Could not parse move"));
16920         }
16921       }
16922 }
16923
16924 void
16925 DisplayMove (int moveNumber)
16926 {
16927     char message[MSG_SIZ];
16928     char res[MSG_SIZ];
16929     char cpThinkOutput[MSG_SIZ];
16930
16931     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16932
16933     if (moveNumber == forwardMostMove - 1 ||
16934         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16935
16936         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16937
16938         if (strchr(cpThinkOutput, '\n')) {
16939             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16940         }
16941     } else {
16942         *cpThinkOutput = NULLCHAR;
16943     }
16944
16945     /* [AS] Hide thinking from human user */
16946     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16947         *cpThinkOutput = NULLCHAR;
16948         if( thinkOutput[0] != NULLCHAR ) {
16949             int i;
16950
16951             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16952                 cpThinkOutput[i] = '.';
16953             }
16954             cpThinkOutput[i] = NULLCHAR;
16955             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16956         }
16957     }
16958
16959     if (moveNumber == forwardMostMove - 1 &&
16960         gameInfo.resultDetails != NULL) {
16961         if (gameInfo.resultDetails[0] == NULLCHAR) {
16962           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16963         } else {
16964           snprintf(res, MSG_SIZ, " {%s} %s",
16965                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16966         }
16967     } else {
16968         res[0] = NULLCHAR;
16969     }
16970
16971     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16972         DisplayMessage(res, cpThinkOutput);
16973     } else {
16974       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16975                 WhiteOnMove(moveNumber) ? " " : ".. ",
16976                 parseList[moveNumber], res);
16977         DisplayMessage(message, cpThinkOutput);
16978     }
16979 }
16980
16981 void
16982 DisplayComment (int moveNumber, char *text)
16983 {
16984     char title[MSG_SIZ];
16985
16986     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16987       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16988     } else {
16989       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16990               WhiteOnMove(moveNumber) ? " " : ".. ",
16991               parseList[moveNumber]);
16992     }
16993     if (text != NULL && (appData.autoDisplayComment || commentUp))
16994         CommentPopUp(title, text);
16995 }
16996
16997 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16998  * might be busy thinking or pondering.  It can be omitted if your
16999  * gnuchess is configured to stop thinking immediately on any user
17000  * input.  However, that gnuchess feature depends on the FIONREAD
17001  * ioctl, which does not work properly on some flavors of Unix.
17002  */
17003 void
17004 Attention (ChessProgramState *cps)
17005 {
17006 #if ATTENTION
17007     if (!cps->useSigint) return;
17008     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17009     switch (gameMode) {
17010       case MachinePlaysWhite:
17011       case MachinePlaysBlack:
17012       case TwoMachinesPlay:
17013       case IcsPlayingWhite:
17014       case IcsPlayingBlack:
17015       case AnalyzeMode:
17016       case AnalyzeFile:
17017         /* Skip if we know it isn't thinking */
17018         if (!cps->maybeThinking) return;
17019         if (appData.debugMode)
17020           fprintf(debugFP, "Interrupting %s\n", cps->which);
17021         InterruptChildProcess(cps->pr);
17022         cps->maybeThinking = FALSE;
17023         break;
17024       default:
17025         break;
17026     }
17027 #endif /*ATTENTION*/
17028 }
17029
17030 int
17031 CheckFlags ()
17032 {
17033     if (whiteTimeRemaining <= 0) {
17034         if (!whiteFlag) {
17035             whiteFlag = TRUE;
17036             if (appData.icsActive) {
17037                 if (appData.autoCallFlag &&
17038                     gameMode == IcsPlayingBlack && !blackFlag) {
17039                   SendToICS(ics_prefix);
17040                   SendToICS("flag\n");
17041                 }
17042             } else {
17043                 if (blackFlag) {
17044                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17045                 } else {
17046                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17047                     if (appData.autoCallFlag) {
17048                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17049                         return TRUE;
17050                     }
17051                 }
17052             }
17053         }
17054     }
17055     if (blackTimeRemaining <= 0) {
17056         if (!blackFlag) {
17057             blackFlag = TRUE;
17058             if (appData.icsActive) {
17059                 if (appData.autoCallFlag &&
17060                     gameMode == IcsPlayingWhite && !whiteFlag) {
17061                   SendToICS(ics_prefix);
17062                   SendToICS("flag\n");
17063                 }
17064             } else {
17065                 if (whiteFlag) {
17066                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17067                 } else {
17068                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17069                     if (appData.autoCallFlag) {
17070                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17071                         return TRUE;
17072                     }
17073                 }
17074             }
17075         }
17076     }
17077     return FALSE;
17078 }
17079
17080 void
17081 CheckTimeControl ()
17082 {
17083     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17084         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17085
17086     /*
17087      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17088      */
17089     if ( !WhiteOnMove(forwardMostMove) ) {
17090         /* White made time control */
17091         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17092         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17093         /* [HGM] time odds: correct new time quota for time odds! */
17094                                             / WhitePlayer()->timeOdds;
17095         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17096     } else {
17097         lastBlack -= blackTimeRemaining;
17098         /* Black made time control */
17099         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17100                                             / WhitePlayer()->other->timeOdds;
17101         lastWhite = whiteTimeRemaining;
17102     }
17103 }
17104
17105 void
17106 DisplayBothClocks ()
17107 {
17108     int wom = gameMode == EditPosition ?
17109       !blackPlaysFirst : WhiteOnMove(currentMove);
17110     DisplayWhiteClock(whiteTimeRemaining, wom);
17111     DisplayBlackClock(blackTimeRemaining, !wom);
17112 }
17113
17114
17115 /* Timekeeping seems to be a portability nightmare.  I think everyone
17116    has ftime(), but I'm really not sure, so I'm including some ifdefs
17117    to use other calls if you don't.  Clocks will be less accurate if
17118    you have neither ftime nor gettimeofday.
17119 */
17120
17121 /* VS 2008 requires the #include outside of the function */
17122 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17123 #include <sys/timeb.h>
17124 #endif
17125
17126 /* Get the current time as a TimeMark */
17127 void
17128 GetTimeMark (TimeMark *tm)
17129 {
17130 #if HAVE_GETTIMEOFDAY
17131
17132     struct timeval timeVal;
17133     struct timezone timeZone;
17134
17135     gettimeofday(&timeVal, &timeZone);
17136     tm->sec = (long) timeVal.tv_sec;
17137     tm->ms = (int) (timeVal.tv_usec / 1000L);
17138
17139 #else /*!HAVE_GETTIMEOFDAY*/
17140 #if HAVE_FTIME
17141
17142 // include <sys/timeb.h> / moved to just above start of function
17143     struct timeb timeB;
17144
17145     ftime(&timeB);
17146     tm->sec = (long) timeB.time;
17147     tm->ms = (int) timeB.millitm;
17148
17149 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17150     tm->sec = (long) time(NULL);
17151     tm->ms = 0;
17152 #endif
17153 #endif
17154 }
17155
17156 /* Return the difference in milliseconds between two
17157    time marks.  We assume the difference will fit in a long!
17158 */
17159 long
17160 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17161 {
17162     return 1000L*(tm2->sec - tm1->sec) +
17163            (long) (tm2->ms - tm1->ms);
17164 }
17165
17166
17167 /*
17168  * Code to manage the game clocks.
17169  *
17170  * In tournament play, black starts the clock and then white makes a move.
17171  * We give the human user a slight advantage if he is playing white---the
17172  * clocks don't run until he makes his first move, so it takes zero time.
17173  * Also, we don't account for network lag, so we could get out of sync
17174  * with GNU Chess's clock -- but then, referees are always right.
17175  */
17176
17177 static TimeMark tickStartTM;
17178 static long intendedTickLength;
17179
17180 long
17181 NextTickLength (long timeRemaining)
17182 {
17183     long nominalTickLength, nextTickLength;
17184
17185     if (timeRemaining > 0L && timeRemaining <= 10000L)
17186       nominalTickLength = 100L;
17187     else
17188       nominalTickLength = 1000L;
17189     nextTickLength = timeRemaining % nominalTickLength;
17190     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17191
17192     return nextTickLength;
17193 }
17194
17195 /* Adjust clock one minute up or down */
17196 void
17197 AdjustClock (Boolean which, int dir)
17198 {
17199     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17200     if(which) blackTimeRemaining += 60000*dir;
17201     else      whiteTimeRemaining += 60000*dir;
17202     DisplayBothClocks();
17203     adjustedClock = TRUE;
17204 }
17205
17206 /* Stop clocks and reset to a fresh time control */
17207 void
17208 ResetClocks ()
17209 {
17210     (void) StopClockTimer();
17211     if (appData.icsActive) {
17212         whiteTimeRemaining = blackTimeRemaining = 0;
17213     } else if (searchTime) {
17214         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17215         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17216     } else { /* [HGM] correct new time quote for time odds */
17217         whiteTC = blackTC = fullTimeControlString;
17218         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17219         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17220     }
17221     if (whiteFlag || blackFlag) {
17222         DisplayTitle("");
17223         whiteFlag = blackFlag = FALSE;
17224     }
17225     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17226     DisplayBothClocks();
17227     adjustedClock = FALSE;
17228 }
17229
17230 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17231
17232 /* Decrement running clock by amount of time that has passed */
17233 void
17234 DecrementClocks ()
17235 {
17236     long timeRemaining;
17237     long lastTickLength, fudge;
17238     TimeMark now;
17239
17240     if (!appData.clockMode) return;
17241     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17242
17243     GetTimeMark(&now);
17244
17245     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17246
17247     /* Fudge if we woke up a little too soon */
17248     fudge = intendedTickLength - lastTickLength;
17249     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17250
17251     if (WhiteOnMove(forwardMostMove)) {
17252         if(whiteNPS >= 0) lastTickLength = 0;
17253         timeRemaining = whiteTimeRemaining -= lastTickLength;
17254         if(timeRemaining < 0 && !appData.icsActive) {
17255             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17256             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17257                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17258                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17259             }
17260         }
17261         DisplayWhiteClock(whiteTimeRemaining - fudge,
17262                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17263     } else {
17264         if(blackNPS >= 0) lastTickLength = 0;
17265         timeRemaining = blackTimeRemaining -= lastTickLength;
17266         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17267             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17268             if(suddenDeath) {
17269                 blackStartMove = forwardMostMove;
17270                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17271             }
17272         }
17273         DisplayBlackClock(blackTimeRemaining - fudge,
17274                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17275     }
17276     if (CheckFlags()) return;
17277
17278     if(twoBoards) { // count down secondary board's clocks as well
17279         activePartnerTime -= lastTickLength;
17280         partnerUp = 1;
17281         if(activePartner == 'W')
17282             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17283         else
17284             DisplayBlackClock(activePartnerTime, TRUE);
17285         partnerUp = 0;
17286     }
17287
17288     tickStartTM = now;
17289     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17290     StartClockTimer(intendedTickLength);
17291
17292     /* if the time remaining has fallen below the alarm threshold, sound the
17293      * alarm. if the alarm has sounded and (due to a takeback or time control
17294      * with increment) the time remaining has increased to a level above the
17295      * threshold, reset the alarm so it can sound again.
17296      */
17297
17298     if (appData.icsActive && appData.icsAlarm) {
17299
17300         /* make sure we are dealing with the user's clock */
17301         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17302                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17303            )) return;
17304
17305         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17306             alarmSounded = FALSE;
17307         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17308             PlayAlarmSound();
17309             alarmSounded = TRUE;
17310         }
17311     }
17312 }
17313
17314
17315 /* A player has just moved, so stop the previously running
17316    clock and (if in clock mode) start the other one.
17317    We redisplay both clocks in case we're in ICS mode, because
17318    ICS gives us an update to both clocks after every move.
17319    Note that this routine is called *after* forwardMostMove
17320    is updated, so the last fractional tick must be subtracted
17321    from the color that is *not* on move now.
17322 */
17323 void
17324 SwitchClocks (int newMoveNr)
17325 {
17326     long lastTickLength;
17327     TimeMark now;
17328     int flagged = FALSE;
17329
17330     GetTimeMark(&now);
17331
17332     if (StopClockTimer() && appData.clockMode) {
17333         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17334         if (!WhiteOnMove(forwardMostMove)) {
17335             if(blackNPS >= 0) lastTickLength = 0;
17336             blackTimeRemaining -= lastTickLength;
17337            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17338 //         if(pvInfoList[forwardMostMove].time == -1)
17339                  pvInfoList[forwardMostMove].time =               // use GUI time
17340                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17341         } else {
17342            if(whiteNPS >= 0) lastTickLength = 0;
17343            whiteTimeRemaining -= lastTickLength;
17344            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17345 //         if(pvInfoList[forwardMostMove].time == -1)
17346                  pvInfoList[forwardMostMove].time =
17347                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17348         }
17349         flagged = CheckFlags();
17350     }
17351     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17352     CheckTimeControl();
17353
17354     if (flagged || !appData.clockMode) return;
17355
17356     switch (gameMode) {
17357       case MachinePlaysBlack:
17358       case MachinePlaysWhite:
17359       case BeginningOfGame:
17360         if (pausing) return;
17361         break;
17362
17363       case EditGame:
17364       case PlayFromGameFile:
17365       case IcsExamining:
17366         return;
17367
17368       default:
17369         break;
17370     }
17371
17372     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17373         if(WhiteOnMove(forwardMostMove))
17374              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17375         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17376     }
17377
17378     tickStartTM = now;
17379     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17380       whiteTimeRemaining : blackTimeRemaining);
17381     StartClockTimer(intendedTickLength);
17382 }
17383
17384
17385 /* Stop both clocks */
17386 void
17387 StopClocks ()
17388 {
17389     long lastTickLength;
17390     TimeMark now;
17391
17392     if (!StopClockTimer()) return;
17393     if (!appData.clockMode) return;
17394
17395     GetTimeMark(&now);
17396
17397     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17398     if (WhiteOnMove(forwardMostMove)) {
17399         if(whiteNPS >= 0) lastTickLength = 0;
17400         whiteTimeRemaining -= lastTickLength;
17401         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17402     } else {
17403         if(blackNPS >= 0) lastTickLength = 0;
17404         blackTimeRemaining -= lastTickLength;
17405         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17406     }
17407     CheckFlags();
17408 }
17409
17410 /* Start clock of player on move.  Time may have been reset, so
17411    if clock is already running, stop and restart it. */
17412 void
17413 StartClocks ()
17414 {
17415     (void) StopClockTimer(); /* in case it was running already */
17416     DisplayBothClocks();
17417     if (CheckFlags()) return;
17418
17419     if (!appData.clockMode) return;
17420     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17421
17422     GetTimeMark(&tickStartTM);
17423     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17424       whiteTimeRemaining : blackTimeRemaining);
17425
17426    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17427     whiteNPS = blackNPS = -1;
17428     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17429        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17430         whiteNPS = first.nps;
17431     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17432        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17433         blackNPS = first.nps;
17434     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17435         whiteNPS = second.nps;
17436     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17437         blackNPS = second.nps;
17438     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17439
17440     StartClockTimer(intendedTickLength);
17441 }
17442
17443 char *
17444 TimeString (long ms)
17445 {
17446     long second, minute, hour, day;
17447     char *sign = "";
17448     static char buf[32];
17449
17450     if (ms > 0 && ms <= 9900) {
17451       /* convert milliseconds to tenths, rounding up */
17452       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17453
17454       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17455       return buf;
17456     }
17457
17458     /* convert milliseconds to seconds, rounding up */
17459     /* use floating point to avoid strangeness of integer division
17460        with negative dividends on many machines */
17461     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17462
17463     if (second < 0) {
17464         sign = "-";
17465         second = -second;
17466     }
17467
17468     day = second / (60 * 60 * 24);
17469     second = second % (60 * 60 * 24);
17470     hour = second / (60 * 60);
17471     second = second % (60 * 60);
17472     minute = second / 60;
17473     second = second % 60;
17474
17475     if (day > 0)
17476       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17477               sign, day, hour, minute, second);
17478     else if (hour > 0)
17479       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17480     else
17481       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17482
17483     return buf;
17484 }
17485
17486
17487 /*
17488  * This is necessary because some C libraries aren't ANSI C compliant yet.
17489  */
17490 char *
17491 StrStr (char *string, char *match)
17492 {
17493     int i, length;
17494
17495     length = strlen(match);
17496
17497     for (i = strlen(string) - length; i >= 0; i--, string++)
17498       if (!strncmp(match, string, length))
17499         return string;
17500
17501     return NULL;
17502 }
17503
17504 char *
17505 StrCaseStr (char *string, char *match)
17506 {
17507     int i, j, length;
17508
17509     length = strlen(match);
17510
17511     for (i = strlen(string) - length; i >= 0; i--, string++) {
17512         for (j = 0; j < length; j++) {
17513             if (ToLower(match[j]) != ToLower(string[j]))
17514               break;
17515         }
17516         if (j == length) return string;
17517     }
17518
17519     return NULL;
17520 }
17521
17522 #ifndef _amigados
17523 int
17524 StrCaseCmp (char *s1, char *s2)
17525 {
17526     char c1, c2;
17527
17528     for (;;) {
17529         c1 = ToLower(*s1++);
17530         c2 = ToLower(*s2++);
17531         if (c1 > c2) return 1;
17532         if (c1 < c2) return -1;
17533         if (c1 == NULLCHAR) return 0;
17534     }
17535 }
17536
17537
17538 int
17539 ToLower (int c)
17540 {
17541     return isupper(c) ? tolower(c) : c;
17542 }
17543
17544
17545 int
17546 ToUpper (int c)
17547 {
17548     return islower(c) ? toupper(c) : c;
17549 }
17550 #endif /* !_amigados    */
17551
17552 char *
17553 StrSave (char *s)
17554 {
17555   char *ret;
17556
17557   if ((ret = (char *) malloc(strlen(s) + 1)))
17558     {
17559       safeStrCpy(ret, s, strlen(s)+1);
17560     }
17561   return ret;
17562 }
17563
17564 char *
17565 StrSavePtr (char *s, char **savePtr)
17566 {
17567     if (*savePtr) {
17568         free(*savePtr);
17569     }
17570     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17571       safeStrCpy(*savePtr, s, strlen(s)+1);
17572     }
17573     return(*savePtr);
17574 }
17575
17576 char *
17577 PGNDate ()
17578 {
17579     time_t clock;
17580     struct tm *tm;
17581     char buf[MSG_SIZ];
17582
17583     clock = time((time_t *)NULL);
17584     tm = localtime(&clock);
17585     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17586             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17587     return StrSave(buf);
17588 }
17589
17590
17591 char *
17592 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17593 {
17594     int i, j, fromX, fromY, toX, toY;
17595     int whiteToPlay;
17596     char buf[MSG_SIZ];
17597     char *p, *q;
17598     int emptycount;
17599     ChessSquare piece;
17600
17601     whiteToPlay = (gameMode == EditPosition) ?
17602       !blackPlaysFirst : (move % 2 == 0);
17603     p = buf;
17604
17605     /* Piece placement data */
17606     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17607         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17608         emptycount = 0;
17609         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17610             if (boards[move][i][j] == EmptySquare) {
17611                 emptycount++;
17612             } else { ChessSquare piece = boards[move][i][j];
17613                 if (emptycount > 0) {
17614                     if(emptycount<10) /* [HGM] can be >= 10 */
17615                         *p++ = '0' + emptycount;
17616                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17617                     emptycount = 0;
17618                 }
17619                 if(PieceToChar(piece) == '+') {
17620                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17621                     *p++ = '+';
17622                     piece = (ChessSquare)(DEMOTED piece);
17623                 }
17624                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17625                 if(p[-1] == '~') {
17626                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17627                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17628                     *p++ = '~';
17629                 }
17630             }
17631         }
17632         if (emptycount > 0) {
17633             if(emptycount<10) /* [HGM] can be >= 10 */
17634                 *p++ = '0' + emptycount;
17635             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17636             emptycount = 0;
17637         }
17638         *p++ = '/';
17639     }
17640     *(p - 1) = ' ';
17641
17642     /* [HGM] print Crazyhouse or Shogi holdings */
17643     if( gameInfo.holdingsWidth ) {
17644         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17645         q = p;
17646         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17647             piece = boards[move][i][BOARD_WIDTH-1];
17648             if( piece != EmptySquare )
17649               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17650                   *p++ = PieceToChar(piece);
17651         }
17652         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17653             piece = boards[move][BOARD_HEIGHT-i-1][0];
17654             if( piece != EmptySquare )
17655               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17656                   *p++ = PieceToChar(piece);
17657         }
17658
17659         if( q == p ) *p++ = '-';
17660         *p++ = ']';
17661         *p++ = ' ';
17662     }
17663
17664     /* Active color */
17665     *p++ = whiteToPlay ? 'w' : 'b';
17666     *p++ = ' ';
17667
17668   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17669     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17670   } else {
17671   if(nrCastlingRights) {
17672      q = p;
17673      if(appData.fischerCastling) {
17674        /* [HGM] write directly from rights */
17675            if(boards[move][CASTLING][2] != NoRights &&
17676               boards[move][CASTLING][0] != NoRights   )
17677                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17678            if(boards[move][CASTLING][2] != NoRights &&
17679               boards[move][CASTLING][1] != NoRights   )
17680                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17681            if(boards[move][CASTLING][5] != NoRights &&
17682               boards[move][CASTLING][3] != NoRights   )
17683                 *p++ = boards[move][CASTLING][3] + AAA;
17684            if(boards[move][CASTLING][5] != NoRights &&
17685               boards[move][CASTLING][4] != NoRights   )
17686                 *p++ = boards[move][CASTLING][4] + AAA;
17687      } else {
17688
17689         /* [HGM] write true castling rights */
17690         if( nrCastlingRights == 6 ) {
17691             int q, k=0;
17692             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17693                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17694             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17695                  boards[move][CASTLING][2] != NoRights  );
17696             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17697                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17698                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17699                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17700                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17701             }
17702             if(q) *p++ = 'Q';
17703             k = 0;
17704             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17705                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17706             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17707                  boards[move][CASTLING][5] != NoRights  );
17708             if(gameInfo.variant == VariantSChess) {
17709                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17710                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17711                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17712                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17713             }
17714             if(q) *p++ = 'q';
17715         }
17716      }
17717      if (q == p) *p++ = '-'; /* No castling rights */
17718      *p++ = ' ';
17719   }
17720
17721   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17722      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17723      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17724     /* En passant target square */
17725     if (move > backwardMostMove) {
17726         fromX = moveList[move - 1][0] - AAA;
17727         fromY = moveList[move - 1][1] - ONE;
17728         toX = moveList[move - 1][2] - AAA;
17729         toY = moveList[move - 1][3] - ONE;
17730         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17731             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17732             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17733             fromX == toX) {
17734             /* 2-square pawn move just happened */
17735             *p++ = toX + AAA;
17736             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17737         } else {
17738             *p++ = '-';
17739         }
17740     } else if(move == backwardMostMove) {
17741         // [HGM] perhaps we should always do it like this, and forget the above?
17742         if((signed char)boards[move][EP_STATUS] >= 0) {
17743             *p++ = boards[move][EP_STATUS] + AAA;
17744             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17745         } else {
17746             *p++ = '-';
17747         }
17748     } else {
17749         *p++ = '-';
17750     }
17751     *p++ = ' ';
17752   }
17753   }
17754
17755     if(moveCounts)
17756     {   int i = 0, j=move;
17757
17758         /* [HGM] find reversible plies */
17759         if (appData.debugMode) { int k;
17760             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17761             for(k=backwardMostMove; k<=forwardMostMove; k++)
17762                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17763
17764         }
17765
17766         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17767         if( j == backwardMostMove ) i += initialRulePlies;
17768         sprintf(p, "%d ", i);
17769         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17770
17771         /* Fullmove number */
17772         sprintf(p, "%d", (move / 2) + 1);
17773     } else *--p = NULLCHAR;
17774
17775     return StrSave(buf);
17776 }
17777
17778 Boolean
17779 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17780 {
17781     int i, j, k, w=0, subst=0, shuffle=0;
17782     char *p, c;
17783     int emptycount, virgin[BOARD_FILES];
17784     ChessSquare piece;
17785
17786     p = fen;
17787
17788     /* Piece placement data */
17789     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17790         j = 0;
17791         for (;;) {
17792             if (*p == '/' || *p == ' ' || *p == '[' ) {
17793                 if(j > w) w = j;
17794                 emptycount = gameInfo.boardWidth - j;
17795                 while (emptycount--)
17796                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17797                 if (*p == '/') p++;
17798                 else if(autoSize) { // we stumbled unexpectedly into end of board
17799                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17800                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17801                     }
17802                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17803                 }
17804                 break;
17805 #if(BOARD_FILES >= 10)
17806             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17807                 p++; emptycount=10;
17808                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17809                 while (emptycount--)
17810                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17811 #endif
17812             } else if (*p == '*') {
17813                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17814             } else if (isdigit(*p)) {
17815                 emptycount = *p++ - '0';
17816                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17817                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17818                 while (emptycount--)
17819                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17820             } else if (*p == '<') {
17821                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17822                 else if (i != 0 || !shuffle) return FALSE;
17823                 p++;
17824             } else if (shuffle && *p == '>') {
17825                 p++; // for now ignore closing shuffle range, and assume rank-end
17826             } else if (*p == '?') {
17827                 if (j >= gameInfo.boardWidth) return FALSE;
17828                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17829                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17830             } else if (*p == '+' || isalpha(*p)) {
17831                 if (j >= gameInfo.boardWidth) return FALSE;
17832                 if(*p=='+') {
17833                     piece = CharToPiece(*++p);
17834                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17835                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17836                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17837                 } else piece = CharToPiece(*p++);
17838
17839                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17840                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17841                     piece = (ChessSquare) (PROMOTED piece);
17842                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17843                     p++;
17844                 }
17845                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17846             } else {
17847                 return FALSE;
17848             }
17849         }
17850     }
17851     while (*p == '/' || *p == ' ') p++;
17852
17853     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17854
17855     /* [HGM] by default clear Crazyhouse holdings, if present */
17856     if(gameInfo.holdingsWidth) {
17857        for(i=0; i<BOARD_HEIGHT; i++) {
17858            board[i][0]             = EmptySquare; /* black holdings */
17859            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17860            board[i][1]             = (ChessSquare) 0; /* black counts */
17861            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17862        }
17863     }
17864
17865     /* [HGM] look for Crazyhouse holdings here */
17866     while(*p==' ') p++;
17867     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17868         int swap=0, wcnt=0, bcnt=0;
17869         if(*p == '[') p++;
17870         if(*p == '<') swap++, p++;
17871         if(*p == '-' ) p++; /* empty holdings */ else {
17872             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17873             /* if we would allow FEN reading to set board size, we would   */
17874             /* have to add holdings and shift the board read so far here   */
17875             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17876                 p++;
17877                 if((int) piece >= (int) BlackPawn ) {
17878                     i = (int)piece - (int)BlackPawn;
17879                     i = PieceToNumber((ChessSquare)i);
17880                     if( i >= gameInfo.holdingsSize ) return FALSE;
17881                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17882                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17883                     bcnt++;
17884                 } else {
17885                     i = (int)piece - (int)WhitePawn;
17886                     i = PieceToNumber((ChessSquare)i);
17887                     if( i >= gameInfo.holdingsSize ) return FALSE;
17888                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17889                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17890                     wcnt++;
17891                 }
17892             }
17893             if(subst) { // substitute back-rank question marks by holdings pieces
17894                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17895                     int k, m, n = bcnt + 1;
17896                     if(board[0][j] == ClearBoard) {
17897                         if(!wcnt) return FALSE;
17898                         n = rand() % wcnt;
17899                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17900                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17901                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17902                             break;
17903                         }
17904                     }
17905                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17906                         if(!bcnt) return FALSE;
17907                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17908                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17909                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17910                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17911                             break;
17912                         }
17913                     }
17914                 }
17915                 subst = 0;
17916             }
17917         }
17918         if(*p == ']') p++;
17919     }
17920
17921     if(subst) return FALSE; // substitution requested, but no holdings
17922
17923     while(*p == ' ') p++;
17924
17925     /* Active color */
17926     c = *p++;
17927     if(appData.colorNickNames) {
17928       if( c == appData.colorNickNames[0] ) c = 'w'; else
17929       if( c == appData.colorNickNames[1] ) c = 'b';
17930     }
17931     switch (c) {
17932       case 'w':
17933         *blackPlaysFirst = FALSE;
17934         break;
17935       case 'b':
17936         *blackPlaysFirst = TRUE;
17937         break;
17938       default:
17939         return FALSE;
17940     }
17941
17942     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17943     /* return the extra info in global variiables             */
17944
17945     /* set defaults in case FEN is incomplete */
17946     board[EP_STATUS] = EP_UNKNOWN;
17947     for(i=0; i<nrCastlingRights; i++ ) {
17948         board[CASTLING][i] =
17949             appData.fischerCastling ? NoRights : initialRights[i];
17950     }   /* assume possible unless obviously impossible */
17951     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17952     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17953     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17954                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17955     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17956     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17957     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17958                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17959     FENrulePlies = 0;
17960
17961     while(*p==' ') p++;
17962     if(nrCastlingRights) {
17963       int fischer = 0;
17964       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17965       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17966           /* castling indicator present, so default becomes no castlings */
17967           for(i=0; i<nrCastlingRights; i++ ) {
17968                  board[CASTLING][i] = NoRights;
17969           }
17970       }
17971       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17972              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
17973              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17974              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17975         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17976
17977         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17978             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17979             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17980         }
17981         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17982             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17983         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17984                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17985         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17986                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17987         switch(c) {
17988           case'K':
17989               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17990               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17991               board[CASTLING][2] = whiteKingFile;
17992               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17993               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17994               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
17995               break;
17996           case'Q':
17997               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17998               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17999               board[CASTLING][2] = whiteKingFile;
18000               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18001               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18002               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18003               break;
18004           case'k':
18005               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18006               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18007               board[CASTLING][5] = blackKingFile;
18008               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18009               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18010               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18011               break;
18012           case'q':
18013               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18014               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18015               board[CASTLING][5] = blackKingFile;
18016               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18017               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18018               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18019           case '-':
18020               break;
18021           default: /* FRC castlings */
18022               if(c >= 'a') { /* black rights */
18023                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18024                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18025                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18026                   if(i == BOARD_RGHT) break;
18027                   board[CASTLING][5] = i;
18028                   c -= AAA;
18029                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18030                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18031                   if(c > i)
18032                       board[CASTLING][3] = c;
18033                   else
18034                       board[CASTLING][4] = c;
18035               } else { /* white rights */
18036                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18037                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18038                     if(board[0][i] == WhiteKing) break;
18039                   if(i == BOARD_RGHT) break;
18040                   board[CASTLING][2] = i;
18041                   c -= AAA - 'a' + 'A';
18042                   if(board[0][c] >= WhiteKing) break;
18043                   if(c > i)
18044                       board[CASTLING][0] = c;
18045                   else
18046                       board[CASTLING][1] = c;
18047               }
18048         }
18049       }
18050       for(i=0; i<nrCastlingRights; i++)
18051         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18052       if(gameInfo.variant == VariantSChess)
18053         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18054       if(fischer && shuffle) appData.fischerCastling = TRUE;
18055     if (appData.debugMode) {
18056         fprintf(debugFP, "FEN castling rights:");
18057         for(i=0; i<nrCastlingRights; i++)
18058         fprintf(debugFP, " %d", board[CASTLING][i]);
18059         fprintf(debugFP, "\n");
18060     }
18061
18062       while(*p==' ') p++;
18063     }
18064
18065     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18066
18067     /* read e.p. field in games that know e.p. capture */
18068     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18069        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18070        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18071       if(*p=='-') {
18072         p++; board[EP_STATUS] = EP_NONE;
18073       } else {
18074          char c = *p++ - AAA;
18075
18076          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18077          if(*p >= '0' && *p <='9') p++;
18078          board[EP_STATUS] = c;
18079       }
18080     }
18081
18082
18083     if(sscanf(p, "%d", &i) == 1) {
18084         FENrulePlies = i; /* 50-move ply counter */
18085         /* (The move number is still ignored)    */
18086     }
18087
18088     return TRUE;
18089 }
18090
18091 void
18092 EditPositionPasteFEN (char *fen)
18093 {
18094   if (fen != NULL) {
18095     Board initial_position;
18096
18097     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18098       DisplayError(_("Bad FEN position in clipboard"), 0);
18099       return ;
18100     } else {
18101       int savedBlackPlaysFirst = blackPlaysFirst;
18102       EditPositionEvent();
18103       blackPlaysFirst = savedBlackPlaysFirst;
18104       CopyBoard(boards[0], initial_position);
18105       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18106       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18107       DisplayBothClocks();
18108       DrawPosition(FALSE, boards[currentMove]);
18109     }
18110   }
18111 }
18112
18113 static char cseq[12] = "\\   ";
18114
18115 Boolean
18116 set_cont_sequence (char *new_seq)
18117 {
18118     int len;
18119     Boolean ret;
18120
18121     // handle bad attempts to set the sequence
18122         if (!new_seq)
18123                 return 0; // acceptable error - no debug
18124
18125     len = strlen(new_seq);
18126     ret = (len > 0) && (len < sizeof(cseq));
18127     if (ret)
18128       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18129     else if (appData.debugMode)
18130       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18131     return ret;
18132 }
18133
18134 /*
18135     reformat a source message so words don't cross the width boundary.  internal
18136     newlines are not removed.  returns the wrapped size (no null character unless
18137     included in source message).  If dest is NULL, only calculate the size required
18138     for the dest buffer.  lp argument indicats line position upon entry, and it's
18139     passed back upon exit.
18140 */
18141 int
18142 wrap (char *dest, char *src, int count, int width, int *lp)
18143 {
18144     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18145
18146     cseq_len = strlen(cseq);
18147     old_line = line = *lp;
18148     ansi = len = clen = 0;
18149
18150     for (i=0; i < count; i++)
18151     {
18152         if (src[i] == '\033')
18153             ansi = 1;
18154
18155         // if we hit the width, back up
18156         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18157         {
18158             // store i & len in case the word is too long
18159             old_i = i, old_len = len;
18160
18161             // find the end of the last word
18162             while (i && src[i] != ' ' && src[i] != '\n')
18163             {
18164                 i--;
18165                 len--;
18166             }
18167
18168             // word too long?  restore i & len before splitting it
18169             if ((old_i-i+clen) >= width)
18170             {
18171                 i = old_i;
18172                 len = old_len;
18173             }
18174
18175             // extra space?
18176             if (i && src[i-1] == ' ')
18177                 len--;
18178
18179             if (src[i] != ' ' && src[i] != '\n')
18180             {
18181                 i--;
18182                 if (len)
18183                     len--;
18184             }
18185
18186             // now append the newline and continuation sequence
18187             if (dest)
18188                 dest[len] = '\n';
18189             len++;
18190             if (dest)
18191                 strncpy(dest+len, cseq, cseq_len);
18192             len += cseq_len;
18193             line = cseq_len;
18194             clen = cseq_len;
18195             continue;
18196         }
18197
18198         if (dest)
18199             dest[len] = src[i];
18200         len++;
18201         if (!ansi)
18202             line++;
18203         if (src[i] == '\n')
18204             line = 0;
18205         if (src[i] == 'm')
18206             ansi = 0;
18207     }
18208     if (dest && appData.debugMode)
18209     {
18210         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18211             count, width, line, len, *lp);
18212         show_bytes(debugFP, src, count);
18213         fprintf(debugFP, "\ndest: ");
18214         show_bytes(debugFP, dest, len);
18215         fprintf(debugFP, "\n");
18216     }
18217     *lp = dest ? line : old_line;
18218
18219     return len;
18220 }
18221
18222 // [HGM] vari: routines for shelving variations
18223 Boolean modeRestore = FALSE;
18224
18225 void
18226 PushInner (int firstMove, int lastMove)
18227 {
18228         int i, j, nrMoves = lastMove - firstMove;
18229
18230         // push current tail of game on stack
18231         savedResult[storedGames] = gameInfo.result;
18232         savedDetails[storedGames] = gameInfo.resultDetails;
18233         gameInfo.resultDetails = NULL;
18234         savedFirst[storedGames] = firstMove;
18235         savedLast [storedGames] = lastMove;
18236         savedFramePtr[storedGames] = framePtr;
18237         framePtr -= nrMoves; // reserve space for the boards
18238         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18239             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18240             for(j=0; j<MOVE_LEN; j++)
18241                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18242             for(j=0; j<2*MOVE_LEN; j++)
18243                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18244             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18245             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18246             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18247             pvInfoList[firstMove+i-1].depth = 0;
18248             commentList[framePtr+i] = commentList[firstMove+i];
18249             commentList[firstMove+i] = NULL;
18250         }
18251
18252         storedGames++;
18253         forwardMostMove = firstMove; // truncate game so we can start variation
18254 }
18255
18256 void
18257 PushTail (int firstMove, int lastMove)
18258 {
18259         if(appData.icsActive) { // only in local mode
18260                 forwardMostMove = currentMove; // mimic old ICS behavior
18261                 return;
18262         }
18263         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18264
18265         PushInner(firstMove, lastMove);
18266         if(storedGames == 1) GreyRevert(FALSE);
18267         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18268 }
18269
18270 void
18271 PopInner (Boolean annotate)
18272 {
18273         int i, j, nrMoves;
18274         char buf[8000], moveBuf[20];
18275
18276         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18277         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18278         nrMoves = savedLast[storedGames] - currentMove;
18279         if(annotate) {
18280                 int cnt = 10;
18281                 if(!WhiteOnMove(currentMove))
18282                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18283                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18284                 for(i=currentMove; i<forwardMostMove; i++) {
18285                         if(WhiteOnMove(i))
18286                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18287                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18288                         strcat(buf, moveBuf);
18289                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18290                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18291                 }
18292                 strcat(buf, ")");
18293         }
18294         for(i=1; i<=nrMoves; i++) { // copy last variation back
18295             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18296             for(j=0; j<MOVE_LEN; j++)
18297                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18298             for(j=0; j<2*MOVE_LEN; j++)
18299                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18300             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18301             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18302             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18303             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18304             commentList[currentMove+i] = commentList[framePtr+i];
18305             commentList[framePtr+i] = NULL;
18306         }
18307         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18308         framePtr = savedFramePtr[storedGames];
18309         gameInfo.result = savedResult[storedGames];
18310         if(gameInfo.resultDetails != NULL) {
18311             free(gameInfo.resultDetails);
18312       }
18313         gameInfo.resultDetails = savedDetails[storedGames];
18314         forwardMostMove = currentMove + nrMoves;
18315 }
18316
18317 Boolean
18318 PopTail (Boolean annotate)
18319 {
18320         if(appData.icsActive) return FALSE; // only in local mode
18321         if(!storedGames) return FALSE; // sanity
18322         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18323
18324         PopInner(annotate);
18325         if(currentMove < forwardMostMove) ForwardEvent(); else
18326         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18327
18328         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18329         return TRUE;
18330 }
18331
18332 void
18333 CleanupTail ()
18334 {       // remove all shelved variations
18335         int i;
18336         for(i=0; i<storedGames; i++) {
18337             if(savedDetails[i])
18338                 free(savedDetails[i]);
18339             savedDetails[i] = NULL;
18340         }
18341         for(i=framePtr; i<MAX_MOVES; i++) {
18342                 if(commentList[i]) free(commentList[i]);
18343                 commentList[i] = NULL;
18344         }
18345         framePtr = MAX_MOVES-1;
18346         storedGames = 0;
18347 }
18348
18349 void
18350 LoadVariation (int index, char *text)
18351 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18352         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18353         int level = 0, move;
18354
18355         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18356         // first find outermost bracketing variation
18357         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18358             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18359                 if(*p == '{') wait = '}'; else
18360                 if(*p == '[') wait = ']'; else
18361                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18362                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18363             }
18364             if(*p == wait) wait = NULLCHAR; // closing ]} found
18365             p++;
18366         }
18367         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18368         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18369         end[1] = NULLCHAR; // clip off comment beyond variation
18370         ToNrEvent(currentMove-1);
18371         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18372         // kludge: use ParsePV() to append variation to game
18373         move = currentMove;
18374         ParsePV(start, TRUE, TRUE);
18375         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18376         ClearPremoveHighlights();
18377         CommentPopDown();
18378         ToNrEvent(currentMove+1);
18379 }
18380
18381 void
18382 LoadTheme ()
18383 {
18384     char *p, *q, buf[MSG_SIZ];
18385     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18386         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18387         ParseArgsFromString(buf);
18388         ActivateTheme(TRUE); // also redo colors
18389         return;
18390     }
18391     p = nickName;
18392     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18393     {
18394         int len;
18395         q = appData.themeNames;
18396         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18397       if(appData.useBitmaps) {
18398         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18399                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18400                 appData.liteBackTextureMode,
18401                 appData.darkBackTextureMode );
18402       } else {
18403         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18404                 Col2Text(2),   // lightSquareColor
18405                 Col2Text(3) ); // darkSquareColor
18406       }
18407       if(appData.useBorder) {
18408         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18409                 appData.border);
18410       } else {
18411         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18412       }
18413       if(appData.useFont) {
18414         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18415                 appData.renderPiecesWithFont,
18416                 appData.fontToPieceTable,
18417                 Col2Text(9),    // appData.fontBackColorWhite
18418                 Col2Text(10) ); // appData.fontForeColorBlack
18419       } else {
18420         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18421                 appData.pieceDirectory);
18422         if(!appData.pieceDirectory[0])
18423           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18424                 Col2Text(0),   // whitePieceColor
18425                 Col2Text(1) ); // blackPieceColor
18426       }
18427       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18428                 Col2Text(4),   // highlightSquareColor
18429                 Col2Text(5) ); // premoveHighlightColor
18430         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18431         if(insert != q) insert[-1] = NULLCHAR;
18432         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18433         if(q)   free(q);
18434     }
18435     ActivateTheme(FALSE);
18436 }