Let Ctrl-O key open chat for last talker
[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;
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                         chattingPartner = -1;
3076                         next_out = i+1; // [HGM] suppress printing in ICS window
3077                     } else
3078                     if(!suppressKibitz) // [HGM] kibitz
3079                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3080                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3081                         int nrDigit = 0, nrAlph = 0, j;
3082                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3083                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3084                         parse[parse_pos] = NULLCHAR;
3085                         // try to be smart: if it does not look like search info, it should go to
3086                         // ICS interaction window after all, not to engine-output window.
3087                         for(j=0; j<parse_pos; j++) { // count letters and digits
3088                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3089                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3090                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3091                         }
3092                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3093                             int depth=0; float score;
3094                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3095                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3096                                 pvInfoList[forwardMostMove-1].depth = depth;
3097                                 pvInfoList[forwardMostMove-1].score = 100*score;
3098                             }
3099                             OutputKibitz(suppressKibitz, parse);
3100                         } else {
3101                             char tmp[MSG_SIZ];
3102                             if(gameMode == IcsObserving) // restore original ICS messages
3103                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3104                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3105                             else
3106                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3107                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3108                             SendToPlayer(tmp, strlen(tmp));
3109                         }
3110                         next_out = i+1; // [HGM] suppress printing in ICS window
3111                     }
3112                     started = STARTED_NONE;
3113                 } else {
3114                     /* Don't match patterns against characters in comment */
3115                     i++;
3116                     continue;
3117                 }
3118             }
3119             if (started == STARTED_CHATTER) {
3120                 if (buf[i] != '\n') {
3121                     /* Don't match patterns against characters in chatter */
3122                     i++;
3123                     continue;
3124                 }
3125                 started = STARTED_NONE;
3126                 if(suppressKibitz) next_out = i+1;
3127             }
3128
3129             /* Kludge to deal with rcmd protocol */
3130             if (firstTime && looking_at(buf, &i, "\001*")) {
3131                 DisplayFatalError(&buf[1], 0, 1);
3132                 continue;
3133             } else {
3134                 firstTime = FALSE;
3135             }
3136
3137             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3138                 ics_type = ICS_ICC;
3139                 ics_prefix = "/";
3140                 if (appData.debugMode)
3141                   fprintf(debugFP, "ics_type %d\n", ics_type);
3142                 continue;
3143             }
3144             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3145                 ics_type = ICS_FICS;
3146                 ics_prefix = "$";
3147                 if (appData.debugMode)
3148                   fprintf(debugFP, "ics_type %d\n", ics_type);
3149                 continue;
3150             }
3151             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3152                 ics_type = ICS_CHESSNET;
3153                 ics_prefix = "/";
3154                 if (appData.debugMode)
3155                   fprintf(debugFP, "ics_type %d\n", ics_type);
3156                 continue;
3157             }
3158
3159             if (!loggedOn &&
3160                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3161                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3162                  looking_at(buf, &i, "will be \"*\""))) {
3163               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3164               continue;
3165             }
3166
3167             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3168               char buf[MSG_SIZ];
3169               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3170               DisplayIcsInteractionTitle(buf);
3171               have_set_title = TRUE;
3172             }
3173
3174             /* skip finger notes */
3175             if (started == STARTED_NONE &&
3176                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3177                  (buf[i] == '1' && buf[i+1] == '0')) &&
3178                 buf[i+2] == ':' && buf[i+3] == ' ') {
3179               started = STARTED_CHATTER;
3180               i += 3;
3181               continue;
3182             }
3183
3184             oldi = i;
3185             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3186             if(appData.seekGraph) {
3187                 if(soughtPending && MatchSoughtLine(buf+i)) {
3188                     i = strstr(buf+i, "rated") - buf;
3189                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3190                     next_out = leftover_start = i;
3191                     started = STARTED_CHATTER;
3192                     suppressKibitz = TRUE;
3193                     continue;
3194                 }
3195                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3196                         && looking_at(buf, &i, "* ads displayed")) {
3197                     soughtPending = FALSE;
3198                     seekGraphUp = TRUE;
3199                     DrawSeekGraph();
3200                     continue;
3201                 }
3202                 if(appData.autoRefresh) {
3203                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3204                         int s = (ics_type == ICS_ICC); // ICC format differs
3205                         if(seekGraphUp)
3206                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3207                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3208                         looking_at(buf, &i, "*% "); // eat prompt
3209                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3210                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3211                         next_out = i; // suppress
3212                         continue;
3213                     }
3214                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3215                         char *p = star_match[0];
3216                         while(*p) {
3217                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3218                             while(*p && *p++ != ' '); // next
3219                         }
3220                         looking_at(buf, &i, "*% "); // eat prompt
3221                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3222                         next_out = i;
3223                         continue;
3224                     }
3225                 }
3226             }
3227
3228             /* skip formula vars */
3229             if (started == STARTED_NONE &&
3230                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3231               started = STARTED_CHATTER;
3232               i += 3;
3233               continue;
3234             }
3235
3236             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3237             if (appData.autoKibitz && started == STARTED_NONE &&
3238                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3239                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3240                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3241                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3242                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3243                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3244                         suppressKibitz = TRUE;
3245                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3246                         next_out = i;
3247                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3248                                 && (gameMode == IcsPlayingWhite)) ||
3249                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3250                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3251                             started = STARTED_CHATTER; // own kibitz we simply discard
3252                         else {
3253                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3254                             parse_pos = 0; parse[0] = NULLCHAR;
3255                             savingComment = TRUE;
3256                             suppressKibitz = gameMode != IcsObserving ? 2 :
3257                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3258                         }
3259                         continue;
3260                 } else
3261                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3262                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3263                          && atoi(star_match[0])) {
3264                     // suppress the acknowledgements of our own autoKibitz
3265                     char *p;
3266                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3267                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3268                     SendToPlayer(star_match[0], strlen(star_match[0]));
3269                     if(looking_at(buf, &i, "*% ")) // eat prompt
3270                         suppressKibitz = FALSE;
3271                     next_out = i;
3272                     continue;
3273                 }
3274             } // [HGM] kibitz: end of patch
3275
3276             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3277
3278             // [HGM] chat: intercept tells by users for which we have an open chat window
3279             channel = -1;
3280             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3281                                            looking_at(buf, &i, "* whispers:") ||
3282                                            looking_at(buf, &i, "* kibitzes:") ||
3283                                            looking_at(buf, &i, "* shouts:") ||
3284                                            looking_at(buf, &i, "* c-shouts:") ||
3285                                            looking_at(buf, &i, "--> * ") ||
3286                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3287                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3288                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3289                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3290                 int p;
3291                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3292                 chattingPartner = -1;
3293
3294                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3295                 for(p=0; p<MAX_CHAT; p++) {
3296                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3297                     talker[0] = '['; strcat(talker, "] ");
3298                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3299                     chattingPartner = p; break;
3300                     }
3301                 } else
3302                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3303                 for(p=0; p<MAX_CHAT; p++) {
3304                     if(!strcmp("kibitzes", chatPartner[p])) {
3305                         talker[0] = '['; strcat(talker, "] ");
3306                         chattingPartner = p; break;
3307                     }
3308                 } else
3309                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3310                 for(p=0; p<MAX_CHAT; p++) {
3311                     if(!strcmp("whispers", chatPartner[p])) {
3312                         talker[0] = '['; strcat(talker, "] ");
3313                         chattingPartner = p; break;
3314                     }
3315                 } else
3316                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3317                   if(buf[i-8] == '-' && buf[i-3] == 't')
3318                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3319                     if(!strcmp("c-shouts", chatPartner[p])) {
3320                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3321                         chattingPartner = p; break;
3322                     }
3323                   }
3324                   if(chattingPartner < 0)
3325                   for(p=0; p<MAX_CHAT; p++) {
3326                     if(!strcmp("shouts", chatPartner[p])) {
3327                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3328                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3329                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3330                         chattingPartner = p; break;
3331                     }
3332                   }
3333                 }
3334                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3335                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3336                     talker[0] = 0; Colorize(ColorTell, FALSE);
3337                     chattingPartner = p; break;
3338                 }
3339                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3340                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3341                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3342                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3343                     started = STARTED_COMMENT;
3344                     parse_pos = 0; parse[0] = NULLCHAR;
3345                     savingComment = 3 + chattingPartner; // counts as TRUE
3346                     suppressKibitz = TRUE;
3347                     continue;
3348                 }
3349             } // [HGM] chat: end of patch
3350
3351           backup = i;
3352             if (appData.zippyTalk || appData.zippyPlay) {
3353                 /* [DM] Backup address for color zippy lines */
3354 #if ZIPPY
3355                if (loggedOn == TRUE)
3356                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3357                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3358 #endif
3359             } // [DM] 'else { ' deleted
3360                 if (
3361                     /* Regular tells and says */
3362                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3363                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3364                     looking_at(buf, &i, "* says: ") ||
3365                     /* Don't color "message" or "messages" output */
3366                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3367                     looking_at(buf, &i, "*. * at *:*: ") ||
3368                     looking_at(buf, &i, "--* (*:*): ") ||
3369                     /* Message notifications (same color as tells) */
3370                     looking_at(buf, &i, "* has left a message ") ||
3371                     looking_at(buf, &i, "* just sent you a message:\n") ||
3372                     /* Whispers and kibitzes */
3373                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3374                     looking_at(buf, &i, "* kibitzes: ") ||
3375                     /* Channel tells */
3376                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3377
3378                   if (tkind == 1 && strchr(star_match[0], ':')) {
3379                       /* Avoid "tells you:" spoofs in channels */
3380                      tkind = 3;
3381                   }
3382                   if (star_match[0][0] == NULLCHAR ||
3383                       strchr(star_match[0], ' ') ||
3384                       (tkind == 3 && strchr(star_match[1], ' '))) {
3385                     /* Reject bogus matches */
3386                     i = oldi;
3387                   } else {
3388                     if (appData.colorize) {
3389                       if (oldi > next_out) {
3390                         SendToPlayer(&buf[next_out], oldi - next_out);
3391                         next_out = oldi;
3392                       }
3393                       switch (tkind) {
3394                       case 1:
3395                         Colorize(ColorTell, FALSE);
3396                         curColor = ColorTell;
3397                         break;
3398                       case 2:
3399                         Colorize(ColorKibitz, FALSE);
3400                         curColor = ColorKibitz;
3401                         break;
3402                       case 3:
3403                         p = strrchr(star_match[1], '(');
3404                         if (p == NULL) {
3405                           p = star_match[1];
3406                         } else {
3407                           p++;
3408                         }
3409                         if (atoi(p) == 1) {
3410                           Colorize(ColorChannel1, FALSE);
3411                           curColor = ColorChannel1;
3412                         } else {
3413                           Colorize(ColorChannel, FALSE);
3414                           curColor = ColorChannel;
3415                         }
3416                         break;
3417                       case 5:
3418                         curColor = ColorNormal;
3419                         break;
3420                       }
3421                     }
3422                     if (started == STARTED_NONE && appData.autoComment &&
3423                         (gameMode == IcsObserving ||
3424                          gameMode == IcsPlayingWhite ||
3425                          gameMode == IcsPlayingBlack)) {
3426                       parse_pos = i - oldi;
3427                       memcpy(parse, &buf[oldi], parse_pos);
3428                       parse[parse_pos] = NULLCHAR;
3429                       started = STARTED_COMMENT;
3430                       savingComment = TRUE;
3431                     } else {
3432                       started = STARTED_CHATTER;
3433                       savingComment = FALSE;
3434                     }
3435                     loggedOn = TRUE;
3436                     continue;
3437                   }
3438                 }
3439
3440                 if (looking_at(buf, &i, "* s-shouts: ") ||
3441                     looking_at(buf, &i, "* c-shouts: ")) {
3442                     if (appData.colorize) {
3443                         if (oldi > next_out) {
3444                             SendToPlayer(&buf[next_out], oldi - next_out);
3445                             next_out = oldi;
3446                         }
3447                         Colorize(ColorSShout, FALSE);
3448                         curColor = ColorSShout;
3449                     }
3450                     loggedOn = TRUE;
3451                     started = STARTED_CHATTER;
3452                     continue;
3453                 }
3454
3455                 if (looking_at(buf, &i, "--->")) {
3456                     loggedOn = TRUE;
3457                     continue;
3458                 }
3459
3460                 if (looking_at(buf, &i, "* shouts: ") ||
3461                     looking_at(buf, &i, "--> ")) {
3462                     if (appData.colorize) {
3463                         if (oldi > next_out) {
3464                             SendToPlayer(&buf[next_out], oldi - next_out);
3465                             next_out = oldi;
3466                         }
3467                         Colorize(ColorShout, FALSE);
3468                         curColor = ColorShout;
3469                     }
3470                     loggedOn = TRUE;
3471                     started = STARTED_CHATTER;
3472                     continue;
3473                 }
3474
3475                 if (looking_at( buf, &i, "Challenge:")) {
3476                     if (appData.colorize) {
3477                         if (oldi > next_out) {
3478                             SendToPlayer(&buf[next_out], oldi - next_out);
3479                             next_out = oldi;
3480                         }
3481                         Colorize(ColorChallenge, FALSE);
3482                         curColor = ColorChallenge;
3483                     }
3484                     loggedOn = TRUE;
3485                     continue;
3486                 }
3487
3488                 if (looking_at(buf, &i, "* offers you") ||
3489                     looking_at(buf, &i, "* offers to be") ||
3490                     looking_at(buf, &i, "* would like to") ||
3491                     looking_at(buf, &i, "* requests to") ||
3492                     looking_at(buf, &i, "Your opponent offers") ||
3493                     looking_at(buf, &i, "Your opponent requests")) {
3494
3495                     if (appData.colorize) {
3496                         if (oldi > next_out) {
3497                             SendToPlayer(&buf[next_out], oldi - next_out);
3498                             next_out = oldi;
3499                         }
3500                         Colorize(ColorRequest, FALSE);
3501                         curColor = ColorRequest;
3502                     }
3503                     continue;
3504                 }
3505
3506                 if (looking_at(buf, &i, "* (*) seeking")) {
3507                     if (appData.colorize) {
3508                         if (oldi > next_out) {
3509                             SendToPlayer(&buf[next_out], oldi - next_out);
3510                             next_out = oldi;
3511                         }
3512                         Colorize(ColorSeek, FALSE);
3513                         curColor = ColorSeek;
3514                     }
3515                     continue;
3516             }
3517
3518           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3519
3520             if (looking_at(buf, &i, "\\   ")) {
3521                 if (prevColor != ColorNormal) {
3522                     if (oldi > next_out) {
3523                         SendToPlayer(&buf[next_out], oldi - next_out);
3524                         next_out = oldi;
3525                     }
3526                     Colorize(prevColor, TRUE);
3527                     curColor = prevColor;
3528                 }
3529                 if (savingComment) {
3530                     parse_pos = i - oldi;
3531                     memcpy(parse, &buf[oldi], parse_pos);
3532                     parse[parse_pos] = NULLCHAR;
3533                     started = STARTED_COMMENT;
3534                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3535                         chattingPartner = savingComment - 3; // kludge to remember the box
3536                 } else {
3537                     started = STARTED_CHATTER;
3538                 }
3539                 continue;
3540             }
3541
3542             if (looking_at(buf, &i, "Black Strength :") ||
3543                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3544                 looking_at(buf, &i, "<10>") ||
3545                 looking_at(buf, &i, "#@#")) {
3546                 /* Wrong board style */
3547                 loggedOn = TRUE;
3548                 SendToICS(ics_prefix);
3549                 SendToICS("set style 12\n");
3550                 SendToICS(ics_prefix);
3551                 SendToICS("refresh\n");
3552                 continue;
3553             }
3554
3555             if (looking_at(buf, &i, "login:")) {
3556               if (!have_sent_ICS_logon) {
3557                 if(ICSInitScript())
3558                   have_sent_ICS_logon = 1;
3559                 else // no init script was found
3560                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3561               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3562                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3563               }
3564                 continue;
3565             }
3566
3567             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3568                 (looking_at(buf, &i, "\n<12> ") ||
3569                  looking_at(buf, &i, "<12> "))) {
3570                 loggedOn = TRUE;
3571                 if (oldi > next_out) {
3572                     SendToPlayer(&buf[next_out], oldi - next_out);
3573                 }
3574                 next_out = i;
3575                 started = STARTED_BOARD;
3576                 parse_pos = 0;
3577                 continue;
3578             }
3579
3580             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3581                 looking_at(buf, &i, "<b1> ")) {
3582                 if (oldi > next_out) {
3583                     SendToPlayer(&buf[next_out], oldi - next_out);
3584                 }
3585                 next_out = i;
3586                 started = STARTED_HOLDINGS;
3587                 parse_pos = 0;
3588                 continue;
3589             }
3590
3591             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3592                 loggedOn = TRUE;
3593                 /* Header for a move list -- first line */
3594
3595                 switch (ics_getting_history) {
3596                   case H_FALSE:
3597                     switch (gameMode) {
3598                       case IcsIdle:
3599                       case BeginningOfGame:
3600                         /* User typed "moves" or "oldmoves" while we
3601                            were idle.  Pretend we asked for these
3602                            moves and soak them up so user can step
3603                            through them and/or save them.
3604                            */
3605                         Reset(FALSE, TRUE);
3606                         gameMode = IcsObserving;
3607                         ModeHighlight();
3608                         ics_gamenum = -1;
3609                         ics_getting_history = H_GOT_UNREQ_HEADER;
3610                         break;
3611                       case EditGame: /*?*/
3612                       case EditPosition: /*?*/
3613                         /* Should above feature work in these modes too? */
3614                         /* For now it doesn't */
3615                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3616                         break;
3617                       default:
3618                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3619                         break;
3620                     }
3621                     break;
3622                   case H_REQUESTED:
3623                     /* Is this the right one? */
3624                     if (gameInfo.white && gameInfo.black &&
3625                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3626                         strcmp(gameInfo.black, star_match[2]) == 0) {
3627                         /* All is well */
3628                         ics_getting_history = H_GOT_REQ_HEADER;
3629                     }
3630                     break;
3631                   case H_GOT_REQ_HEADER:
3632                   case H_GOT_UNREQ_HEADER:
3633                   case H_GOT_UNWANTED_HEADER:
3634                   case H_GETTING_MOVES:
3635                     /* Should not happen */
3636                     DisplayError(_("Error gathering move list: two headers"), 0);
3637                     ics_getting_history = H_FALSE;
3638                     break;
3639                 }
3640
3641                 /* Save player ratings into gameInfo if needed */
3642                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3643                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3644                     (gameInfo.whiteRating == -1 ||
3645                      gameInfo.blackRating == -1)) {
3646
3647                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3648                     gameInfo.blackRating = string_to_rating(star_match[3]);
3649                     if (appData.debugMode)
3650                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3651                               gameInfo.whiteRating, gameInfo.blackRating);
3652                 }
3653                 continue;
3654             }
3655
3656             if (looking_at(buf, &i,
3657               "* * match, initial time: * minute*, increment: * second")) {
3658                 /* Header for a move list -- second line */
3659                 /* Initial board will follow if this is a wild game */
3660                 if (gameInfo.event != NULL) free(gameInfo.event);
3661                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3662                 gameInfo.event = StrSave(str);
3663                 /* [HGM] we switched variant. Translate boards if needed. */
3664                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3665                 continue;
3666             }
3667
3668             if (looking_at(buf, &i, "Move  ")) {
3669                 /* Beginning of a move list */
3670                 switch (ics_getting_history) {
3671                   case H_FALSE:
3672                     /* Normally should not happen */
3673                     /* Maybe user hit reset while we were parsing */
3674                     break;
3675                   case H_REQUESTED:
3676                     /* Happens if we are ignoring a move list that is not
3677                      * the one we just requested.  Common if the user
3678                      * tries to observe two games without turning off
3679                      * getMoveList */
3680                     break;
3681                   case H_GETTING_MOVES:
3682                     /* Should not happen */
3683                     DisplayError(_("Error gathering move list: nested"), 0);
3684                     ics_getting_history = H_FALSE;
3685                     break;
3686                   case H_GOT_REQ_HEADER:
3687                     ics_getting_history = H_GETTING_MOVES;
3688                     started = STARTED_MOVES;
3689                     parse_pos = 0;
3690                     if (oldi > next_out) {
3691                         SendToPlayer(&buf[next_out], oldi - next_out);
3692                     }
3693                     break;
3694                   case H_GOT_UNREQ_HEADER:
3695                     ics_getting_history = H_GETTING_MOVES;
3696                     started = STARTED_MOVES_NOHIDE;
3697                     parse_pos = 0;
3698                     break;
3699                   case H_GOT_UNWANTED_HEADER:
3700                     ics_getting_history = H_FALSE;
3701                     break;
3702                 }
3703                 continue;
3704             }
3705
3706             if (looking_at(buf, &i, "% ") ||
3707                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3708                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3709                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3710                     soughtPending = FALSE;
3711                     seekGraphUp = TRUE;
3712                     DrawSeekGraph();
3713                 }
3714                 if(suppressKibitz) next_out = i;
3715                 savingComment = FALSE;
3716                 suppressKibitz = 0;
3717                 switch (started) {
3718                   case STARTED_MOVES:
3719                   case STARTED_MOVES_NOHIDE:
3720                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3721                     parse[parse_pos + i - oldi] = NULLCHAR;
3722                     ParseGameHistory(parse);
3723 #if ZIPPY
3724                     if (appData.zippyPlay && first.initDone) {
3725                         FeedMovesToProgram(&first, forwardMostMove);
3726                         if (gameMode == IcsPlayingWhite) {
3727                             if (WhiteOnMove(forwardMostMove)) {
3728                                 if (first.sendTime) {
3729                                   if (first.useColors) {
3730                                     SendToProgram("black\n", &first);
3731                                   }
3732                                   SendTimeRemaining(&first, TRUE);
3733                                 }
3734                                 if (first.useColors) {
3735                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3736                                 }
3737                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3738                                 first.maybeThinking = TRUE;
3739                             } else {
3740                                 if (first.usePlayother) {
3741                                   if (first.sendTime) {
3742                                     SendTimeRemaining(&first, TRUE);
3743                                   }
3744                                   SendToProgram("playother\n", &first);
3745                                   firstMove = FALSE;
3746                                 } else {
3747                                   firstMove = TRUE;
3748                                 }
3749                             }
3750                         } else if (gameMode == IcsPlayingBlack) {
3751                             if (!WhiteOnMove(forwardMostMove)) {
3752                                 if (first.sendTime) {
3753                                   if (first.useColors) {
3754                                     SendToProgram("white\n", &first);
3755                                   }
3756                                   SendTimeRemaining(&first, FALSE);
3757                                 }
3758                                 if (first.useColors) {
3759                                   SendToProgram("black\n", &first);
3760                                 }
3761                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3762                                 first.maybeThinking = TRUE;
3763                             } else {
3764                                 if (first.usePlayother) {
3765                                   if (first.sendTime) {
3766                                     SendTimeRemaining(&first, FALSE);
3767                                   }
3768                                   SendToProgram("playother\n", &first);
3769                                   firstMove = FALSE;
3770                                 } else {
3771                                   firstMove = TRUE;
3772                                 }
3773                             }
3774                         }
3775                     }
3776 #endif
3777                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3778                         /* Moves came from oldmoves or moves command
3779                            while we weren't doing anything else.
3780                            */
3781                         currentMove = forwardMostMove;
3782                         ClearHighlights();/*!!could figure this out*/
3783                         flipView = appData.flipView;
3784                         DrawPosition(TRUE, boards[currentMove]);
3785                         DisplayBothClocks();
3786                         snprintf(str, MSG_SIZ, "%s %s %s",
3787                                 gameInfo.white, _("vs."),  gameInfo.black);
3788                         DisplayTitle(str);
3789                         gameMode = IcsIdle;
3790                     } else {
3791                         /* Moves were history of an active game */
3792                         if (gameInfo.resultDetails != NULL) {
3793                             free(gameInfo.resultDetails);
3794                             gameInfo.resultDetails = NULL;
3795                         }
3796                     }
3797                     HistorySet(parseList, backwardMostMove,
3798                                forwardMostMove, currentMove-1);
3799                     DisplayMove(currentMove - 1);
3800                     if (started == STARTED_MOVES) next_out = i;
3801                     started = STARTED_NONE;
3802                     ics_getting_history = H_FALSE;
3803                     break;
3804
3805                   case STARTED_OBSERVE:
3806                     started = STARTED_NONE;
3807                     SendToICS(ics_prefix);
3808                     SendToICS("refresh\n");
3809                     break;
3810
3811                   default:
3812                     break;
3813                 }
3814                 if(bookHit) { // [HGM] book: simulate book reply
3815                     static char bookMove[MSG_SIZ]; // a bit generous?
3816
3817                     programStats.nodes = programStats.depth = programStats.time =
3818                     programStats.score = programStats.got_only_move = 0;
3819                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3820
3821                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3822                     strcat(bookMove, bookHit);
3823                     HandleMachineMove(bookMove, &first);
3824                 }
3825                 continue;
3826             }
3827
3828             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3829                  started == STARTED_HOLDINGS ||
3830                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3831                 /* Accumulate characters in move list or board */
3832                 parse[parse_pos++] = buf[i];
3833             }
3834
3835             /* Start of game messages.  Mostly we detect start of game
3836                when the first board image arrives.  On some versions
3837                of the ICS, though, we need to do a "refresh" after starting
3838                to observe in order to get the current board right away. */
3839             if (looking_at(buf, &i, "Adding game * to observation list")) {
3840                 started = STARTED_OBSERVE;
3841                 continue;
3842             }
3843
3844             /* Handle auto-observe */
3845             if (appData.autoObserve &&
3846                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3847                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3848                 char *player;
3849                 /* Choose the player that was highlighted, if any. */
3850                 if (star_match[0][0] == '\033' ||
3851                     star_match[1][0] != '\033') {
3852                     player = star_match[0];
3853                 } else {
3854                     player = star_match[2];
3855                 }
3856                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3857                         ics_prefix, StripHighlightAndTitle(player));
3858                 SendToICS(str);
3859
3860                 /* Save ratings from notify string */
3861                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3862                 player1Rating = string_to_rating(star_match[1]);
3863                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3864                 player2Rating = string_to_rating(star_match[3]);
3865
3866                 if (appData.debugMode)
3867                   fprintf(debugFP,
3868                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3869                           player1Name, player1Rating,
3870                           player2Name, player2Rating);
3871
3872                 continue;
3873             }
3874
3875             /* Deal with automatic examine mode after a game,
3876                and with IcsObserving -> IcsExamining transition */
3877             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3878                 looking_at(buf, &i, "has made you an examiner of game *")) {
3879
3880                 int gamenum = atoi(star_match[0]);
3881                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3882                     gamenum == ics_gamenum) {
3883                     /* We were already playing or observing this game;
3884                        no need to refetch history */
3885                     gameMode = IcsExamining;
3886                     if (pausing) {
3887                         pauseExamForwardMostMove = forwardMostMove;
3888                     } else if (currentMove < forwardMostMove) {
3889                         ForwardInner(forwardMostMove);
3890                     }
3891                 } else {
3892                     /* I don't think this case really can happen */
3893                     SendToICS(ics_prefix);
3894                     SendToICS("refresh\n");
3895                 }
3896                 continue;
3897             }
3898
3899             /* Error messages */
3900 //          if (ics_user_moved) {
3901             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3902                 if (looking_at(buf, &i, "Illegal move") ||
3903                     looking_at(buf, &i, "Not a legal move") ||
3904                     looking_at(buf, &i, "Your king is in check") ||
3905                     looking_at(buf, &i, "It isn't your turn") ||
3906                     looking_at(buf, &i, "It is not your move")) {
3907                     /* Illegal move */
3908                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3909                         currentMove = forwardMostMove-1;
3910                         DisplayMove(currentMove - 1); /* before DMError */
3911                         DrawPosition(FALSE, boards[currentMove]);
3912                         SwitchClocks(forwardMostMove-1); // [HGM] race
3913                         DisplayBothClocks();
3914                     }
3915                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3916                     ics_user_moved = 0;
3917                     continue;
3918                 }
3919             }
3920
3921             if (looking_at(buf, &i, "still have time") ||
3922                 looking_at(buf, &i, "not out of time") ||
3923                 looking_at(buf, &i, "either player is out of time") ||
3924                 looking_at(buf, &i, "has timeseal; checking")) {
3925                 /* We must have called his flag a little too soon */
3926                 whiteFlag = blackFlag = FALSE;
3927                 continue;
3928             }
3929
3930             if (looking_at(buf, &i, "added * seconds to") ||
3931                 looking_at(buf, &i, "seconds were added to")) {
3932                 /* Update the clocks */
3933                 SendToICS(ics_prefix);
3934                 SendToICS("refresh\n");
3935                 continue;
3936             }
3937
3938             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3939                 ics_clock_paused = TRUE;
3940                 StopClocks();
3941                 continue;
3942             }
3943
3944             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3945                 ics_clock_paused = FALSE;
3946                 StartClocks();
3947                 continue;
3948             }
3949
3950             /* Grab player ratings from the Creating: message.
3951                Note we have to check for the special case when
3952                the ICS inserts things like [white] or [black]. */
3953             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3954                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3955                 /* star_matches:
3956                    0    player 1 name (not necessarily white)
3957                    1    player 1 rating
3958                    2    empty, white, or black (IGNORED)
3959                    3    player 2 name (not necessarily black)
3960                    4    player 2 rating
3961
3962                    The names/ratings are sorted out when the game
3963                    actually starts (below).
3964                 */
3965                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3966                 player1Rating = string_to_rating(star_match[1]);
3967                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3968                 player2Rating = string_to_rating(star_match[4]);
3969
3970                 if (appData.debugMode)
3971                   fprintf(debugFP,
3972                           "Ratings from 'Creating:' %s %d, %s %d\n",
3973                           player1Name, player1Rating,
3974                           player2Name, player2Rating);
3975
3976                 continue;
3977             }
3978
3979             /* Improved generic start/end-of-game messages */
3980             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3981                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3982                 /* If tkind == 0: */
3983                 /* star_match[0] is the game number */
3984                 /*           [1] is the white player's name */
3985                 /*           [2] is the black player's name */
3986                 /* For end-of-game: */
3987                 /*           [3] is the reason for the game end */
3988                 /*           [4] is a PGN end game-token, preceded by " " */
3989                 /* For start-of-game: */
3990                 /*           [3] begins with "Creating" or "Continuing" */
3991                 /*           [4] is " *" or empty (don't care). */
3992                 int gamenum = atoi(star_match[0]);
3993                 char *whitename, *blackname, *why, *endtoken;
3994                 ChessMove endtype = EndOfFile;
3995
3996                 if (tkind == 0) {
3997                   whitename = star_match[1];
3998                   blackname = star_match[2];
3999                   why = star_match[3];
4000                   endtoken = star_match[4];
4001                 } else {
4002                   whitename = star_match[1];
4003                   blackname = star_match[3];
4004                   why = star_match[5];
4005                   endtoken = star_match[6];
4006                 }
4007
4008                 /* Game start messages */
4009                 if (strncmp(why, "Creating ", 9) == 0 ||
4010                     strncmp(why, "Continuing ", 11) == 0) {
4011                     gs_gamenum = gamenum;
4012                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4013                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4014                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4015 #if ZIPPY
4016                     if (appData.zippyPlay) {
4017                         ZippyGameStart(whitename, blackname);
4018                     }
4019 #endif /*ZIPPY*/
4020                     partnerBoardValid = FALSE; // [HGM] bughouse
4021                     continue;
4022                 }
4023
4024                 /* Game end messages */
4025                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4026                     ics_gamenum != gamenum) {
4027                     continue;
4028                 }
4029                 while (endtoken[0] == ' ') endtoken++;
4030                 switch (endtoken[0]) {
4031                   case '*':
4032                   default:
4033                     endtype = GameUnfinished;
4034                     break;
4035                   case '0':
4036                     endtype = BlackWins;
4037                     break;
4038                   case '1':
4039                     if (endtoken[1] == '/')
4040                       endtype = GameIsDrawn;
4041                     else
4042                       endtype = WhiteWins;
4043                     break;
4044                 }
4045                 GameEnds(endtype, why, GE_ICS);
4046 #if ZIPPY
4047                 if (appData.zippyPlay && first.initDone) {
4048                     ZippyGameEnd(endtype, why);
4049                     if (first.pr == NoProc) {
4050                       /* Start the next process early so that we'll
4051                          be ready for the next challenge */
4052                       StartChessProgram(&first);
4053                     }
4054                     /* Send "new" early, in case this command takes
4055                        a long time to finish, so that we'll be ready
4056                        for the next challenge. */
4057                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4058                     Reset(TRUE, TRUE);
4059                 }
4060 #endif /*ZIPPY*/
4061                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4062                 continue;
4063             }
4064
4065             if (looking_at(buf, &i, "Removing game * from observation") ||
4066                 looking_at(buf, &i, "no longer observing game *") ||
4067                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4068                 if (gameMode == IcsObserving &&
4069                     atoi(star_match[0]) == ics_gamenum)
4070                   {
4071                       /* icsEngineAnalyze */
4072                       if (appData.icsEngineAnalyze) {
4073                             ExitAnalyzeMode();
4074                             ModeHighlight();
4075                       }
4076                       StopClocks();
4077                       gameMode = IcsIdle;
4078                       ics_gamenum = -1;
4079                       ics_user_moved = FALSE;
4080                   }
4081                 continue;
4082             }
4083
4084             if (looking_at(buf, &i, "no longer examining game *")) {
4085                 if (gameMode == IcsExamining &&
4086                     atoi(star_match[0]) == ics_gamenum)
4087                   {
4088                       gameMode = IcsIdle;
4089                       ics_gamenum = -1;
4090                       ics_user_moved = FALSE;
4091                   }
4092                 continue;
4093             }
4094
4095             /* Advance leftover_start past any newlines we find,
4096                so only partial lines can get reparsed */
4097             if (looking_at(buf, &i, "\n")) {
4098                 prevColor = curColor;
4099                 if (curColor != ColorNormal) {
4100                     if (oldi > next_out) {
4101                         SendToPlayer(&buf[next_out], oldi - next_out);
4102                         next_out = oldi;
4103                     }
4104                     Colorize(ColorNormal, FALSE);
4105                     curColor = ColorNormal;
4106                 }
4107                 if (started == STARTED_BOARD) {
4108                     started = STARTED_NONE;
4109                     parse[parse_pos] = NULLCHAR;
4110                     ParseBoard12(parse);
4111                     ics_user_moved = 0;
4112
4113                     /* Send premove here */
4114                     if (appData.premove) {
4115                       char str[MSG_SIZ];
4116                       if (currentMove == 0 &&
4117                           gameMode == IcsPlayingWhite &&
4118                           appData.premoveWhite) {
4119                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4120                         if (appData.debugMode)
4121                           fprintf(debugFP, "Sending premove:\n");
4122                         SendToICS(str);
4123                       } else if (currentMove == 1 &&
4124                                  gameMode == IcsPlayingBlack &&
4125                                  appData.premoveBlack) {
4126                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4127                         if (appData.debugMode)
4128                           fprintf(debugFP, "Sending premove:\n");
4129                         SendToICS(str);
4130                       } else if (gotPremove) {
4131                         gotPremove = 0;
4132                         ClearPremoveHighlights();
4133                         if (appData.debugMode)
4134                           fprintf(debugFP, "Sending premove:\n");
4135                           UserMoveEvent(premoveFromX, premoveFromY,
4136                                         premoveToX, premoveToY,
4137                                         premovePromoChar);
4138                       }
4139                     }
4140
4141                     /* Usually suppress following prompt */
4142                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4143                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4144                         if (looking_at(buf, &i, "*% ")) {
4145                             savingComment = FALSE;
4146                             suppressKibitz = 0;
4147                         }
4148                     }
4149                     next_out = i;
4150                 } else if (started == STARTED_HOLDINGS) {
4151                     int gamenum;
4152                     char new_piece[MSG_SIZ];
4153                     started = STARTED_NONE;
4154                     parse[parse_pos] = NULLCHAR;
4155                     if (appData.debugMode)
4156                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4157                                                         parse, currentMove);
4158                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4159                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4160                         if (gameInfo.variant == VariantNormal) {
4161                           /* [HGM] We seem to switch variant during a game!
4162                            * Presumably no holdings were displayed, so we have
4163                            * to move the position two files to the right to
4164                            * create room for them!
4165                            */
4166                           VariantClass newVariant;
4167                           switch(gameInfo.boardWidth) { // base guess on board width
4168                                 case 9:  newVariant = VariantShogi; break;
4169                                 case 10: newVariant = VariantGreat; break;
4170                                 default: newVariant = VariantCrazyhouse; break;
4171                           }
4172                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4173                           /* Get a move list just to see the header, which
4174                              will tell us whether this is really bug or zh */
4175                           if (ics_getting_history == H_FALSE) {
4176                             ics_getting_history = H_REQUESTED;
4177                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4178                             SendToICS(str);
4179                           }
4180                         }
4181                         new_piece[0] = NULLCHAR;
4182                         sscanf(parse, "game %d white [%s black [%s <- %s",
4183                                &gamenum, white_holding, black_holding,
4184                                new_piece);
4185                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4186                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4187                         /* [HGM] copy holdings to board holdings area */
4188                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4189                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4190                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4191 #if ZIPPY
4192                         if (appData.zippyPlay && first.initDone) {
4193                             ZippyHoldings(white_holding, black_holding,
4194                                           new_piece);
4195                         }
4196 #endif /*ZIPPY*/
4197                         if (tinyLayout || smallLayout) {
4198                             char wh[16], bh[16];
4199                             PackHolding(wh, white_holding);
4200                             PackHolding(bh, black_holding);
4201                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4202                                     gameInfo.white, gameInfo.black);
4203                         } else {
4204                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4205                                     gameInfo.white, white_holding, _("vs."),
4206                                     gameInfo.black, black_holding);
4207                         }
4208                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4209                         DrawPosition(FALSE, boards[currentMove]);
4210                         DisplayTitle(str);
4211                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4212                         sscanf(parse, "game %d white [%s black [%s <- %s",
4213                                &gamenum, white_holding, black_holding,
4214                                new_piece);
4215                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4216                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4217                         /* [HGM] copy holdings to partner-board holdings area */
4218                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4219                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4220                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4221                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4222                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4223                       }
4224                     }
4225                     /* Suppress following prompt */
4226                     if (looking_at(buf, &i, "*% ")) {
4227                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4228                         savingComment = FALSE;
4229                         suppressKibitz = 0;
4230                     }
4231                     next_out = i;
4232                 }
4233                 continue;
4234             }
4235
4236             i++;                /* skip unparsed character and loop back */
4237         }
4238
4239         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4240 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4241 //          SendToPlayer(&buf[next_out], i - next_out);
4242             started != STARTED_HOLDINGS && leftover_start > next_out) {
4243             SendToPlayer(&buf[next_out], leftover_start - next_out);
4244             next_out = i;
4245         }
4246
4247         leftover_len = buf_len - leftover_start;
4248         /* if buffer ends with something we couldn't parse,
4249            reparse it after appending the next read */
4250
4251     } else if (count == 0) {
4252         RemoveInputSource(isr);
4253         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4254     } else {
4255         DisplayFatalError(_("Error reading from ICS"), error, 1);
4256     }
4257 }
4258
4259
4260 /* Board style 12 looks like this:
4261
4262    <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
4263
4264  * The "<12> " is stripped before it gets to this routine.  The two
4265  * trailing 0's (flip state and clock ticking) are later addition, and
4266  * some chess servers may not have them, or may have only the first.
4267  * Additional trailing fields may be added in the future.
4268  */
4269
4270 #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"
4271
4272 #define RELATION_OBSERVING_PLAYED    0
4273 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4274 #define RELATION_PLAYING_MYMOVE      1
4275 #define RELATION_PLAYING_NOTMYMOVE  -1
4276 #define RELATION_EXAMINING           2
4277 #define RELATION_ISOLATED_BOARD     -3
4278 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4279
4280 void
4281 ParseBoard12 (char *string)
4282 {
4283 #if ZIPPY
4284     int i, takeback;
4285     char *bookHit = NULL; // [HGM] book
4286 #endif
4287     GameMode newGameMode;
4288     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4289     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4290     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4291     char to_play, board_chars[200];
4292     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4293     char black[32], white[32];
4294     Board board;
4295     int prevMove = currentMove;
4296     int ticking = 2;
4297     ChessMove moveType;
4298     int fromX, fromY, toX, toY;
4299     char promoChar;
4300     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4301     Boolean weird = FALSE, reqFlag = FALSE;
4302
4303     fromX = fromY = toX = toY = -1;
4304
4305     newGame = FALSE;
4306
4307     if (appData.debugMode)
4308       fprintf(debugFP, "Parsing board: %s\n", string);
4309
4310     move_str[0] = NULLCHAR;
4311     elapsed_time[0] = NULLCHAR;
4312     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4313         int  i = 0, j;
4314         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4315             if(string[i] == ' ') { ranks++; files = 0; }
4316             else files++;
4317             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4318             i++;
4319         }
4320         for(j = 0; j <i; j++) board_chars[j] = string[j];
4321         board_chars[i] = '\0';
4322         string += i + 1;
4323     }
4324     n = sscanf(string, PATTERN, &to_play, &double_push,
4325                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4326                &gamenum, white, black, &relation, &basetime, &increment,
4327                &white_stren, &black_stren, &white_time, &black_time,
4328                &moveNum, str, elapsed_time, move_str, &ics_flip,
4329                &ticking);
4330
4331     if (n < 21) {
4332         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4333         DisplayError(str, 0);
4334         return;
4335     }
4336
4337     /* Convert the move number to internal form */
4338     moveNum = (moveNum - 1) * 2;
4339     if (to_play == 'B') moveNum++;
4340     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4341       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4342                         0, 1);
4343       return;
4344     }
4345
4346     switch (relation) {
4347       case RELATION_OBSERVING_PLAYED:
4348       case RELATION_OBSERVING_STATIC:
4349         if (gamenum == -1) {
4350             /* Old ICC buglet */
4351             relation = RELATION_OBSERVING_STATIC;
4352         }
4353         newGameMode = IcsObserving;
4354         break;
4355       case RELATION_PLAYING_MYMOVE:
4356       case RELATION_PLAYING_NOTMYMOVE:
4357         newGameMode =
4358           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4359             IcsPlayingWhite : IcsPlayingBlack;
4360         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4361         break;
4362       case RELATION_EXAMINING:
4363         newGameMode = IcsExamining;
4364         break;
4365       case RELATION_ISOLATED_BOARD:
4366       default:
4367         /* Just display this board.  If user was doing something else,
4368            we will forget about it until the next board comes. */
4369         newGameMode = IcsIdle;
4370         break;
4371       case RELATION_STARTING_POSITION:
4372         newGameMode = gameMode;
4373         break;
4374     }
4375
4376     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4377         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4378          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4379       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4380       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4381       static int lastBgGame = -1;
4382       char *toSqr;
4383       for (k = 0; k < ranks; k++) {
4384         for (j = 0; j < files; j++)
4385           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4386         if(gameInfo.holdingsWidth > 1) {
4387              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4388              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4389         }
4390       }
4391       CopyBoard(partnerBoard, board);
4392       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4393         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4394         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4395       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4396       if(toSqr = strchr(str, '-')) {
4397         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4398         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4399       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4400       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4401       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4402       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4403       if(twoBoards) {
4404           DisplayWhiteClock(white_time*fac, to_play == 'W');
4405           DisplayBlackClock(black_time*fac, to_play != 'W');
4406           activePartner = to_play;
4407           if(gamenum != lastBgGame) {
4408               char buf[MSG_SIZ];
4409               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4410               DisplayTitle(buf);
4411           }
4412           lastBgGame = gamenum;
4413           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4414                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4415       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4416                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4417       if(!twoBoards) DisplayMessage(partnerStatus, "");
4418         partnerBoardValid = TRUE;
4419       return;
4420     }
4421
4422     if(appData.dualBoard && appData.bgObserve) {
4423         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4424             SendToICS(ics_prefix), SendToICS("pobserve\n");
4425         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4426             char buf[MSG_SIZ];
4427             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4428             SendToICS(buf);
4429         }
4430     }
4431
4432     /* Modify behavior for initial board display on move listing
4433        of wild games.
4434        */
4435     switch (ics_getting_history) {
4436       case H_FALSE:
4437       case H_REQUESTED:
4438         break;
4439       case H_GOT_REQ_HEADER:
4440       case H_GOT_UNREQ_HEADER:
4441         /* This is the initial position of the current game */
4442         gamenum = ics_gamenum;
4443         moveNum = 0;            /* old ICS bug workaround */
4444         if (to_play == 'B') {
4445           startedFromSetupPosition = TRUE;
4446           blackPlaysFirst = TRUE;
4447           moveNum = 1;
4448           if (forwardMostMove == 0) forwardMostMove = 1;
4449           if (backwardMostMove == 0) backwardMostMove = 1;
4450           if (currentMove == 0) currentMove = 1;
4451         }
4452         newGameMode = gameMode;
4453         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4454         break;
4455       case H_GOT_UNWANTED_HEADER:
4456         /* This is an initial board that we don't want */
4457         return;
4458       case H_GETTING_MOVES:
4459         /* Should not happen */
4460         DisplayError(_("Error gathering move list: extra board"), 0);
4461         ics_getting_history = H_FALSE;
4462         return;
4463     }
4464
4465    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4466                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4467                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4468      /* [HGM] We seem to have switched variant unexpectedly
4469       * Try to guess new variant from board size
4470       */
4471           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4472           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4473           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4474           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4475           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4476           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4477           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4478           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4479           /* Get a move list just to see the header, which
4480              will tell us whether this is really bug or zh */
4481           if (ics_getting_history == H_FALSE) {
4482             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4483             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4484             SendToICS(str);
4485           }
4486     }
4487
4488     /* Take action if this is the first board of a new game, or of a
4489        different game than is currently being displayed.  */
4490     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4491         relation == RELATION_ISOLATED_BOARD) {
4492
4493         /* Forget the old game and get the history (if any) of the new one */
4494         if (gameMode != BeginningOfGame) {
4495           Reset(TRUE, TRUE);
4496         }
4497         newGame = TRUE;
4498         if (appData.autoRaiseBoard) BoardToTop();
4499         prevMove = -3;
4500         if (gamenum == -1) {
4501             newGameMode = IcsIdle;
4502         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4503                    appData.getMoveList && !reqFlag) {
4504             /* Need to get game history */
4505             ics_getting_history = H_REQUESTED;
4506             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4507             SendToICS(str);
4508         }
4509
4510         /* Initially flip the board to have black on the bottom if playing
4511            black or if the ICS flip flag is set, but let the user change
4512            it with the Flip View button. */
4513         flipView = appData.autoFlipView ?
4514           (newGameMode == IcsPlayingBlack) || ics_flip :
4515           appData.flipView;
4516
4517         /* Done with values from previous mode; copy in new ones */
4518         gameMode = newGameMode;
4519         ModeHighlight();
4520         ics_gamenum = gamenum;
4521         if (gamenum == gs_gamenum) {
4522             int klen = strlen(gs_kind);
4523             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4524             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4525             gameInfo.event = StrSave(str);
4526         } else {
4527             gameInfo.event = StrSave("ICS game");
4528         }
4529         gameInfo.site = StrSave(appData.icsHost);
4530         gameInfo.date = PGNDate();
4531         gameInfo.round = StrSave("-");
4532         gameInfo.white = StrSave(white);
4533         gameInfo.black = StrSave(black);
4534         timeControl = basetime * 60 * 1000;
4535         timeControl_2 = 0;
4536         timeIncrement = increment * 1000;
4537         movesPerSession = 0;
4538         gameInfo.timeControl = TimeControlTagValue();
4539         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4540   if (appData.debugMode) {
4541     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4542     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4543     setbuf(debugFP, NULL);
4544   }
4545
4546         gameInfo.outOfBook = NULL;
4547
4548         /* Do we have the ratings? */
4549         if (strcmp(player1Name, white) == 0 &&
4550             strcmp(player2Name, black) == 0) {
4551             if (appData.debugMode)
4552               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4553                       player1Rating, player2Rating);
4554             gameInfo.whiteRating = player1Rating;
4555             gameInfo.blackRating = player2Rating;
4556         } else if (strcmp(player2Name, white) == 0 &&
4557                    strcmp(player1Name, black) == 0) {
4558             if (appData.debugMode)
4559               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4560                       player2Rating, player1Rating);
4561             gameInfo.whiteRating = player2Rating;
4562             gameInfo.blackRating = player1Rating;
4563         }
4564         player1Name[0] = player2Name[0] = NULLCHAR;
4565
4566         /* Silence shouts if requested */
4567         if (appData.quietPlay &&
4568             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4569             SendToICS(ics_prefix);
4570             SendToICS("set shout 0\n");
4571         }
4572     }
4573
4574     /* Deal with midgame name changes */
4575     if (!newGame) {
4576         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4577             if (gameInfo.white) free(gameInfo.white);
4578             gameInfo.white = StrSave(white);
4579         }
4580         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4581             if (gameInfo.black) free(gameInfo.black);
4582             gameInfo.black = StrSave(black);
4583         }
4584     }
4585
4586     /* Throw away game result if anything actually changes in examine mode */
4587     if (gameMode == IcsExamining && !newGame) {
4588         gameInfo.result = GameUnfinished;
4589         if (gameInfo.resultDetails != NULL) {
4590             free(gameInfo.resultDetails);
4591             gameInfo.resultDetails = NULL;
4592         }
4593     }
4594
4595     /* In pausing && IcsExamining mode, we ignore boards coming
4596        in if they are in a different variation than we are. */
4597     if (pauseExamInvalid) return;
4598     if (pausing && gameMode == IcsExamining) {
4599         if (moveNum <= pauseExamForwardMostMove) {
4600             pauseExamInvalid = TRUE;
4601             forwardMostMove = pauseExamForwardMostMove;
4602             return;
4603         }
4604     }
4605
4606   if (appData.debugMode) {
4607     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4608   }
4609     /* Parse the board */
4610     for (k = 0; k < ranks; k++) {
4611       for (j = 0; j < files; j++)
4612         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4613       if(gameInfo.holdingsWidth > 1) {
4614            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4615            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4616       }
4617     }
4618     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4619       board[5][BOARD_RGHT+1] = WhiteAngel;
4620       board[6][BOARD_RGHT+1] = WhiteMarshall;
4621       board[1][0] = BlackMarshall;
4622       board[2][0] = BlackAngel;
4623       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4624     }
4625     CopyBoard(boards[moveNum], board);
4626     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4627     if (moveNum == 0) {
4628         startedFromSetupPosition =
4629           !CompareBoards(board, initialPosition);
4630         if(startedFromSetupPosition)
4631             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4632     }
4633
4634     /* [HGM] Set castling rights. Take the outermost Rooks,
4635        to make it also work for FRC opening positions. Note that board12
4636        is really defective for later FRC positions, as it has no way to
4637        indicate which Rook can castle if they are on the same side of King.
4638        For the initial position we grant rights to the outermost Rooks,
4639        and remember thos rights, and we then copy them on positions
4640        later in an FRC game. This means WB might not recognize castlings with
4641        Rooks that have moved back to their original position as illegal,
4642        but in ICS mode that is not its job anyway.
4643     */
4644     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4645     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4646
4647         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4648             if(board[0][i] == WhiteRook) j = i;
4649         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4650         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4651             if(board[0][i] == WhiteRook) j = i;
4652         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4653         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4654             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4655         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4656         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4657             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4658         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4659
4660         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4661         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4662         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4663             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4664         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4665             if(board[BOARD_HEIGHT-1][k] == bKing)
4666                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4667         if(gameInfo.variant == VariantTwoKings) {
4668             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4669             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4670             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4671         }
4672     } else { int r;
4673         r = boards[moveNum][CASTLING][0] = initialRights[0];
4674         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4675         r = boards[moveNum][CASTLING][1] = initialRights[1];
4676         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4677         r = boards[moveNum][CASTLING][3] = initialRights[3];
4678         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4679         r = boards[moveNum][CASTLING][4] = initialRights[4];
4680         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4681         /* wildcastle kludge: always assume King has rights */
4682         r = boards[moveNum][CASTLING][2] = initialRights[2];
4683         r = boards[moveNum][CASTLING][5] = initialRights[5];
4684     }
4685     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4686     boards[moveNum][EP_STATUS] = EP_NONE;
4687     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4688     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4689     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4690
4691
4692     if (ics_getting_history == H_GOT_REQ_HEADER ||
4693         ics_getting_history == H_GOT_UNREQ_HEADER) {
4694         /* This was an initial position from a move list, not
4695            the current position */
4696         return;
4697     }
4698
4699     /* Update currentMove and known move number limits */
4700     newMove = newGame || moveNum > forwardMostMove;
4701
4702     if (newGame) {
4703         forwardMostMove = backwardMostMove = currentMove = moveNum;
4704         if (gameMode == IcsExamining && moveNum == 0) {
4705           /* Workaround for ICS limitation: we are not told the wild
4706              type when starting to examine a game.  But if we ask for
4707              the move list, the move list header will tell us */
4708             ics_getting_history = H_REQUESTED;
4709             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4710             SendToICS(str);
4711         }
4712     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4713                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4714 #if ZIPPY
4715         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4716         /* [HGM] applied this also to an engine that is silently watching        */
4717         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4718             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4719             gameInfo.variant == currentlyInitializedVariant) {
4720           takeback = forwardMostMove - moveNum;
4721           for (i = 0; i < takeback; i++) {
4722             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4723             SendToProgram("undo\n", &first);
4724           }
4725         }
4726 #endif
4727
4728         forwardMostMove = moveNum;
4729         if (!pausing || currentMove > forwardMostMove)
4730           currentMove = forwardMostMove;
4731     } else {
4732         /* New part of history that is not contiguous with old part */
4733         if (pausing && gameMode == IcsExamining) {
4734             pauseExamInvalid = TRUE;
4735             forwardMostMove = pauseExamForwardMostMove;
4736             return;
4737         }
4738         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4739 #if ZIPPY
4740             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4741                 // [HGM] when we will receive the move list we now request, it will be
4742                 // fed to the engine from the first move on. So if the engine is not
4743                 // in the initial position now, bring it there.
4744                 InitChessProgram(&first, 0);
4745             }
4746 #endif
4747             ics_getting_history = H_REQUESTED;
4748             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4749             SendToICS(str);
4750         }
4751         forwardMostMove = backwardMostMove = currentMove = moveNum;
4752     }
4753
4754     /* Update the clocks */
4755     if (strchr(elapsed_time, '.')) {
4756       /* Time is in ms */
4757       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4758       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4759     } else {
4760       /* Time is in seconds */
4761       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4762       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4763     }
4764
4765
4766 #if ZIPPY
4767     if (appData.zippyPlay && newGame &&
4768         gameMode != IcsObserving && gameMode != IcsIdle &&
4769         gameMode != IcsExamining)
4770       ZippyFirstBoard(moveNum, basetime, increment);
4771 #endif
4772
4773     /* Put the move on the move list, first converting
4774        to canonical algebraic form. */
4775     if (moveNum > 0) {
4776   if (appData.debugMode) {
4777     int f = forwardMostMove;
4778     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4779             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4780             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4781     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4782     fprintf(debugFP, "moveNum = %d\n", moveNum);
4783     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4784     setbuf(debugFP, NULL);
4785   }
4786         if (moveNum <= backwardMostMove) {
4787             /* We don't know what the board looked like before
4788                this move.  Punt. */
4789           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4790             strcat(parseList[moveNum - 1], " ");
4791             strcat(parseList[moveNum - 1], elapsed_time);
4792             moveList[moveNum - 1][0] = NULLCHAR;
4793         } else if (strcmp(move_str, "none") == 0) {
4794             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4795             /* Again, we don't know what the board looked like;
4796                this is really the start of the game. */
4797             parseList[moveNum - 1][0] = NULLCHAR;
4798             moveList[moveNum - 1][0] = NULLCHAR;
4799             backwardMostMove = moveNum;
4800             startedFromSetupPosition = TRUE;
4801             fromX = fromY = toX = toY = -1;
4802         } else {
4803           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4804           //                 So we parse the long-algebraic move string in stead of the SAN move
4805           int valid; char buf[MSG_SIZ], *prom;
4806
4807           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4808                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4809           // str looks something like "Q/a1-a2"; kill the slash
4810           if(str[1] == '/')
4811             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4812           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4813           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4814                 strcat(buf, prom); // long move lacks promo specification!
4815           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4816                 if(appData.debugMode)
4817                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4818                 safeStrCpy(move_str, buf, MSG_SIZ);
4819           }
4820           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4821                                 &fromX, &fromY, &toX, &toY, &promoChar)
4822                || ParseOneMove(buf, moveNum - 1, &moveType,
4823                                 &fromX, &fromY, &toX, &toY, &promoChar);
4824           // end of long SAN patch
4825           if (valid) {
4826             (void) CoordsToAlgebraic(boards[moveNum - 1],
4827                                      PosFlags(moveNum - 1),
4828                                      fromY, fromX, toY, toX, promoChar,
4829                                      parseList[moveNum-1]);
4830             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4831               case MT_NONE:
4832               case MT_STALEMATE:
4833               default:
4834                 break;
4835               case MT_CHECK:
4836                 if(!IS_SHOGI(gameInfo.variant))
4837                     strcat(parseList[moveNum - 1], "+");
4838                 break;
4839               case MT_CHECKMATE:
4840               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4841                 strcat(parseList[moveNum - 1], "#");
4842                 break;
4843             }
4844             strcat(parseList[moveNum - 1], " ");
4845             strcat(parseList[moveNum - 1], elapsed_time);
4846             /* currentMoveString is set as a side-effect of ParseOneMove */
4847             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4848             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4849             strcat(moveList[moveNum - 1], "\n");
4850
4851             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4852                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4853               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4854                 ChessSquare old, new = boards[moveNum][k][j];
4855                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4856                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4857                   if(old == new) continue;
4858                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4859                   else if(new == WhiteWazir || new == BlackWazir) {
4860                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4861                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4862                       else boards[moveNum][k][j] = old; // preserve type of Gold
4863                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4864                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4865               }
4866           } else {
4867             /* Move from ICS was illegal!?  Punt. */
4868             if (appData.debugMode) {
4869               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4870               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4871             }
4872             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4873             strcat(parseList[moveNum - 1], " ");
4874             strcat(parseList[moveNum - 1], elapsed_time);
4875             moveList[moveNum - 1][0] = NULLCHAR;
4876             fromX = fromY = toX = toY = -1;
4877           }
4878         }
4879   if (appData.debugMode) {
4880     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4881     setbuf(debugFP, NULL);
4882   }
4883
4884 #if ZIPPY
4885         /* Send move to chess program (BEFORE animating it). */
4886         if (appData.zippyPlay && !newGame && newMove &&
4887            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4888
4889             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4890                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4891                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4892                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4893                             move_str);
4894                     DisplayError(str, 0);
4895                 } else {
4896                     if (first.sendTime) {
4897                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4898                     }
4899                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4900                     if (firstMove && !bookHit) {
4901                         firstMove = FALSE;
4902                         if (first.useColors) {
4903                           SendToProgram(gameMode == IcsPlayingWhite ?
4904                                         "white\ngo\n" :
4905                                         "black\ngo\n", &first);
4906                         } else {
4907                           SendToProgram("go\n", &first);
4908                         }
4909                         first.maybeThinking = TRUE;
4910                     }
4911                 }
4912             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4913               if (moveList[moveNum - 1][0] == NULLCHAR) {
4914                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4915                 DisplayError(str, 0);
4916               } else {
4917                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4918                 SendMoveToProgram(moveNum - 1, &first);
4919               }
4920             }
4921         }
4922 #endif
4923     }
4924
4925     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4926         /* If move comes from a remote source, animate it.  If it
4927            isn't remote, it will have already been animated. */
4928         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4929             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4930         }
4931         if (!pausing && appData.highlightLastMove) {
4932             SetHighlights(fromX, fromY, toX, toY);
4933         }
4934     }
4935
4936     /* Start the clocks */
4937     whiteFlag = blackFlag = FALSE;
4938     appData.clockMode = !(basetime == 0 && increment == 0);
4939     if (ticking == 0) {
4940       ics_clock_paused = TRUE;
4941       StopClocks();
4942     } else if (ticking == 1) {
4943       ics_clock_paused = FALSE;
4944     }
4945     if (gameMode == IcsIdle ||
4946         relation == RELATION_OBSERVING_STATIC ||
4947         relation == RELATION_EXAMINING ||
4948         ics_clock_paused)
4949       DisplayBothClocks();
4950     else
4951       StartClocks();
4952
4953     /* Display opponents and material strengths */
4954     if (gameInfo.variant != VariantBughouse &&
4955         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4956         if (tinyLayout || smallLayout) {
4957             if(gameInfo.variant == VariantNormal)
4958               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4959                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4960                     basetime, increment);
4961             else
4962               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4963                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4964                     basetime, increment, (int) gameInfo.variant);
4965         } else {
4966             if(gameInfo.variant == VariantNormal)
4967               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4968                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4969                     basetime, increment);
4970             else
4971               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4972                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4973                     basetime, increment, VariantName(gameInfo.variant));
4974         }
4975         DisplayTitle(str);
4976   if (appData.debugMode) {
4977     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4978   }
4979     }
4980
4981
4982     /* Display the board */
4983     if (!pausing && !appData.noGUI) {
4984
4985       if (appData.premove)
4986           if (!gotPremove ||
4987              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4988              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4989               ClearPremoveHighlights();
4990
4991       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4992         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4993       DrawPosition(j, boards[currentMove]);
4994
4995       DisplayMove(moveNum - 1);
4996       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4997             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4998               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4999         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5000       }
5001     }
5002
5003     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5004 #if ZIPPY
5005     if(bookHit) { // [HGM] book: simulate book reply
5006         static char bookMove[MSG_SIZ]; // a bit generous?
5007
5008         programStats.nodes = programStats.depth = programStats.time =
5009         programStats.score = programStats.got_only_move = 0;
5010         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5011
5012         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5013         strcat(bookMove, bookHit);
5014         HandleMachineMove(bookMove, &first);
5015     }
5016 #endif
5017 }
5018
5019 void
5020 GetMoveListEvent ()
5021 {
5022     char buf[MSG_SIZ];
5023     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5024         ics_getting_history = H_REQUESTED;
5025         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5026         SendToICS(buf);
5027     }
5028 }
5029
5030 void
5031 SendToBoth (char *msg)
5032 {   // to make it easy to keep two engines in step in dual analysis
5033     SendToProgram(msg, &first);
5034     if(second.analyzing) SendToProgram(msg, &second);
5035 }
5036
5037 void
5038 AnalysisPeriodicEvent (int force)
5039 {
5040     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5041          && !force) || !appData.periodicUpdates)
5042       return;
5043
5044     /* Send . command to Crafty to collect stats */
5045     SendToBoth(".\n");
5046
5047     /* Don't send another until we get a response (this makes
5048        us stop sending to old Crafty's which don't understand
5049        the "." command (sending illegal cmds resets node count & time,
5050        which looks bad)) */
5051     programStats.ok_to_send = 0;
5052 }
5053
5054 void
5055 ics_update_width (int new_width)
5056 {
5057         ics_printf("set width %d\n", new_width);
5058 }
5059
5060 void
5061 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5062 {
5063     char buf[MSG_SIZ];
5064
5065     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5066         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5067             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5068             SendToProgram(buf, cps);
5069             return;
5070         }
5071         // null move in variant where engine does not understand it (for analysis purposes)
5072         SendBoard(cps, moveNum + 1); // send position after move in stead.
5073         return;
5074     }
5075     if (cps->useUsermove) {
5076       SendToProgram("usermove ", cps);
5077     }
5078     if (cps->useSAN) {
5079       char *space;
5080       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5081         int len = space - parseList[moveNum];
5082         memcpy(buf, parseList[moveNum], len);
5083         buf[len++] = '\n';
5084         buf[len] = NULLCHAR;
5085       } else {
5086         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5087       }
5088       SendToProgram(buf, cps);
5089     } else {
5090       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5091         AlphaRank(moveList[moveNum], 4);
5092         SendToProgram(moveList[moveNum], cps);
5093         AlphaRank(moveList[moveNum], 4); // and back
5094       } else
5095       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5096        * the engine. It would be nice to have a better way to identify castle
5097        * moves here. */
5098       if(appData.fischerCastling && cps->useOOCastle) {
5099         int fromX = moveList[moveNum][0] - AAA;
5100         int fromY = moveList[moveNum][1] - ONE;
5101         int toX = moveList[moveNum][2] - AAA;
5102         int toY = moveList[moveNum][3] - ONE;
5103         if((boards[moveNum][fromY][fromX] == WhiteKing
5104             && boards[moveNum][toY][toX] == WhiteRook)
5105            || (boards[moveNum][fromY][fromX] == BlackKing
5106                && boards[moveNum][toY][toX] == BlackRook)) {
5107           if(toX > fromX) SendToProgram("O-O\n", cps);
5108           else SendToProgram("O-O-O\n", cps);
5109         }
5110         else SendToProgram(moveList[moveNum], cps);
5111       } else
5112       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5113           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5114                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5115                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5116                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5117           SendToProgram(buf, cps);
5118       } else
5119       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5120         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5121           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5122           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5123                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5124         } else
5125           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5126                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5127         SendToProgram(buf, cps);
5128       }
5129       else SendToProgram(moveList[moveNum], cps);
5130       /* End of additions by Tord */
5131     }
5132
5133     /* [HGM] setting up the opening has brought engine in force mode! */
5134     /*       Send 'go' if we are in a mode where machine should play. */
5135     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5136         (gameMode == TwoMachinesPlay   ||
5137 #if ZIPPY
5138          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5139 #endif
5140          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5141         SendToProgram("go\n", cps);
5142   if (appData.debugMode) {
5143     fprintf(debugFP, "(extra)\n");
5144   }
5145     }
5146     setboardSpoiledMachineBlack = 0;
5147 }
5148
5149 void
5150 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5151 {
5152     char user_move[MSG_SIZ];
5153     char suffix[4];
5154
5155     if(gameInfo.variant == VariantSChess && promoChar) {
5156         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5157         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5158     } else suffix[0] = NULLCHAR;
5159
5160     switch (moveType) {
5161       default:
5162         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5163                 (int)moveType, fromX, fromY, toX, toY);
5164         DisplayError(user_move + strlen("say "), 0);
5165         break;
5166       case WhiteKingSideCastle:
5167       case BlackKingSideCastle:
5168       case WhiteQueenSideCastleWild:
5169       case BlackQueenSideCastleWild:
5170       /* PUSH Fabien */
5171       case WhiteHSideCastleFR:
5172       case BlackHSideCastleFR:
5173       /* POP Fabien */
5174         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5175         break;
5176       case WhiteQueenSideCastle:
5177       case BlackQueenSideCastle:
5178       case WhiteKingSideCastleWild:
5179       case BlackKingSideCastleWild:
5180       /* PUSH Fabien */
5181       case WhiteASideCastleFR:
5182       case BlackASideCastleFR:
5183       /* POP Fabien */
5184         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5185         break;
5186       case WhiteNonPromotion:
5187       case BlackNonPromotion:
5188         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5189         break;
5190       case WhitePromotion:
5191       case BlackPromotion:
5192         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5193            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5194           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5195                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5196                 PieceToChar(WhiteFerz));
5197         else if(gameInfo.variant == VariantGreat)
5198           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5199                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5200                 PieceToChar(WhiteMan));
5201         else
5202           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5203                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5204                 promoChar);
5205         break;
5206       case WhiteDrop:
5207       case BlackDrop:
5208       drop:
5209         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5210                  ToUpper(PieceToChar((ChessSquare) fromX)),
5211                  AAA + toX, ONE + toY);
5212         break;
5213       case IllegalMove:  /* could be a variant we don't quite understand */
5214         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5215       case NormalMove:
5216       case WhiteCapturesEnPassant:
5217       case BlackCapturesEnPassant:
5218         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5219                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5220         break;
5221     }
5222     SendToICS(user_move);
5223     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5224         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5225 }
5226
5227 void
5228 UploadGameEvent ()
5229 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5230     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5231     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5232     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5233       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5234       return;
5235     }
5236     if(gameMode != IcsExamining) { // is this ever not the case?
5237         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5238
5239         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5240           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5241         } else { // on FICS we must first go to general examine mode
5242           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5243         }
5244         if(gameInfo.variant != VariantNormal) {
5245             // try figure out wild number, as xboard names are not always valid on ICS
5246             for(i=1; i<=36; i++) {
5247               snprintf(buf, MSG_SIZ, "wild/%d", i);
5248                 if(StringToVariant(buf) == gameInfo.variant) break;
5249             }
5250             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5251             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5252             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5253         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5254         SendToICS(ics_prefix);
5255         SendToICS(buf);
5256         if(startedFromSetupPosition || backwardMostMove != 0) {
5257           fen = PositionToFEN(backwardMostMove, NULL, 1);
5258           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5259             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5260             SendToICS(buf);
5261           } else { // FICS: everything has to set by separate bsetup commands
5262             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5263             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5264             SendToICS(buf);
5265             if(!WhiteOnMove(backwardMostMove)) {
5266                 SendToICS("bsetup tomove black\n");
5267             }
5268             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5269             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5270             SendToICS(buf);
5271             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5272             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5273             SendToICS(buf);
5274             i = boards[backwardMostMove][EP_STATUS];
5275             if(i >= 0) { // set e.p.
5276               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5277                 SendToICS(buf);
5278             }
5279             bsetup++;
5280           }
5281         }
5282       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5283             SendToICS("bsetup done\n"); // switch to normal examining.
5284     }
5285     for(i = backwardMostMove; i<last; i++) {
5286         char buf[20];
5287         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5288         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5289             int len = strlen(moveList[i]);
5290             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5291             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5292         }
5293         SendToICS(buf);
5294     }
5295     SendToICS(ics_prefix);
5296     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5297 }
5298
5299 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5300
5301 void
5302 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5303 {
5304     if (rf == DROP_RANK) {
5305       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5306       sprintf(move, "%c@%c%c\n",
5307                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5308     } else {
5309         if (promoChar == 'x' || promoChar == NULLCHAR) {
5310           sprintf(move, "%c%c%c%c\n",
5311                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5312           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5313         } else {
5314             sprintf(move, "%c%c%c%c%c\n",
5315                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5316         }
5317     }
5318 }
5319
5320 void
5321 ProcessICSInitScript (FILE *f)
5322 {
5323     char buf[MSG_SIZ];
5324
5325     while (fgets(buf, MSG_SIZ, f)) {
5326         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5327     }
5328
5329     fclose(f);
5330 }
5331
5332
5333 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5334 int dragging;
5335 static ClickType lastClickType;
5336
5337 int
5338 Partner (ChessSquare *p)
5339 { // change piece into promotion partner if one shogi-promotes to the other
5340   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5341   ChessSquare partner;
5342   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5343   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5344   *p = partner;
5345   return 1;
5346 }
5347
5348 void
5349 Sweep (int step)
5350 {
5351     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5352     static int toggleFlag;
5353     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5354     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5355     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5356     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5357     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5358     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5359     do {
5360         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5361         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5362         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5363         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5364         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5365         if(!step) step = -1;
5366     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5367             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5368             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5369             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5370     if(toX >= 0) {
5371         int victim = boards[currentMove][toY][toX];
5372         boards[currentMove][toY][toX] = promoSweep;
5373         DrawPosition(FALSE, boards[currentMove]);
5374         boards[currentMove][toY][toX] = victim;
5375     } else
5376     ChangeDragPiece(promoSweep);
5377 }
5378
5379 int
5380 PromoScroll (int x, int y)
5381 {
5382   int step = 0;
5383
5384   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5385   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5386   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5387   if(!step) return FALSE;
5388   lastX = x; lastY = y;
5389   if((promoSweep < BlackPawn) == flipView) step = -step;
5390   if(step > 0) selectFlag = 1;
5391   if(!selectFlag) Sweep(step);
5392   return FALSE;
5393 }
5394
5395 void
5396 NextPiece (int step)
5397 {
5398     ChessSquare piece = boards[currentMove][toY][toX];
5399     do {
5400         pieceSweep -= step;
5401         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5402         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5403         if(!step) step = -1;
5404     } while(PieceToChar(pieceSweep) == '.');
5405     boards[currentMove][toY][toX] = pieceSweep;
5406     DrawPosition(FALSE, boards[currentMove]);
5407     boards[currentMove][toY][toX] = piece;
5408 }
5409 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5410 void
5411 AlphaRank (char *move, int n)
5412 {
5413 //    char *p = move, c; int x, y;
5414
5415     if (appData.debugMode) {
5416         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5417     }
5418
5419     if(move[1]=='*' &&
5420        move[2]>='0' && move[2]<='9' &&
5421        move[3]>='a' && move[3]<='x'    ) {
5422         move[1] = '@';
5423         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5424         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5425     } else
5426     if(move[0]>='0' && move[0]<='9' &&
5427        move[1]>='a' && move[1]<='x' &&
5428        move[2]>='0' && move[2]<='9' &&
5429        move[3]>='a' && move[3]<='x'    ) {
5430         /* input move, Shogi -> normal */
5431         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5432         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5433         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5434         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5435     } else
5436     if(move[1]=='@' &&
5437        move[3]>='0' && move[3]<='9' &&
5438        move[2]>='a' && move[2]<='x'    ) {
5439         move[1] = '*';
5440         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5441         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5442     } else
5443     if(
5444        move[0]>='a' && move[0]<='x' &&
5445        move[3]>='0' && move[3]<='9' &&
5446        move[2]>='a' && move[2]<='x'    ) {
5447          /* output move, normal -> Shogi */
5448         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5449         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5450         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5451         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5452         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5453     }
5454     if (appData.debugMode) {
5455         fprintf(debugFP, "   out = '%s'\n", move);
5456     }
5457 }
5458
5459 char yy_textstr[8000];
5460
5461 /* Parser for moves from gnuchess, ICS, or user typein box */
5462 Boolean
5463 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5464 {
5465     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5466
5467     switch (*moveType) {
5468       case WhitePromotion:
5469       case BlackPromotion:
5470       case WhiteNonPromotion:
5471       case BlackNonPromotion:
5472       case NormalMove:
5473       case FirstLeg:
5474       case WhiteCapturesEnPassant:
5475       case BlackCapturesEnPassant:
5476       case WhiteKingSideCastle:
5477       case WhiteQueenSideCastle:
5478       case BlackKingSideCastle:
5479       case BlackQueenSideCastle:
5480       case WhiteKingSideCastleWild:
5481       case WhiteQueenSideCastleWild:
5482       case BlackKingSideCastleWild:
5483       case BlackQueenSideCastleWild:
5484       /* Code added by Tord: */
5485       case WhiteHSideCastleFR:
5486       case WhiteASideCastleFR:
5487       case BlackHSideCastleFR:
5488       case BlackASideCastleFR:
5489       /* End of code added by Tord */
5490       case IllegalMove:         /* bug or odd chess variant */
5491         *fromX = currentMoveString[0] - AAA;
5492         *fromY = currentMoveString[1] - ONE;
5493         *toX = currentMoveString[2] - AAA;
5494         *toY = currentMoveString[3] - ONE;
5495         *promoChar = currentMoveString[4];
5496         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5497             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5498     if (appData.debugMode) {
5499         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5500     }
5501             *fromX = *fromY = *toX = *toY = 0;
5502             return FALSE;
5503         }
5504         if (appData.testLegality) {
5505           return (*moveType != IllegalMove);
5506         } else {
5507           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5508                          // [HGM] lion: if this is a double move we are less critical
5509                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5510         }
5511
5512       case WhiteDrop:
5513       case BlackDrop:
5514         *fromX = *moveType == WhiteDrop ?
5515           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5516           (int) CharToPiece(ToLower(currentMoveString[0]));
5517         *fromY = DROP_RANK;
5518         *toX = currentMoveString[2] - AAA;
5519         *toY = currentMoveString[3] - ONE;
5520         *promoChar = NULLCHAR;
5521         return TRUE;
5522
5523       case AmbiguousMove:
5524       case ImpossibleMove:
5525       case EndOfFile:
5526       case ElapsedTime:
5527       case Comment:
5528       case PGNTag:
5529       case NAG:
5530       case WhiteWins:
5531       case BlackWins:
5532       case GameIsDrawn:
5533       default:
5534     if (appData.debugMode) {
5535         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5536     }
5537         /* bug? */
5538         *fromX = *fromY = *toX = *toY = 0;
5539         *promoChar = NULLCHAR;
5540         return FALSE;
5541     }
5542 }
5543
5544 Boolean pushed = FALSE;
5545 char *lastParseAttempt;
5546
5547 void
5548 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5549 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5550   int fromX, fromY, toX, toY; char promoChar;
5551   ChessMove moveType;
5552   Boolean valid;
5553   int nr = 0;
5554
5555   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5556   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5557     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5558     pushed = TRUE;
5559   }
5560   endPV = forwardMostMove;
5561   do {
5562     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5563     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5564     lastParseAttempt = pv;
5565     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5566     if(!valid && nr == 0 &&
5567        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5568         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5569         // Hande case where played move is different from leading PV move
5570         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5571         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5572         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5573         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5574           endPV += 2; // if position different, keep this
5575           moveList[endPV-1][0] = fromX + AAA;
5576           moveList[endPV-1][1] = fromY + ONE;
5577           moveList[endPV-1][2] = toX + AAA;
5578           moveList[endPV-1][3] = toY + ONE;
5579           parseList[endPV-1][0] = NULLCHAR;
5580           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5581         }
5582       }
5583     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5584     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5585     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5586     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5587         valid++; // allow comments in PV
5588         continue;
5589     }
5590     nr++;
5591     if(endPV+1 > framePtr) break; // no space, truncate
5592     if(!valid) break;
5593     endPV++;
5594     CopyBoard(boards[endPV], boards[endPV-1]);
5595     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5596     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5597     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5598     CoordsToAlgebraic(boards[endPV - 1],
5599                              PosFlags(endPV - 1),
5600                              fromY, fromX, toY, toX, promoChar,
5601                              parseList[endPV - 1]);
5602   } while(valid);
5603   if(atEnd == 2) return; // used hidden, for PV conversion
5604   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5605   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5606   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5607                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5608   DrawPosition(TRUE, boards[currentMove]);
5609 }
5610
5611 int
5612 MultiPV (ChessProgramState *cps)
5613 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5614         int i;
5615         for(i=0; i<cps->nrOptions; i++)
5616             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5617                 return i;
5618         return -1;
5619 }
5620
5621 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5622
5623 Boolean
5624 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5625 {
5626         int startPV, multi, lineStart, origIndex = index;
5627         char *p, buf2[MSG_SIZ];
5628         ChessProgramState *cps = (pane ? &second : &first);
5629
5630         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5631         lastX = x; lastY = y;
5632         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5633         lineStart = startPV = index;
5634         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5635         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5636         index = startPV;
5637         do{ while(buf[index] && buf[index] != '\n') index++;
5638         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5639         buf[index] = 0;
5640         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5641                 int n = cps->option[multi].value;
5642                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5643                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5644                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5645                 cps->option[multi].value = n;
5646                 *start = *end = 0;
5647                 return FALSE;
5648         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5649                 ExcludeClick(origIndex - lineStart);
5650                 return FALSE;
5651         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5652                 Collapse(origIndex - lineStart);
5653                 return FALSE;
5654         }
5655         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5656         *start = startPV; *end = index-1;
5657         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5658         return TRUE;
5659 }
5660
5661 char *
5662 PvToSAN (char *pv)
5663 {
5664         static char buf[10*MSG_SIZ];
5665         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5666         *buf = NULLCHAR;
5667         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5668         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5669         for(i = forwardMostMove; i<endPV; i++){
5670             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5671             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5672             k += strlen(buf+k);
5673         }
5674         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5675         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5676         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5677         endPV = savedEnd;
5678         return buf;
5679 }
5680
5681 Boolean
5682 LoadPV (int x, int y)
5683 { // called on right mouse click to load PV
5684   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5685   lastX = x; lastY = y;
5686   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5687   extendGame = FALSE;
5688   return TRUE;
5689 }
5690
5691 void
5692 UnLoadPV ()
5693 {
5694   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5695   if(endPV < 0) return;
5696   if(appData.autoCopyPV) CopyFENToClipboard();
5697   endPV = -1;
5698   if(extendGame && currentMove > forwardMostMove) {
5699         Boolean saveAnimate = appData.animate;
5700         if(pushed) {
5701             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5702                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5703             } else storedGames--; // abandon shelved tail of original game
5704         }
5705         pushed = FALSE;
5706         forwardMostMove = currentMove;
5707         currentMove = oldFMM;
5708         appData.animate = FALSE;
5709         ToNrEvent(forwardMostMove);
5710         appData.animate = saveAnimate;
5711   }
5712   currentMove = forwardMostMove;
5713   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5714   ClearPremoveHighlights();
5715   DrawPosition(TRUE, boards[currentMove]);
5716 }
5717
5718 void
5719 MovePV (int x, int y, int h)
5720 { // step through PV based on mouse coordinates (called on mouse move)
5721   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5722
5723   // we must somehow check if right button is still down (might be released off board!)
5724   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5725   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5726   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5727   if(!step) return;
5728   lastX = x; lastY = y;
5729
5730   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5731   if(endPV < 0) return;
5732   if(y < margin) step = 1; else
5733   if(y > h - margin) step = -1;
5734   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5735   currentMove += step;
5736   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5737   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5738                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5739   DrawPosition(FALSE, boards[currentMove]);
5740 }
5741
5742
5743 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5744 // All positions will have equal probability, but the current method will not provide a unique
5745 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5746 #define DARK 1
5747 #define LITE 2
5748 #define ANY 3
5749
5750 int squaresLeft[4];
5751 int piecesLeft[(int)BlackPawn];
5752 int seed, nrOfShuffles;
5753
5754 void
5755 GetPositionNumber ()
5756 {       // sets global variable seed
5757         int i;
5758
5759         seed = appData.defaultFrcPosition;
5760         if(seed < 0) { // randomize based on time for negative FRC position numbers
5761                 for(i=0; i<50; i++) seed += random();
5762                 seed = random() ^ random() >> 8 ^ random() << 8;
5763                 if(seed<0) seed = -seed;
5764         }
5765 }
5766
5767 int
5768 put (Board board, int pieceType, int rank, int n, int shade)
5769 // put the piece on the (n-1)-th empty squares of the given shade
5770 {
5771         int i;
5772
5773         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5774                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5775                         board[rank][i] = (ChessSquare) pieceType;
5776                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5777                         squaresLeft[ANY]--;
5778                         piecesLeft[pieceType]--;
5779                         return i;
5780                 }
5781         }
5782         return -1;
5783 }
5784
5785
5786 void
5787 AddOnePiece (Board board, int pieceType, int rank, int shade)
5788 // calculate where the next piece goes, (any empty square), and put it there
5789 {
5790         int i;
5791
5792         i = seed % squaresLeft[shade];
5793         nrOfShuffles *= squaresLeft[shade];
5794         seed /= squaresLeft[shade];
5795         put(board, pieceType, rank, i, shade);
5796 }
5797
5798 void
5799 AddTwoPieces (Board board, int pieceType, int rank)
5800 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5801 {
5802         int i, n=squaresLeft[ANY], j=n-1, k;
5803
5804         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5805         i = seed % k;  // pick one
5806         nrOfShuffles *= k;
5807         seed /= k;
5808         while(i >= j) i -= j--;
5809         j = n - 1 - j; i += j;
5810         put(board, pieceType, rank, j, ANY);
5811         put(board, pieceType, rank, i, ANY);
5812 }
5813
5814 void
5815 SetUpShuffle (Board board, int number)
5816 {
5817         int i, p, first=1;
5818
5819         GetPositionNumber(); nrOfShuffles = 1;
5820
5821         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5822         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5823         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5824
5825         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5826
5827         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5828             p = (int) board[0][i];
5829             if(p < (int) BlackPawn) piecesLeft[p] ++;
5830             board[0][i] = EmptySquare;
5831         }
5832
5833         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5834             // shuffles restricted to allow normal castling put KRR first
5835             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5836                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5837             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5838                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5839             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5840                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5841             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5842                 put(board, WhiteRook, 0, 0, ANY);
5843             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5844         }
5845
5846         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5847             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5848             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5849                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5850                 while(piecesLeft[p] >= 2) {
5851                     AddOnePiece(board, p, 0, LITE);
5852                     AddOnePiece(board, p, 0, DARK);
5853                 }
5854                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5855             }
5856
5857         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5858             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5859             // but we leave King and Rooks for last, to possibly obey FRC restriction
5860             if(p == (int)WhiteRook) continue;
5861             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5862             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5863         }
5864
5865         // now everything is placed, except perhaps King (Unicorn) and Rooks
5866
5867         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5868             // Last King gets castling rights
5869             while(piecesLeft[(int)WhiteUnicorn]) {
5870                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5871                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5872             }
5873
5874             while(piecesLeft[(int)WhiteKing]) {
5875                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5876                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5877             }
5878
5879
5880         } else {
5881             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5882             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5883         }
5884
5885         // Only Rooks can be left; simply place them all
5886         while(piecesLeft[(int)WhiteRook]) {
5887                 i = put(board, WhiteRook, 0, 0, ANY);
5888                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5889                         if(first) {
5890                                 first=0;
5891                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5892                         }
5893                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5894                 }
5895         }
5896         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5897             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5898         }
5899
5900         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5901 }
5902
5903 int
5904 SetCharTable (char *table, const char * map)
5905 /* [HGM] moved here from winboard.c because of its general usefulness */
5906 /*       Basically a safe strcpy that uses the last character as King */
5907 {
5908     int result = FALSE; int NrPieces;
5909
5910     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5911                     && NrPieces >= 12 && !(NrPieces&1)) {
5912         int i; /* [HGM] Accept even length from 12 to 34 */
5913
5914         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5915         for( i=0; i<NrPieces/2-1; i++ ) {
5916             table[i] = map[i];
5917             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5918         }
5919         table[(int) WhiteKing]  = map[NrPieces/2-1];
5920         table[(int) BlackKing]  = map[NrPieces-1];
5921
5922         result = TRUE;
5923     }
5924
5925     return result;
5926 }
5927
5928 void
5929 Prelude (Board board)
5930 {       // [HGM] superchess: random selection of exo-pieces
5931         int i, j, k; ChessSquare p;
5932         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5933
5934         GetPositionNumber(); // use FRC position number
5935
5936         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5937             SetCharTable(pieceToChar, appData.pieceToCharTable);
5938             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5939                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5940         }
5941
5942         j = seed%4;                 seed /= 4;
5943         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5944         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5945         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5946         j = seed%3 + (seed%3 >= j); seed /= 3;
5947         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5948         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5949         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5950         j = seed%3;                 seed /= 3;
5951         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5952         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5953         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5954         j = seed%2 + (seed%2 >= j); seed /= 2;
5955         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5956         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5957         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5958         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5959         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5960         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5961         put(board, exoPieces[0],    0, 0, ANY);
5962         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5963 }
5964
5965 void
5966 InitPosition (int redraw)
5967 {
5968     ChessSquare (* pieces)[BOARD_FILES];
5969     int i, j, pawnRow=1, pieceRows=1, overrule,
5970     oldx = gameInfo.boardWidth,
5971     oldy = gameInfo.boardHeight,
5972     oldh = gameInfo.holdingsWidth;
5973     static int oldv;
5974
5975     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5976
5977     /* [AS] Initialize pv info list [HGM] and game status */
5978     {
5979         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5980             pvInfoList[i].depth = 0;
5981             boards[i][EP_STATUS] = EP_NONE;
5982             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5983         }
5984
5985         initialRulePlies = 0; /* 50-move counter start */
5986
5987         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5988         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5989     }
5990
5991
5992     /* [HGM] logic here is completely changed. In stead of full positions */
5993     /* the initialized data only consist of the two backranks. The switch */
5994     /* selects which one we will use, which is than copied to the Board   */
5995     /* initialPosition, which for the rest is initialized by Pawns and    */
5996     /* empty squares. This initial position is then copied to boards[0],  */
5997     /* possibly after shuffling, so that it remains available.            */
5998
5999     gameInfo.holdingsWidth = 0; /* default board sizes */
6000     gameInfo.boardWidth    = 8;
6001     gameInfo.boardHeight   = 8;
6002     gameInfo.holdingsSize  = 0;
6003     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6004     for(i=0; i<BOARD_FILES-2; i++)
6005       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6006     initialPosition[EP_STATUS] = EP_NONE;
6007     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6008     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6009          SetCharTable(pieceNickName, appData.pieceNickNames);
6010     else SetCharTable(pieceNickName, "............");
6011     pieces = FIDEArray;
6012
6013     switch (gameInfo.variant) {
6014     case VariantFischeRandom:
6015       shuffleOpenings = TRUE;
6016       appData.fischerCastling = TRUE;
6017     default:
6018       break;
6019     case VariantShatranj:
6020       pieces = ShatranjArray;
6021       nrCastlingRights = 0;
6022       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6023       break;
6024     case VariantMakruk:
6025       pieces = makrukArray;
6026       nrCastlingRights = 0;
6027       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6028       break;
6029     case VariantASEAN:
6030       pieces = aseanArray;
6031       nrCastlingRights = 0;
6032       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6033       break;
6034     case VariantTwoKings:
6035       pieces = twoKingsArray;
6036       break;
6037     case VariantGrand:
6038       pieces = GrandArray;
6039       nrCastlingRights = 0;
6040       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6041       gameInfo.boardWidth = 10;
6042       gameInfo.boardHeight = 10;
6043       gameInfo.holdingsSize = 7;
6044       break;
6045     case VariantCapaRandom:
6046       shuffleOpenings = TRUE;
6047       appData.fischerCastling = TRUE;
6048     case VariantCapablanca:
6049       pieces = CapablancaArray;
6050       gameInfo.boardWidth = 10;
6051       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6052       break;
6053     case VariantGothic:
6054       pieces = GothicArray;
6055       gameInfo.boardWidth = 10;
6056       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6057       break;
6058     case VariantSChess:
6059       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6060       gameInfo.holdingsSize = 7;
6061       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6062       break;
6063     case VariantJanus:
6064       pieces = JanusArray;
6065       gameInfo.boardWidth = 10;
6066       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6067       nrCastlingRights = 6;
6068         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6069         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6070         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6071         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6072         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6073         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6074       break;
6075     case VariantFalcon:
6076       pieces = FalconArray;
6077       gameInfo.boardWidth = 10;
6078       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6079       break;
6080     case VariantXiangqi:
6081       pieces = XiangqiArray;
6082       gameInfo.boardWidth  = 9;
6083       gameInfo.boardHeight = 10;
6084       nrCastlingRights = 0;
6085       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6086       break;
6087     case VariantShogi:
6088       pieces = ShogiArray;
6089       gameInfo.boardWidth  = 9;
6090       gameInfo.boardHeight = 9;
6091       gameInfo.holdingsSize = 7;
6092       nrCastlingRights = 0;
6093       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6094       break;
6095     case VariantChu:
6096       pieces = ChuArray; pieceRows = 3;
6097       gameInfo.boardWidth  = 12;
6098       gameInfo.boardHeight = 12;
6099       nrCastlingRights = 0;
6100       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6101                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6102       break;
6103     case VariantCourier:
6104       pieces = CourierArray;
6105       gameInfo.boardWidth  = 12;
6106       nrCastlingRights = 0;
6107       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6108       break;
6109     case VariantKnightmate:
6110       pieces = KnightmateArray;
6111       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6112       break;
6113     case VariantSpartan:
6114       pieces = SpartanArray;
6115       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6116       break;
6117     case VariantLion:
6118       pieces = lionArray;
6119       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6120       break;
6121     case VariantChuChess:
6122       pieces = ChuChessArray;
6123       gameInfo.boardWidth = 10;
6124       gameInfo.boardHeight = 10;
6125       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6126       break;
6127     case VariantFairy:
6128       pieces = fairyArray;
6129       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6130       break;
6131     case VariantGreat:
6132       pieces = GreatArray;
6133       gameInfo.boardWidth = 10;
6134       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6135       gameInfo.holdingsSize = 8;
6136       break;
6137     case VariantSuper:
6138       pieces = FIDEArray;
6139       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6140       gameInfo.holdingsSize = 8;
6141       startedFromSetupPosition = TRUE;
6142       break;
6143     case VariantCrazyhouse:
6144     case VariantBughouse:
6145       pieces = FIDEArray;
6146       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6147       gameInfo.holdingsSize = 5;
6148       break;
6149     case VariantWildCastle:
6150       pieces = FIDEArray;
6151       /* !!?shuffle with kings guaranteed to be on d or e file */
6152       shuffleOpenings = 1;
6153       break;
6154     case VariantNoCastle:
6155       pieces = FIDEArray;
6156       nrCastlingRights = 0;
6157       /* !!?unconstrained back-rank shuffle */
6158       shuffleOpenings = 1;
6159       break;
6160     }
6161
6162     overrule = 0;
6163     if(appData.NrFiles >= 0) {
6164         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6165         gameInfo.boardWidth = appData.NrFiles;
6166     }
6167     if(appData.NrRanks >= 0) {
6168         gameInfo.boardHeight = appData.NrRanks;
6169     }
6170     if(appData.holdingsSize >= 0) {
6171         i = appData.holdingsSize;
6172         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6173         gameInfo.holdingsSize = i;
6174     }
6175     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6176     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6177         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6178
6179     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6180     if(pawnRow < 1) pawnRow = 1;
6181     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6182        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6183     if(gameInfo.variant == VariantChu) pawnRow = 3;
6184
6185     /* User pieceToChar list overrules defaults */
6186     if(appData.pieceToCharTable != NULL)
6187         SetCharTable(pieceToChar, appData.pieceToCharTable);
6188
6189     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6190
6191         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6192             s = (ChessSquare) 0; /* account holding counts in guard band */
6193         for( i=0; i<BOARD_HEIGHT; i++ )
6194             initialPosition[i][j] = s;
6195
6196         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6197         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6198         initialPosition[pawnRow][j] = WhitePawn;
6199         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6200         if(gameInfo.variant == VariantXiangqi) {
6201             if(j&1) {
6202                 initialPosition[pawnRow][j] =
6203                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6204                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6205                    initialPosition[2][j] = WhiteCannon;
6206                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6207                 }
6208             }
6209         }
6210         if(gameInfo.variant == VariantChu) {
6211              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6212                initialPosition[pawnRow+1][j] = WhiteCobra,
6213                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6214              for(i=1; i<pieceRows; i++) {
6215                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6216                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6217              }
6218         }
6219         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6220             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6221                initialPosition[0][j] = WhiteRook;
6222                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6223             }
6224         }
6225         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6226     }
6227     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6228     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6229
6230             j=BOARD_LEFT+1;
6231             initialPosition[1][j] = WhiteBishop;
6232             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6233             j=BOARD_RGHT-2;
6234             initialPosition[1][j] = WhiteRook;
6235             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6236     }
6237
6238     if( nrCastlingRights == -1) {
6239         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6240         /*       This sets default castling rights from none to normal corners   */
6241         /* Variants with other castling rights must set them themselves above    */
6242         nrCastlingRights = 6;
6243
6244         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6245         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6246         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6247         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6248         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6249         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6250      }
6251
6252      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6253      if(gameInfo.variant == VariantGreat) { // promotion commoners
6254         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6255         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6256         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6257         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6258      }
6259      if( gameInfo.variant == VariantSChess ) {
6260       initialPosition[1][0] = BlackMarshall;
6261       initialPosition[2][0] = BlackAngel;
6262       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6263       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6264       initialPosition[1][1] = initialPosition[2][1] =
6265       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6266      }
6267   if (appData.debugMode) {
6268     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6269   }
6270     if(shuffleOpenings) {
6271         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6272         startedFromSetupPosition = TRUE;
6273     }
6274     if(startedFromPositionFile) {
6275       /* [HGM] loadPos: use PositionFile for every new game */
6276       CopyBoard(initialPosition, filePosition);
6277       for(i=0; i<nrCastlingRights; i++)
6278           initialRights[i] = filePosition[CASTLING][i];
6279       startedFromSetupPosition = TRUE;
6280     }
6281
6282     CopyBoard(boards[0], initialPosition);
6283
6284     if(oldx != gameInfo.boardWidth ||
6285        oldy != gameInfo.boardHeight ||
6286        oldv != gameInfo.variant ||
6287        oldh != gameInfo.holdingsWidth
6288                                          )
6289             InitDrawingSizes(-2 ,0);
6290
6291     oldv = gameInfo.variant;
6292     if (redraw)
6293       DrawPosition(TRUE, boards[currentMove]);
6294 }
6295
6296 void
6297 SendBoard (ChessProgramState *cps, int moveNum)
6298 {
6299     char message[MSG_SIZ];
6300
6301     if (cps->useSetboard) {
6302       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6303       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6304       SendToProgram(message, cps);
6305       free(fen);
6306
6307     } else {
6308       ChessSquare *bp;
6309       int i, j, left=0, right=BOARD_WIDTH;
6310       /* Kludge to set black to move, avoiding the troublesome and now
6311        * deprecated "black" command.
6312        */
6313       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6314         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6315
6316       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6317
6318       SendToProgram("edit\n", cps);
6319       SendToProgram("#\n", cps);
6320       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6321         bp = &boards[moveNum][i][left];
6322         for (j = left; j < right; j++, bp++) {
6323           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6324           if ((int) *bp < (int) BlackPawn) {
6325             if(j == BOARD_RGHT+1)
6326                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6327             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6328             if(message[0] == '+' || message[0] == '~') {
6329               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6330                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6331                         AAA + j, ONE + i);
6332             }
6333             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6334                 message[1] = BOARD_RGHT   - 1 - j + '1';
6335                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6336             }
6337             SendToProgram(message, cps);
6338           }
6339         }
6340       }
6341
6342       SendToProgram("c\n", cps);
6343       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6344         bp = &boards[moveNum][i][left];
6345         for (j = left; j < right; j++, bp++) {
6346           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6347           if (((int) *bp != (int) EmptySquare)
6348               && ((int) *bp >= (int) BlackPawn)) {
6349             if(j == BOARD_LEFT-2)
6350                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6351             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6352                     AAA + j, ONE + i);
6353             if(message[0] == '+' || message[0] == '~') {
6354               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6355                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6356                         AAA + j, ONE + i);
6357             }
6358             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6359                 message[1] = BOARD_RGHT   - 1 - j + '1';
6360                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6361             }
6362             SendToProgram(message, cps);
6363           }
6364         }
6365       }
6366
6367       SendToProgram(".\n", cps);
6368     }
6369     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6370 }
6371
6372 char exclusionHeader[MSG_SIZ];
6373 int exCnt, excludePtr;
6374 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6375 static Exclusion excluTab[200];
6376 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6377
6378 static void
6379 WriteMap (int s)
6380 {
6381     int j;
6382     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6383     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6384 }
6385
6386 static void
6387 ClearMap ()
6388 {
6389     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6390     excludePtr = 24; exCnt = 0;
6391     WriteMap(0);
6392 }
6393
6394 static void
6395 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6396 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6397     char buf[2*MOVE_LEN], *p;
6398     Exclusion *e = excluTab;
6399     int i;
6400     for(i=0; i<exCnt; i++)
6401         if(e[i].ff == fromX && e[i].fr == fromY &&
6402            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6403     if(i == exCnt) { // was not in exclude list; add it
6404         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6405         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6406             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6407             return; // abort
6408         }
6409         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6410         excludePtr++; e[i].mark = excludePtr++;
6411         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6412         exCnt++;
6413     }
6414     exclusionHeader[e[i].mark] = state;
6415 }
6416
6417 static int
6418 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6419 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6420     char buf[MSG_SIZ];
6421     int j, k;
6422     ChessMove moveType;
6423     if((signed char)promoChar == -1) { // kludge to indicate best move
6424         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6425             return 1; // if unparsable, abort
6426     }
6427     // update exclusion map (resolving toggle by consulting existing state)
6428     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6429     j = k%8; k >>= 3;
6430     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6431     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6432          excludeMap[k] |=   1<<j;
6433     else excludeMap[k] &= ~(1<<j);
6434     // update header
6435     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6436     // inform engine
6437     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6438     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6439     SendToBoth(buf);
6440     return (state == '+');
6441 }
6442
6443 static void
6444 ExcludeClick (int index)
6445 {
6446     int i, j;
6447     Exclusion *e = excluTab;
6448     if(index < 25) { // none, best or tail clicked
6449         if(index < 13) { // none: include all
6450             WriteMap(0); // clear map
6451             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6452             SendToBoth("include all\n"); // and inform engine
6453         } else if(index > 18) { // tail
6454             if(exclusionHeader[19] == '-') { // tail was excluded
6455                 SendToBoth("include all\n");
6456                 WriteMap(0); // clear map completely
6457                 // now re-exclude selected moves
6458                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6459                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6460             } else { // tail was included or in mixed state
6461                 SendToBoth("exclude all\n");
6462                 WriteMap(0xFF); // fill map completely
6463                 // now re-include selected moves
6464                 j = 0; // count them
6465                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6466                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6467                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6468             }
6469         } else { // best
6470             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6471         }
6472     } else {
6473         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6474             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6475             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6476             break;
6477         }
6478     }
6479 }
6480
6481 ChessSquare
6482 DefaultPromoChoice (int white)
6483 {
6484     ChessSquare result;
6485     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6486        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6487         result = WhiteFerz; // no choice
6488     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6489         result= WhiteKing; // in Suicide Q is the last thing we want
6490     else if(gameInfo.variant == VariantSpartan)
6491         result = white ? WhiteQueen : WhiteAngel;
6492     else result = WhiteQueen;
6493     if(!white) result = WHITE_TO_BLACK result;
6494     return result;
6495 }
6496
6497 static int autoQueen; // [HGM] oneclick
6498
6499 int
6500 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6501 {
6502     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6503     /* [HGM] add Shogi promotions */
6504     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6505     ChessSquare piece, partner;
6506     ChessMove moveType;
6507     Boolean premove;
6508
6509     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6510     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6511
6512     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6513       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6514         return FALSE;
6515
6516     piece = boards[currentMove][fromY][fromX];
6517     if(gameInfo.variant == VariantChu) {
6518         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6519         promotionZoneSize = BOARD_HEIGHT/3;
6520         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6521     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6522         promotionZoneSize = BOARD_HEIGHT/3;
6523         highestPromotingPiece = (int)WhiteAlfil;
6524     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6525         promotionZoneSize = 3;
6526     }
6527
6528     // Treat Lance as Pawn when it is not representing Amazon or Lance
6529     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6530         if(piece == WhiteLance) piece = WhitePawn; else
6531         if(piece == BlackLance) piece = BlackPawn;
6532     }
6533
6534     // next weed out all moves that do not touch the promotion zone at all
6535     if((int)piece >= BlackPawn) {
6536         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6537              return FALSE;
6538         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6539         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6540     } else {
6541         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6542            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6543         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6544              return FALSE;
6545     }
6546
6547     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6548
6549     // weed out mandatory Shogi promotions
6550     if(gameInfo.variant == VariantShogi) {
6551         if(piece >= BlackPawn) {
6552             if(toY == 0 && piece == BlackPawn ||
6553                toY == 0 && piece == BlackQueen ||
6554                toY <= 1 && piece == BlackKnight) {
6555                 *promoChoice = '+';
6556                 return FALSE;
6557             }
6558         } else {
6559             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6560                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6561                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6562                 *promoChoice = '+';
6563                 return FALSE;
6564             }
6565         }
6566     }
6567
6568     // weed out obviously illegal Pawn moves
6569     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6570         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6571         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6572         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6573         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6574         // note we are not allowed to test for valid (non-)capture, due to premove
6575     }
6576
6577     // we either have a choice what to promote to, or (in Shogi) whether to promote
6578     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6579        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6580         ChessSquare p=BlackFerz;  // no choice
6581         while(p < EmptySquare) {  //but make sure we use piece that exists
6582             *promoChoice = PieceToChar(p++);
6583             if(*promoChoice != '.') break;
6584         }
6585         return FALSE;
6586     }
6587     // no sense asking what we must promote to if it is going to explode...
6588     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6589         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6590         return FALSE;
6591     }
6592     // give caller the default choice even if we will not make it
6593     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6594     partner = piece; // pieces can promote if the pieceToCharTable says so
6595     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6596     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6597     if(        sweepSelect && gameInfo.variant != VariantGreat
6598                            && gameInfo.variant != VariantGrand
6599                            && gameInfo.variant != VariantSuper) return FALSE;
6600     if(autoQueen) return FALSE; // predetermined
6601
6602     // suppress promotion popup on illegal moves that are not premoves
6603     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6604               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6605     if(appData.testLegality && !premove) {
6606         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6607                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6608         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6609         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6610             return FALSE;
6611     }
6612
6613     return TRUE;
6614 }
6615
6616 int
6617 InPalace (int row, int column)
6618 {   /* [HGM] for Xiangqi */
6619     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6620          column < (BOARD_WIDTH + 4)/2 &&
6621          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6622     return FALSE;
6623 }
6624
6625 int
6626 PieceForSquare (int x, int y)
6627 {
6628   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6629      return -1;
6630   else
6631      return boards[currentMove][y][x];
6632 }
6633
6634 int
6635 OKToStartUserMove (int x, int y)
6636 {
6637     ChessSquare from_piece;
6638     int white_piece;
6639
6640     if (matchMode) return FALSE;
6641     if (gameMode == EditPosition) return TRUE;
6642
6643     if (x >= 0 && y >= 0)
6644       from_piece = boards[currentMove][y][x];
6645     else
6646       from_piece = EmptySquare;
6647
6648     if (from_piece == EmptySquare) return FALSE;
6649
6650     white_piece = (int)from_piece >= (int)WhitePawn &&
6651       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6652
6653     switch (gameMode) {
6654       case AnalyzeFile:
6655       case TwoMachinesPlay:
6656       case EndOfGame:
6657         return FALSE;
6658
6659       case IcsObserving:
6660       case IcsIdle:
6661         return FALSE;
6662
6663       case MachinePlaysWhite:
6664       case IcsPlayingBlack:
6665         if (appData.zippyPlay) return FALSE;
6666         if (white_piece) {
6667             DisplayMoveError(_("You are playing Black"));
6668             return FALSE;
6669         }
6670         break;
6671
6672       case MachinePlaysBlack:
6673       case IcsPlayingWhite:
6674         if (appData.zippyPlay) return FALSE;
6675         if (!white_piece) {
6676             DisplayMoveError(_("You are playing White"));
6677             return FALSE;
6678         }
6679         break;
6680
6681       case PlayFromGameFile:
6682             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6683       case EditGame:
6684         if (!white_piece && WhiteOnMove(currentMove)) {
6685             DisplayMoveError(_("It is White's turn"));
6686             return FALSE;
6687         }
6688         if (white_piece && !WhiteOnMove(currentMove)) {
6689             DisplayMoveError(_("It is Black's turn"));
6690             return FALSE;
6691         }
6692         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6693             /* Editing correspondence game history */
6694             /* Could disallow this or prompt for confirmation */
6695             cmailOldMove = -1;
6696         }
6697         break;
6698
6699       case BeginningOfGame:
6700         if (appData.icsActive) return FALSE;
6701         if (!appData.noChessProgram) {
6702             if (!white_piece) {
6703                 DisplayMoveError(_("You are playing White"));
6704                 return FALSE;
6705             }
6706         }
6707         break;
6708
6709       case Training:
6710         if (!white_piece && WhiteOnMove(currentMove)) {
6711             DisplayMoveError(_("It is White's turn"));
6712             return FALSE;
6713         }
6714         if (white_piece && !WhiteOnMove(currentMove)) {
6715             DisplayMoveError(_("It is Black's turn"));
6716             return FALSE;
6717         }
6718         break;
6719
6720       default:
6721       case IcsExamining:
6722         break;
6723     }
6724     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6725         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6726         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6727         && gameMode != AnalyzeFile && gameMode != Training) {
6728         DisplayMoveError(_("Displayed position is not current"));
6729         return FALSE;
6730     }
6731     return TRUE;
6732 }
6733
6734 Boolean
6735 OnlyMove (int *x, int *y, Boolean captures)
6736 {
6737     DisambiguateClosure cl;
6738     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6739     switch(gameMode) {
6740       case MachinePlaysBlack:
6741       case IcsPlayingWhite:
6742       case BeginningOfGame:
6743         if(!WhiteOnMove(currentMove)) return FALSE;
6744         break;
6745       case MachinePlaysWhite:
6746       case IcsPlayingBlack:
6747         if(WhiteOnMove(currentMove)) return FALSE;
6748         break;
6749       case EditGame:
6750         break;
6751       default:
6752         return FALSE;
6753     }
6754     cl.pieceIn = EmptySquare;
6755     cl.rfIn = *y;
6756     cl.ffIn = *x;
6757     cl.rtIn = -1;
6758     cl.ftIn = -1;
6759     cl.promoCharIn = NULLCHAR;
6760     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6761     if( cl.kind == NormalMove ||
6762         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6763         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6764         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6765       fromX = cl.ff;
6766       fromY = cl.rf;
6767       *x = cl.ft;
6768       *y = cl.rt;
6769       return TRUE;
6770     }
6771     if(cl.kind != ImpossibleMove) return FALSE;
6772     cl.pieceIn = EmptySquare;
6773     cl.rfIn = -1;
6774     cl.ffIn = -1;
6775     cl.rtIn = *y;
6776     cl.ftIn = *x;
6777     cl.promoCharIn = NULLCHAR;
6778     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6779     if( cl.kind == NormalMove ||
6780         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6781         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6782         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6783       fromX = cl.ff;
6784       fromY = cl.rf;
6785       *x = cl.ft;
6786       *y = cl.rt;
6787       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6788       return TRUE;
6789     }
6790     return FALSE;
6791 }
6792
6793 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6794 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6795 int lastLoadGameUseList = FALSE;
6796 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6797 ChessMove lastLoadGameStart = EndOfFile;
6798 int doubleClick;
6799
6800 void
6801 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6802 {
6803     ChessMove moveType;
6804     ChessSquare pup;
6805     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6806
6807     /* Check if the user is playing in turn.  This is complicated because we
6808        let the user "pick up" a piece before it is his turn.  So the piece he
6809        tried to pick up may have been captured by the time he puts it down!
6810        Therefore we use the color the user is supposed to be playing in this
6811        test, not the color of the piece that is currently on the starting
6812        square---except in EditGame mode, where the user is playing both
6813        sides; fortunately there the capture race can't happen.  (It can
6814        now happen in IcsExamining mode, but that's just too bad.  The user
6815        will get a somewhat confusing message in that case.)
6816        */
6817
6818     switch (gameMode) {
6819       case AnalyzeFile:
6820       case TwoMachinesPlay:
6821       case EndOfGame:
6822       case IcsObserving:
6823       case IcsIdle:
6824         /* We switched into a game mode where moves are not accepted,
6825            perhaps while the mouse button was down. */
6826         return;
6827
6828       case MachinePlaysWhite:
6829         /* User is moving for Black */
6830         if (WhiteOnMove(currentMove)) {
6831             DisplayMoveError(_("It is White's turn"));
6832             return;
6833         }
6834         break;
6835
6836       case MachinePlaysBlack:
6837         /* User is moving for White */
6838         if (!WhiteOnMove(currentMove)) {
6839             DisplayMoveError(_("It is Black's turn"));
6840             return;
6841         }
6842         break;
6843
6844       case PlayFromGameFile:
6845             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6846       case EditGame:
6847       case IcsExamining:
6848       case BeginningOfGame:
6849       case AnalyzeMode:
6850       case Training:
6851         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6852         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6853             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6854             /* User is moving for Black */
6855             if (WhiteOnMove(currentMove)) {
6856                 DisplayMoveError(_("It is White's turn"));
6857                 return;
6858             }
6859         } else {
6860             /* User is moving for White */
6861             if (!WhiteOnMove(currentMove)) {
6862                 DisplayMoveError(_("It is Black's turn"));
6863                 return;
6864             }
6865         }
6866         break;
6867
6868       case IcsPlayingBlack:
6869         /* User is moving for Black */
6870         if (WhiteOnMove(currentMove)) {
6871             if (!appData.premove) {
6872                 DisplayMoveError(_("It is White's turn"));
6873             } else if (toX >= 0 && toY >= 0) {
6874                 premoveToX = toX;
6875                 premoveToY = toY;
6876                 premoveFromX = fromX;
6877                 premoveFromY = fromY;
6878                 premovePromoChar = promoChar;
6879                 gotPremove = 1;
6880                 if (appData.debugMode)
6881                     fprintf(debugFP, "Got premove: fromX %d,"
6882                             "fromY %d, toX %d, toY %d\n",
6883                             fromX, fromY, toX, toY);
6884             }
6885             return;
6886         }
6887         break;
6888
6889       case IcsPlayingWhite:
6890         /* User is moving for White */
6891         if (!WhiteOnMove(currentMove)) {
6892             if (!appData.premove) {
6893                 DisplayMoveError(_("It is Black's turn"));
6894             } else if (toX >= 0 && toY >= 0) {
6895                 premoveToX = toX;
6896                 premoveToY = toY;
6897                 premoveFromX = fromX;
6898                 premoveFromY = fromY;
6899                 premovePromoChar = promoChar;
6900                 gotPremove = 1;
6901                 if (appData.debugMode)
6902                     fprintf(debugFP, "Got premove: fromX %d,"
6903                             "fromY %d, toX %d, toY %d\n",
6904                             fromX, fromY, toX, toY);
6905             }
6906             return;
6907         }
6908         break;
6909
6910       default:
6911         break;
6912
6913       case EditPosition:
6914         /* EditPosition, empty square, or different color piece;
6915            click-click move is possible */
6916         if (toX == -2 || toY == -2) {
6917             boards[0][fromY][fromX] = EmptySquare;
6918             DrawPosition(FALSE, boards[currentMove]);
6919             return;
6920         } else if (toX >= 0 && toY >= 0) {
6921             boards[0][toY][toX] = boards[0][fromY][fromX];
6922             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6923                 if(boards[0][fromY][0] != EmptySquare) {
6924                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6925                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6926                 }
6927             } else
6928             if(fromX == BOARD_RGHT+1) {
6929                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6930                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6931                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6932                 }
6933             } else
6934             boards[0][fromY][fromX] = gatingPiece;
6935             DrawPosition(FALSE, boards[currentMove]);
6936             return;
6937         }
6938         return;
6939     }
6940
6941     if(toX < 0 || toY < 0) return;
6942     pup = boards[currentMove][toY][toX];
6943
6944     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6945     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6946          if( pup != EmptySquare ) return;
6947          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6948            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6949                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6950            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6951            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6952            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6953            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6954          fromY = DROP_RANK;
6955     }
6956
6957     /* [HGM] always test for legality, to get promotion info */
6958     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6959                                          fromY, fromX, toY, toX, promoChar);
6960
6961     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6962
6963     /* [HGM] but possibly ignore an IllegalMove result */
6964     if (appData.testLegality) {
6965         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6966             DisplayMoveError(_("Illegal move"));
6967             return;
6968         }
6969     }
6970
6971     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6972         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6973              ClearPremoveHighlights(); // was included
6974         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6975         return;
6976     }
6977
6978     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6979 }
6980
6981 /* Common tail of UserMoveEvent and DropMenuEvent */
6982 int
6983 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6984 {
6985     char *bookHit = 0;
6986
6987     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6988         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6989         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6990         if(WhiteOnMove(currentMove)) {
6991             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6992         } else {
6993             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6994         }
6995     }
6996
6997     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6998        move type in caller when we know the move is a legal promotion */
6999     if(moveType == NormalMove && promoChar)
7000         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7001
7002     /* [HGM] <popupFix> The following if has been moved here from
7003        UserMoveEvent(). Because it seemed to belong here (why not allow
7004        piece drops in training games?), and because it can only be
7005        performed after it is known to what we promote. */
7006     if (gameMode == Training) {
7007       /* compare the move played on the board to the next move in the
7008        * game. If they match, display the move and the opponent's response.
7009        * If they don't match, display an error message.
7010        */
7011       int saveAnimate;
7012       Board testBoard;
7013       CopyBoard(testBoard, boards[currentMove]);
7014       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7015
7016       if (CompareBoards(testBoard, boards[currentMove+1])) {
7017         ForwardInner(currentMove+1);
7018
7019         /* Autoplay the opponent's response.
7020          * if appData.animate was TRUE when Training mode was entered,
7021          * the response will be animated.
7022          */
7023         saveAnimate = appData.animate;
7024         appData.animate = animateTraining;
7025         ForwardInner(currentMove+1);
7026         appData.animate = saveAnimate;
7027
7028         /* check for the end of the game */
7029         if (currentMove >= forwardMostMove) {
7030           gameMode = PlayFromGameFile;
7031           ModeHighlight();
7032           SetTrainingModeOff();
7033           DisplayInformation(_("End of game"));
7034         }
7035       } else {
7036         DisplayError(_("Incorrect move"), 0);
7037       }
7038       return 1;
7039     }
7040
7041   /* Ok, now we know that the move is good, so we can kill
7042      the previous line in Analysis Mode */
7043   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7044                                 && currentMove < forwardMostMove) {
7045     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7046     else forwardMostMove = currentMove;
7047   }
7048
7049   ClearMap();
7050
7051   /* If we need the chess program but it's dead, restart it */
7052   ResurrectChessProgram();
7053
7054   /* A user move restarts a paused game*/
7055   if (pausing)
7056     PauseEvent();
7057
7058   thinkOutput[0] = NULLCHAR;
7059
7060   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7061
7062   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7063     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7064     return 1;
7065   }
7066
7067   if (gameMode == BeginningOfGame) {
7068     if (appData.noChessProgram) {
7069       gameMode = EditGame;
7070       SetGameInfo();
7071     } else {
7072       char buf[MSG_SIZ];
7073       gameMode = MachinePlaysBlack;
7074       StartClocks();
7075       SetGameInfo();
7076       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7077       DisplayTitle(buf);
7078       if (first.sendName) {
7079         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7080         SendToProgram(buf, &first);
7081       }
7082       StartClocks();
7083     }
7084     ModeHighlight();
7085   }
7086
7087   /* Relay move to ICS or chess engine */
7088   if (appData.icsActive) {
7089     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7090         gameMode == IcsExamining) {
7091       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7092         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7093         SendToICS("draw ");
7094         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7095       }
7096       // also send plain move, in case ICS does not understand atomic claims
7097       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7098       ics_user_moved = 1;
7099     }
7100   } else {
7101     if (first.sendTime && (gameMode == BeginningOfGame ||
7102                            gameMode == MachinePlaysWhite ||
7103                            gameMode == MachinePlaysBlack)) {
7104       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7105     }
7106     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7107          // [HGM] book: if program might be playing, let it use book
7108         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7109         first.maybeThinking = TRUE;
7110     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7111         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7112         SendBoard(&first, currentMove+1);
7113         if(second.analyzing) {
7114             if(!second.useSetboard) SendToProgram("undo\n", &second);
7115             SendBoard(&second, currentMove+1);
7116         }
7117     } else {
7118         SendMoveToProgram(forwardMostMove-1, &first);
7119         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7120     }
7121     if (currentMove == cmailOldMove + 1) {
7122       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7123     }
7124   }
7125
7126   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7127
7128   switch (gameMode) {
7129   case EditGame:
7130     if(appData.testLegality)
7131     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7132     case MT_NONE:
7133     case MT_CHECK:
7134       break;
7135     case MT_CHECKMATE:
7136     case MT_STAINMATE:
7137       if (WhiteOnMove(currentMove)) {
7138         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7139       } else {
7140         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7141       }
7142       break;
7143     case MT_STALEMATE:
7144       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7145       break;
7146     }
7147     break;
7148
7149   case MachinePlaysBlack:
7150   case MachinePlaysWhite:
7151     /* disable certain menu options while machine is thinking */
7152     SetMachineThinkingEnables();
7153     break;
7154
7155   default:
7156     break;
7157   }
7158
7159   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7160   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7161
7162   if(bookHit) { // [HGM] book: simulate book reply
7163         static char bookMove[MSG_SIZ]; // a bit generous?
7164
7165         programStats.nodes = programStats.depth = programStats.time =
7166         programStats.score = programStats.got_only_move = 0;
7167         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7168
7169         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7170         strcat(bookMove, bookHit);
7171         HandleMachineMove(bookMove, &first);
7172   }
7173   return 1;
7174 }
7175
7176 void
7177 MarkByFEN(char *fen)
7178 {
7179         int r, f;
7180         if(!appData.markers || !appData.highlightDragging) return;
7181         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7182         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7183         while(*fen) {
7184             int s = 0;
7185             marker[r][f] = 0;
7186             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7187             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7188             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7189             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7190             if(*fen == 'T') marker[r][f++] = 0; else
7191             if(*fen == 'Y') marker[r][f++] = 1; else
7192             if(*fen == 'G') marker[r][f++] = 3; else
7193             if(*fen == 'B') marker[r][f++] = 4; else
7194             if(*fen == 'C') marker[r][f++] = 5; else
7195             if(*fen == 'M') marker[r][f++] = 6; else
7196             if(*fen == 'W') marker[r][f++] = 7; else
7197             if(*fen == 'D') marker[r][f++] = 8; else
7198             if(*fen == 'R') marker[r][f++] = 2; else {
7199                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7200               f += s; fen -= s>0;
7201             }
7202             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7203             if(r < 0) break;
7204             fen++;
7205         }
7206         DrawPosition(TRUE, NULL);
7207 }
7208
7209 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7210
7211 void
7212 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7213 {
7214     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7215     Markers *m = (Markers *) closure;
7216     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7217         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7218                          || kind == WhiteCapturesEnPassant
7219                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7220     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7221 }
7222
7223 static int hoverSavedValid;
7224
7225 void
7226 MarkTargetSquares (int clear)
7227 {
7228   int x, y, sum=0;
7229   if(clear) { // no reason to ever suppress clearing
7230     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7231     hoverSavedValid = 0;
7232     if(!sum) return; // nothing was cleared,no redraw needed
7233   } else {
7234     int capt = 0;
7235     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7236        !appData.testLegality || gameMode == EditPosition) return;
7237     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7238     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7239       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7240       if(capt)
7241       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7242     }
7243   }
7244   DrawPosition(FALSE, NULL);
7245 }
7246
7247 int
7248 Explode (Board board, int fromX, int fromY, int toX, int toY)
7249 {
7250     if(gameInfo.variant == VariantAtomic &&
7251        (board[toY][toX] != EmptySquare ||                     // capture?
7252         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7253                          board[fromY][fromX] == BlackPawn   )
7254       )) {
7255         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7256         return TRUE;
7257     }
7258     return FALSE;
7259 }
7260
7261 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7262
7263 int
7264 CanPromote (ChessSquare piece, int y)
7265 {
7266         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7267         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7268         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7269         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7270            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7271            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7272          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7273         return (piece == BlackPawn && y <= zone ||
7274                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7275                 piece == BlackLance && y == 1 ||
7276                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7277 }
7278
7279 void
7280 HoverEvent (int xPix, int yPix, int x, int y)
7281 {
7282         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7283         int r, f;
7284         if(!first.highlight) return;
7285         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7286         if(x == oldX && y == oldY) return; // only do something if we enter new square
7287         oldFromX = fromX; oldFromY = fromY;
7288         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7289           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7290             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7291           hoverSavedValid = 1;
7292         } else if(oldX != x || oldY != y) {
7293           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7294           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7295           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7296             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7297           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7298             char buf[MSG_SIZ];
7299             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7300             SendToProgram(buf, &first);
7301           }
7302           oldX = x; oldY = y;
7303 //        SetHighlights(fromX, fromY, x, y);
7304         }
7305 }
7306
7307 void ReportClick(char *action, int x, int y)
7308 {
7309         char buf[MSG_SIZ]; // Inform engine of what user does
7310         int r, f;
7311         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7312           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7313         if(!first.highlight || gameMode == EditPosition) return;
7314         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7315         SendToProgram(buf, &first);
7316 }
7317
7318 void
7319 LeftClick (ClickType clickType, int xPix, int yPix)
7320 {
7321     int x, y;
7322     Boolean saveAnimate;
7323     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7324     char promoChoice = NULLCHAR;
7325     ChessSquare piece;
7326     static TimeMark lastClickTime, prevClickTime;
7327
7328     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7329
7330     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7331
7332     if (clickType == Press) ErrorPopDown();
7333     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7334
7335     x = EventToSquare(xPix, BOARD_WIDTH);
7336     y = EventToSquare(yPix, BOARD_HEIGHT);
7337     if (!flipView && y >= 0) {
7338         y = BOARD_HEIGHT - 1 - y;
7339     }
7340     if (flipView && x >= 0) {
7341         x = BOARD_WIDTH - 1 - x;
7342     }
7343
7344     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7345         defaultPromoChoice = promoSweep;
7346         promoSweep = EmptySquare;   // terminate sweep
7347         promoDefaultAltered = TRUE;
7348         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7349     }
7350
7351     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7352         if(clickType == Release) return; // ignore upclick of click-click destination
7353         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7354         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7355         if(gameInfo.holdingsWidth &&
7356                 (WhiteOnMove(currentMove)
7357                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7358                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7359             // click in right holdings, for determining promotion piece
7360             ChessSquare p = boards[currentMove][y][x];
7361             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7362             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7363             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7364                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7365                 fromX = fromY = -1;
7366                 return;
7367             }
7368         }
7369         DrawPosition(FALSE, boards[currentMove]);
7370         return;
7371     }
7372
7373     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7374     if(clickType == Press
7375             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7376               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7377               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7378         return;
7379
7380     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7381         // could be static click on premove from-square: abort premove
7382         gotPremove = 0;
7383         ClearPremoveHighlights();
7384     }
7385
7386     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7387         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7388
7389     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7390         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7391                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7392         defaultPromoChoice = DefaultPromoChoice(side);
7393     }
7394
7395     autoQueen = appData.alwaysPromoteToQueen;
7396
7397     if (fromX == -1) {
7398       int originalY = y;
7399       gatingPiece = EmptySquare;
7400       if (clickType != Press) {
7401         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7402             DragPieceEnd(xPix, yPix); dragging = 0;
7403             DrawPosition(FALSE, NULL);
7404         }
7405         return;
7406       }
7407       doubleClick = FALSE;
7408       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7409         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7410       }
7411       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7412       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7413          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7414          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7415             /* First square */
7416             if (OKToStartUserMove(fromX, fromY)) {
7417                 second = 0;
7418                 ReportClick("lift", x, y);
7419                 MarkTargetSquares(0);
7420                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7421                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7422                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7423                     promoSweep = defaultPromoChoice;
7424                     selectFlag = 0; lastX = xPix; lastY = yPix;
7425                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7426                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7427                 }
7428                 if (appData.highlightDragging) {
7429                     SetHighlights(fromX, fromY, -1, -1);
7430                 } else {
7431                     ClearHighlights();
7432                 }
7433             } else fromX = fromY = -1;
7434             return;
7435         }
7436     }
7437
7438     /* fromX != -1 */
7439     if (clickType == Press && gameMode != EditPosition) {
7440         ChessSquare fromP;
7441         ChessSquare toP;
7442         int frc;
7443
7444         // ignore off-board to clicks
7445         if(y < 0 || x < 0) return;
7446
7447         /* Check if clicking again on the same color piece */
7448         fromP = boards[currentMove][fromY][fromX];
7449         toP = boards[currentMove][y][x];
7450         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7451         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7452            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7453              WhitePawn <= toP && toP <= WhiteKing &&
7454              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7455              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7456             (BlackPawn <= fromP && fromP <= BlackKing &&
7457              BlackPawn <= toP && toP <= BlackKing &&
7458              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7459              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7460             /* Clicked again on same color piece -- changed his mind */
7461             second = (x == fromX && y == fromY);
7462             killX = killY = -1;
7463             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7464                 second = FALSE; // first double-click rather than scond click
7465                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7466             }
7467             promoDefaultAltered = FALSE;
7468             MarkTargetSquares(1);
7469            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7470             if (appData.highlightDragging) {
7471                 SetHighlights(x, y, -1, -1);
7472             } else {
7473                 ClearHighlights();
7474             }
7475             if (OKToStartUserMove(x, y)) {
7476                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7477                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7478                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7479                  gatingPiece = boards[currentMove][fromY][fromX];
7480                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7481                 fromX = x;
7482                 fromY = y; dragging = 1;
7483                 ReportClick("lift", x, y);
7484                 MarkTargetSquares(0);
7485                 DragPieceBegin(xPix, yPix, FALSE);
7486                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7487                     promoSweep = defaultPromoChoice;
7488                     selectFlag = 0; lastX = xPix; lastY = yPix;
7489                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7490                 }
7491             }
7492            }
7493            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7494            second = FALSE;
7495         }
7496         // ignore clicks on holdings
7497         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7498     }
7499
7500     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7501         DragPieceEnd(xPix, yPix); dragging = 0;
7502         if(clearFlag) {
7503             // a deferred attempt to click-click move an empty square on top of a piece
7504             boards[currentMove][y][x] = EmptySquare;
7505             ClearHighlights();
7506             DrawPosition(FALSE, boards[currentMove]);
7507             fromX = fromY = -1; clearFlag = 0;
7508             return;
7509         }
7510         if (appData.animateDragging) {
7511             /* Undo animation damage if any */
7512             DrawPosition(FALSE, NULL);
7513         }
7514         if (second || sweepSelecting) {
7515             /* Second up/down in same square; just abort move */
7516             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7517             second = sweepSelecting = 0;
7518             fromX = fromY = -1;
7519             gatingPiece = EmptySquare;
7520             MarkTargetSquares(1);
7521             ClearHighlights();
7522             gotPremove = 0;
7523             ClearPremoveHighlights();
7524         } else {
7525             /* First upclick in same square; start click-click mode */
7526             SetHighlights(x, y, -1, -1);
7527         }
7528         return;
7529     }
7530
7531     clearFlag = 0;
7532
7533     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7534         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7535         DisplayMessage(_("only marked squares are legal"),"");
7536         DrawPosition(TRUE, NULL);
7537         return; // ignore to-click
7538     }
7539
7540     /* we now have a different from- and (possibly off-board) to-square */
7541     /* Completed move */
7542     if(!sweepSelecting) {
7543         toX = x;
7544         toY = y;
7545     }
7546
7547     piece = boards[currentMove][fromY][fromX];
7548
7549     saveAnimate = appData.animate;
7550     if (clickType == Press) {
7551         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7552         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7553             // must be Edit Position mode with empty-square selected
7554             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7555             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7556             return;
7557         }
7558         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7559             return;
7560         }
7561         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7562             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7563         } else
7564         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7565         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7566           if(appData.sweepSelect) {
7567             promoSweep = defaultPromoChoice;
7568             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7569             selectFlag = 0; lastX = xPix; lastY = yPix;
7570             Sweep(0); // Pawn that is going to promote: preview promotion piece
7571             sweepSelecting = 1;
7572             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7573             MarkTargetSquares(1);
7574           }
7575           return; // promo popup appears on up-click
7576         }
7577         /* Finish clickclick move */
7578         if (appData.animate || appData.highlightLastMove) {
7579             SetHighlights(fromX, fromY, toX, toY);
7580         } else {
7581             ClearHighlights();
7582         }
7583     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7584         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7585         if (appData.animate || appData.highlightLastMove) {
7586             SetHighlights(fromX, fromY, toX, toY);
7587         } else {
7588             ClearHighlights();
7589         }
7590     } else {
7591 #if 0
7592 // [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
7593         /* Finish drag move */
7594         if (appData.highlightLastMove) {
7595             SetHighlights(fromX, fromY, toX, toY);
7596         } else {
7597             ClearHighlights();
7598         }
7599 #endif
7600         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7601         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7602           dragging *= 2;            // flag button-less dragging if we are dragging
7603           MarkTargetSquares(1);
7604           if(x == killX && y == killY) killX = killY = -1; else {
7605             killX = x; killY = y;     //remeber this square as intermediate
7606             ReportClick("put", x, y); // and inform engine
7607             ReportClick("lift", x, y);
7608             MarkTargetSquares(0);
7609             return;
7610           }
7611         }
7612         DragPieceEnd(xPix, yPix); dragging = 0;
7613         /* Don't animate move and drag both */
7614         appData.animate = FALSE;
7615     }
7616
7617     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7618     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7619         ChessSquare piece = boards[currentMove][fromY][fromX];
7620         if(gameMode == EditPosition && piece != EmptySquare &&
7621            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7622             int n;
7623
7624             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7625                 n = PieceToNumber(piece - (int)BlackPawn);
7626                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7627                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7628                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7629             } else
7630             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7631                 n = PieceToNumber(piece);
7632                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7633                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7634                 boards[currentMove][n][BOARD_WIDTH-2]++;
7635             }
7636             boards[currentMove][fromY][fromX] = EmptySquare;
7637         }
7638         ClearHighlights();
7639         fromX = fromY = -1;
7640         MarkTargetSquares(1);
7641         DrawPosition(TRUE, boards[currentMove]);
7642         return;
7643     }
7644
7645     // off-board moves should not be highlighted
7646     if(x < 0 || y < 0) ClearHighlights();
7647     else ReportClick("put", x, y);
7648
7649     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7650
7651     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7652         SetHighlights(fromX, fromY, toX, toY);
7653         MarkTargetSquares(1);
7654         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7655             // [HGM] super: promotion to captured piece selected from holdings
7656             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7657             promotionChoice = TRUE;
7658             // kludge follows to temporarily execute move on display, without promoting yet
7659             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7660             boards[currentMove][toY][toX] = p;
7661             DrawPosition(FALSE, boards[currentMove]);
7662             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7663             boards[currentMove][toY][toX] = q;
7664             DisplayMessage("Click in holdings to choose piece", "");
7665             return;
7666         }
7667         PromotionPopUp(promoChoice);
7668     } else {
7669         int oldMove = currentMove;
7670         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7671         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7672         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7673         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7674            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7675             DrawPosition(TRUE, boards[currentMove]);
7676         MarkTargetSquares(1);
7677         fromX = fromY = -1;
7678     }
7679     appData.animate = saveAnimate;
7680     if (appData.animate || appData.animateDragging) {
7681         /* Undo animation damage if needed */
7682         DrawPosition(FALSE, NULL);
7683     }
7684 }
7685
7686 int
7687 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7688 {   // front-end-free part taken out of PieceMenuPopup
7689     int whichMenu; int xSqr, ySqr;
7690
7691     if(seekGraphUp) { // [HGM] seekgraph
7692         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7693         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7694         return -2;
7695     }
7696
7697     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7698          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7699         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7700         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7701         if(action == Press)   {
7702             originalFlip = flipView;
7703             flipView = !flipView; // temporarily flip board to see game from partners perspective
7704             DrawPosition(TRUE, partnerBoard);
7705             DisplayMessage(partnerStatus, "");
7706             partnerUp = TRUE;
7707         } else if(action == Release) {
7708             flipView = originalFlip;
7709             DrawPosition(TRUE, boards[currentMove]);
7710             partnerUp = FALSE;
7711         }
7712         return -2;
7713     }
7714
7715     xSqr = EventToSquare(x, BOARD_WIDTH);
7716     ySqr = EventToSquare(y, BOARD_HEIGHT);
7717     if (action == Release) {
7718         if(pieceSweep != EmptySquare) {
7719             EditPositionMenuEvent(pieceSweep, toX, toY);
7720             pieceSweep = EmptySquare;
7721         } else UnLoadPV(); // [HGM] pv
7722     }
7723     if (action != Press) return -2; // return code to be ignored
7724     switch (gameMode) {
7725       case IcsExamining:
7726         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7727       case EditPosition:
7728         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7729         if (xSqr < 0 || ySqr < 0) return -1;
7730         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7731         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7732         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7733         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7734         NextPiece(0);
7735         return 2; // grab
7736       case IcsObserving:
7737         if(!appData.icsEngineAnalyze) return -1;
7738       case IcsPlayingWhite:
7739       case IcsPlayingBlack:
7740         if(!appData.zippyPlay) goto noZip;
7741       case AnalyzeMode:
7742       case AnalyzeFile:
7743       case MachinePlaysWhite:
7744       case MachinePlaysBlack:
7745       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7746         if (!appData.dropMenu) {
7747           LoadPV(x, y);
7748           return 2; // flag front-end to grab mouse events
7749         }
7750         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7751            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7752       case EditGame:
7753       noZip:
7754         if (xSqr < 0 || ySqr < 0) return -1;
7755         if (!appData.dropMenu || appData.testLegality &&
7756             gameInfo.variant != VariantBughouse &&
7757             gameInfo.variant != VariantCrazyhouse) return -1;
7758         whichMenu = 1; // drop menu
7759         break;
7760       default:
7761         return -1;
7762     }
7763
7764     if (((*fromX = xSqr) < 0) ||
7765         ((*fromY = ySqr) < 0)) {
7766         *fromX = *fromY = -1;
7767         return -1;
7768     }
7769     if (flipView)
7770       *fromX = BOARD_WIDTH - 1 - *fromX;
7771     else
7772       *fromY = BOARD_HEIGHT - 1 - *fromY;
7773
7774     return whichMenu;
7775 }
7776
7777 void
7778 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7779 {
7780 //    char * hint = lastHint;
7781     FrontEndProgramStats stats;
7782
7783     stats.which = cps == &first ? 0 : 1;
7784     stats.depth = cpstats->depth;
7785     stats.nodes = cpstats->nodes;
7786     stats.score = cpstats->score;
7787     stats.time = cpstats->time;
7788     stats.pv = cpstats->movelist;
7789     stats.hint = lastHint;
7790     stats.an_move_index = 0;
7791     stats.an_move_count = 0;
7792
7793     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7794         stats.hint = cpstats->move_name;
7795         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7796         stats.an_move_count = cpstats->nr_moves;
7797     }
7798
7799     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
7800
7801     SetProgramStats( &stats );
7802 }
7803
7804 void
7805 ClearEngineOutputPane (int which)
7806 {
7807     static FrontEndProgramStats dummyStats;
7808     dummyStats.which = which;
7809     dummyStats.pv = "#";
7810     SetProgramStats( &dummyStats );
7811 }
7812
7813 #define MAXPLAYERS 500
7814
7815 char *
7816 TourneyStandings (int display)
7817 {
7818     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7819     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7820     char result, *p, *names[MAXPLAYERS];
7821
7822     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7823         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7824     names[0] = p = strdup(appData.participants);
7825     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7826
7827     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7828
7829     while(result = appData.results[nr]) {
7830         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7831         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7832         wScore = bScore = 0;
7833         switch(result) {
7834           case '+': wScore = 2; break;
7835           case '-': bScore = 2; break;
7836           case '=': wScore = bScore = 1; break;
7837           case ' ':
7838           case '*': return strdup("busy"); // tourney not finished
7839         }
7840         score[w] += wScore;
7841         score[b] += bScore;
7842         games[w]++;
7843         games[b]++;
7844         nr++;
7845     }
7846     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7847     for(w=0; w<nPlayers; w++) {
7848         bScore = -1;
7849         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7850         ranking[w] = b; points[w] = bScore; score[b] = -2;
7851     }
7852     p = malloc(nPlayers*34+1);
7853     for(w=0; w<nPlayers && w<display; w++)
7854         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7855     free(names[0]);
7856     return p;
7857 }
7858
7859 void
7860 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7861 {       // count all piece types
7862         int p, f, r;
7863         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7864         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7865         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7866                 p = board[r][f];
7867                 pCnt[p]++;
7868                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7869                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7870                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7871                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7872                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7873                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7874         }
7875 }
7876
7877 int
7878 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7879 {
7880         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7881         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7882
7883         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7884         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7885         if(myPawns == 2 && nMine == 3) // KPP
7886             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7887         if(myPawns == 1 && nMine == 2) // KP
7888             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7889         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7890             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7891         if(myPawns) return FALSE;
7892         if(pCnt[WhiteRook+side])
7893             return pCnt[BlackRook-side] ||
7894                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7895                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7896                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7897         if(pCnt[WhiteCannon+side]) {
7898             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7899             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7900         }
7901         if(pCnt[WhiteKnight+side])
7902             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7903         return FALSE;
7904 }
7905
7906 int
7907 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7908 {
7909         VariantClass v = gameInfo.variant;
7910
7911         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7912         if(v == VariantShatranj) return TRUE; // always winnable through baring
7913         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7914         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7915
7916         if(v == VariantXiangqi) {
7917                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7918
7919                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7920                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7921                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7922                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7923                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7924                 if(stale) // we have at least one last-rank P plus perhaps C
7925                     return majors // KPKX
7926                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7927                 else // KCA*E*
7928                     return pCnt[WhiteFerz+side] // KCAK
7929                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7930                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7931                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7932
7933         } else if(v == VariantKnightmate) {
7934                 if(nMine == 1) return FALSE;
7935                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7936         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7937                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7938
7939                 if(nMine == 1) return FALSE; // bare King
7940                 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
7941                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7942                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7943                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7944                 if(pCnt[WhiteKnight+side])
7945                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7946                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7947                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7948                 if(nBishops)
7949                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7950                 if(pCnt[WhiteAlfil+side])
7951                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7952                 if(pCnt[WhiteWazir+side])
7953                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7954         }
7955
7956         return TRUE;
7957 }
7958
7959 int
7960 CompareWithRights (Board b1, Board b2)
7961 {
7962     int rights = 0;
7963     if(!CompareBoards(b1, b2)) return FALSE;
7964     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7965     /* compare castling rights */
7966     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7967            rights++; /* King lost rights, while rook still had them */
7968     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7969         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7970            rights++; /* but at least one rook lost them */
7971     }
7972     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7973            rights++;
7974     if( b1[CASTLING][5] != NoRights ) {
7975         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7976            rights++;
7977     }
7978     return rights == 0;
7979 }
7980
7981 int
7982 Adjudicate (ChessProgramState *cps)
7983 {       // [HGM] some adjudications useful with buggy engines
7984         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7985         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7986         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7987         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7988         int k, drop, count = 0; static int bare = 1;
7989         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7990         Boolean canAdjudicate = !appData.icsActive;
7991
7992         // most tests only when we understand the game, i.e. legality-checking on
7993             if( appData.testLegality )
7994             {   /* [HGM] Some more adjudications for obstinate engines */
7995                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7996                 static int moveCount = 6;
7997                 ChessMove result;
7998                 char *reason = NULL;
7999
8000                 /* Count what is on board. */
8001                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8002
8003                 /* Some material-based adjudications that have to be made before stalemate test */
8004                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8005                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8006                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8007                      if(canAdjudicate && appData.checkMates) {
8008                          if(engineOpponent)
8009                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8010                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8011                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8012                          return 1;
8013                      }
8014                 }
8015
8016                 /* Bare King in Shatranj (loses) or Losers (wins) */
8017                 if( nrW == 1 || nrB == 1) {
8018                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8019                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8020                      if(canAdjudicate && appData.checkMates) {
8021                          if(engineOpponent)
8022                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8023                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8024                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8025                          return 1;
8026                      }
8027                   } else
8028                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8029                   {    /* bare King */
8030                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8031                         if(canAdjudicate && appData.checkMates) {
8032                             /* but only adjudicate if adjudication enabled */
8033                             if(engineOpponent)
8034                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8035                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8036                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8037                             return 1;
8038                         }
8039                   }
8040                 } else bare = 1;
8041
8042
8043             // don't wait for engine to announce game end if we can judge ourselves
8044             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8045               case MT_CHECK:
8046                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8047                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8048                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8049                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8050                             checkCnt++;
8051                         if(checkCnt >= 2) {
8052                             reason = "Xboard adjudication: 3rd check";
8053                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8054                             break;
8055                         }
8056                     }
8057                 }
8058               case MT_NONE:
8059               default:
8060                 break;
8061               case MT_STEALMATE:
8062               case MT_STALEMATE:
8063               case MT_STAINMATE:
8064                 reason = "Xboard adjudication: Stalemate";
8065                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8066                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8067                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8068                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8069                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8070                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8071                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8072                                                                         EP_CHECKMATE : EP_WINS);
8073                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8074                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8075                 }
8076                 break;
8077               case MT_CHECKMATE:
8078                 reason = "Xboard adjudication: Checkmate";
8079                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8080                 if(gameInfo.variant == VariantShogi) {
8081                     if(forwardMostMove > backwardMostMove
8082                        && moveList[forwardMostMove-1][1] == '@'
8083                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8084                         reason = "XBoard adjudication: pawn-drop mate";
8085                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8086                     }
8087                 }
8088                 break;
8089             }
8090
8091                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8092                     case EP_STALEMATE:
8093                         result = GameIsDrawn; break;
8094                     case EP_CHECKMATE:
8095                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8096                     case EP_WINS:
8097                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8098                     default:
8099                         result = EndOfFile;
8100                 }
8101                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8102                     if(engineOpponent)
8103                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8104                     GameEnds( result, reason, GE_XBOARD );
8105                     return 1;
8106                 }
8107
8108                 /* Next absolutely insufficient mating material. */
8109                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8110                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8111                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8112
8113                      /* always flag draws, for judging claims */
8114                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8115
8116                      if(canAdjudicate && appData.materialDraws) {
8117                          /* but only adjudicate them if adjudication enabled */
8118                          if(engineOpponent) {
8119                            SendToProgram("force\n", engineOpponent); // suppress reply
8120                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8121                          }
8122                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8123                          return 1;
8124                      }
8125                 }
8126
8127                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8128                 if(gameInfo.variant == VariantXiangqi ?
8129                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8130                  : nrW + nrB == 4 &&
8131                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8132                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8133                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8134                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8135                    ) ) {
8136                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8137                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8138                           if(engineOpponent) {
8139                             SendToProgram("force\n", engineOpponent); // suppress reply
8140                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8141                           }
8142                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8143                           return 1;
8144                      }
8145                 } else moveCount = 6;
8146             }
8147
8148         // Repetition draws and 50-move rule can be applied independently of legality testing
8149
8150                 /* Check for rep-draws */
8151                 count = 0;
8152                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8153                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8154                 for(k = forwardMostMove-2;
8155                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8156                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8157                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8158                     k-=2)
8159                 {   int rights=0;
8160                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8161                         /* compare castling rights */
8162                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8163                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8164                                 rights++; /* King lost rights, while rook still had them */
8165                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8166                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8167                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8168                                    rights++; /* but at least one rook lost them */
8169                         }
8170                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8171                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8172                                 rights++;
8173                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8174                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8175                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8176                                    rights++;
8177                         }
8178                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8179                             && appData.drawRepeats > 1) {
8180                              /* adjudicate after user-specified nr of repeats */
8181                              int result = GameIsDrawn;
8182                              char *details = "XBoard adjudication: repetition draw";
8183                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8184                                 // [HGM] xiangqi: check for forbidden perpetuals
8185                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8186                                 for(m=forwardMostMove; m>k; m-=2) {
8187                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8188                                         ourPerpetual = 0; // the current mover did not always check
8189                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8190                                         hisPerpetual = 0; // the opponent did not always check
8191                                 }
8192                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8193                                                                         ourPerpetual, hisPerpetual);
8194                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8195                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8196                                     details = "Xboard adjudication: perpetual checking";
8197                                 } else
8198                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8199                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8200                                 } else
8201                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8202                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8203                                         result = BlackWins;
8204                                         details = "Xboard adjudication: repetition";
8205                                     }
8206                                 } else // it must be XQ
8207                                 // Now check for perpetual chases
8208                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8209                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8210                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8211                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8212                                         static char resdet[MSG_SIZ];
8213                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8214                                         details = resdet;
8215                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8216                                     } else
8217                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8218                                         break; // Abort repetition-checking loop.
8219                                 }
8220                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8221                              }
8222                              if(engineOpponent) {
8223                                SendToProgram("force\n", engineOpponent); // suppress reply
8224                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8225                              }
8226                              GameEnds( result, details, GE_XBOARD );
8227                              return 1;
8228                         }
8229                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8230                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8231                     }
8232                 }
8233
8234                 /* Now we test for 50-move draws. Determine ply count */
8235                 count = forwardMostMove;
8236                 /* look for last irreversble move */
8237                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8238                     count--;
8239                 /* if we hit starting position, add initial plies */
8240                 if( count == backwardMostMove )
8241                     count -= initialRulePlies;
8242                 count = forwardMostMove - count;
8243                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8244                         // adjust reversible move counter for checks in Xiangqi
8245                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8246                         if(i < backwardMostMove) i = backwardMostMove;
8247                         while(i <= forwardMostMove) {
8248                                 lastCheck = inCheck; // check evasion does not count
8249                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8250                                 if(inCheck || lastCheck) count--; // check does not count
8251                                 i++;
8252                         }
8253                 }
8254                 if( count >= 100)
8255                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8256                          /* this is used to judge if draw claims are legal */
8257                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8258                          if(engineOpponent) {
8259                            SendToProgram("force\n", engineOpponent); // suppress reply
8260                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8261                          }
8262                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8263                          return 1;
8264                 }
8265
8266                 /* if draw offer is pending, treat it as a draw claim
8267                  * when draw condition present, to allow engines a way to
8268                  * claim draws before making their move to avoid a race
8269                  * condition occurring after their move
8270                  */
8271                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8272                          char *p = NULL;
8273                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8274                              p = "Draw claim: 50-move rule";
8275                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8276                              p = "Draw claim: 3-fold repetition";
8277                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8278                              p = "Draw claim: insufficient mating material";
8279                          if( p != NULL && canAdjudicate) {
8280                              if(engineOpponent) {
8281                                SendToProgram("force\n", engineOpponent); // suppress reply
8282                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8283                              }
8284                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8285                              return 1;
8286                          }
8287                 }
8288
8289                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8290                     if(engineOpponent) {
8291                       SendToProgram("force\n", engineOpponent); // suppress reply
8292                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8293                     }
8294                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8295                     return 1;
8296                 }
8297         return 0;
8298 }
8299
8300 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8301 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8302 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8303
8304 static int
8305 BitbaseProbe ()
8306 {
8307     int pieces[10], squares[10], cnt=0, r, f, res;
8308     static int loaded;
8309     static PPROBE_EGBB probeBB;
8310     if(!appData.testLegality) return 10;
8311     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8312     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8313     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8314     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8315         ChessSquare piece = boards[forwardMostMove][r][f];
8316         int black = (piece >= BlackPawn);
8317         int type = piece - black*BlackPawn;
8318         if(piece == EmptySquare) continue;
8319         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8320         if(type == WhiteKing) type = WhiteQueen + 1;
8321         type = egbbCode[type];
8322         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8323         pieces[cnt] = type + black*6;
8324         if(++cnt > 5) return 11;
8325     }
8326     pieces[cnt] = squares[cnt] = 0;
8327     // probe EGBB
8328     if(loaded == 2) return 13; // loading failed before
8329     if(loaded == 0) {
8330         loaded = 2; // prepare for failure
8331         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8332         HMODULE lib;
8333         PLOAD_EGBB loadBB;
8334         if(!path) return 13; // no egbb installed
8335         strncpy(buf, path + 8, MSG_SIZ);
8336         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8337         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8338         lib = LoadLibrary(buf);
8339         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8340         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8341         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8342         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8343         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8344         loaded = 1; // success!
8345     }
8346     res = probeBB(forwardMostMove & 1, pieces, squares);
8347     return res > 0 ? 1 : res < 0 ? -1 : 0;
8348 }
8349
8350 char *
8351 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8352 {   // [HGM] book: this routine intercepts moves to simulate book replies
8353     char *bookHit = NULL;
8354
8355     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8356         char buf[MSG_SIZ];
8357         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8358         SendToProgram(buf, cps);
8359     }
8360     //first determine if the incoming move brings opponent into his book
8361     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8362         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8363     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8364     if(bookHit != NULL && !cps->bookSuspend) {
8365         // make sure opponent is not going to reply after receiving move to book position
8366         SendToProgram("force\n", cps);
8367         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8368     }
8369     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8370     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8371     // now arrange restart after book miss
8372     if(bookHit) {
8373         // after a book hit we never send 'go', and the code after the call to this routine
8374         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8375         char buf[MSG_SIZ], *move = bookHit;
8376         if(cps->useSAN) {
8377             int fromX, fromY, toX, toY;
8378             char promoChar;
8379             ChessMove moveType;
8380             move = buf + 30;
8381             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8382                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8383                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8384                                     PosFlags(forwardMostMove),
8385                                     fromY, fromX, toY, toX, promoChar, move);
8386             } else {
8387                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8388                 bookHit = NULL;
8389             }
8390         }
8391         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8392         SendToProgram(buf, cps);
8393         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8394     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8395         SendToProgram("go\n", cps);
8396         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8397     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8398         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8399             SendToProgram("go\n", cps);
8400         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8401     }
8402     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8403 }
8404
8405 int
8406 LoadError (char *errmess, ChessProgramState *cps)
8407 {   // unloads engine and switches back to -ncp mode if it was first
8408     if(cps->initDone) return FALSE;
8409     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8410     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8411     cps->pr = NoProc;
8412     if(cps == &first) {
8413         appData.noChessProgram = TRUE;
8414         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8415         gameMode = BeginningOfGame; ModeHighlight();
8416         SetNCPMode();
8417     }
8418     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8419     DisplayMessage("", ""); // erase waiting message
8420     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8421     return TRUE;
8422 }
8423
8424 char *savedMessage;
8425 ChessProgramState *savedState;
8426 void
8427 DeferredBookMove (void)
8428 {
8429         if(savedState->lastPing != savedState->lastPong)
8430                     ScheduleDelayedEvent(DeferredBookMove, 10);
8431         else
8432         HandleMachineMove(savedMessage, savedState);
8433 }
8434
8435 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8436 static ChessProgramState *stalledEngine;
8437 static char stashedInputMove[MSG_SIZ];
8438
8439 void
8440 HandleMachineMove (char *message, ChessProgramState *cps)
8441 {
8442     static char firstLeg[20];
8443     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8444     char realname[MSG_SIZ];
8445     int fromX, fromY, toX, toY;
8446     ChessMove moveType;
8447     char promoChar, roar;
8448     char *p, *pv=buf1;
8449     int machineWhite, oldError;
8450     char *bookHit;
8451
8452     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8453         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8454         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8455             DisplayError(_("Invalid pairing from pairing engine"), 0);
8456             return;
8457         }
8458         pairingReceived = 1;
8459         NextMatchGame();
8460         return; // Skim the pairing messages here.
8461     }
8462
8463     oldError = cps->userError; cps->userError = 0;
8464
8465 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8466     /*
8467      * Kludge to ignore BEL characters
8468      */
8469     while (*message == '\007') message++;
8470
8471     /*
8472      * [HGM] engine debug message: ignore lines starting with '#' character
8473      */
8474     if(cps->debug && *message == '#') return;
8475
8476     /*
8477      * Look for book output
8478      */
8479     if (cps == &first && bookRequested) {
8480         if (message[0] == '\t' || message[0] == ' ') {
8481             /* Part of the book output is here; append it */
8482             strcat(bookOutput, message);
8483             strcat(bookOutput, "  \n");
8484             return;
8485         } else if (bookOutput[0] != NULLCHAR) {
8486             /* All of book output has arrived; display it */
8487             char *p = bookOutput;
8488             while (*p != NULLCHAR) {
8489                 if (*p == '\t') *p = ' ';
8490                 p++;
8491             }
8492             DisplayInformation(bookOutput);
8493             bookRequested = FALSE;
8494             /* Fall through to parse the current output */
8495         }
8496     }
8497
8498     /*
8499      * Look for machine move.
8500      */
8501     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8502         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8503     {
8504         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8505             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8506             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8507             stalledEngine = cps;
8508             if(appData.ponderNextMove) { // bring opponent out of ponder
8509                 if(gameMode == TwoMachinesPlay) {
8510                     if(cps->other->pause)
8511                         PauseEngine(cps->other);
8512                     else
8513                         SendToProgram("easy\n", cps->other);
8514                 }
8515             }
8516             StopClocks();
8517             return;
8518         }
8519
8520         /* This method is only useful on engines that support ping */
8521         if (cps->lastPing != cps->lastPong) {
8522           if (gameMode == BeginningOfGame) {
8523             /* Extra move from before last new; ignore */
8524             if (appData.debugMode) {
8525                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8526             }
8527           } else {
8528             if (appData.debugMode) {
8529                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8530                         cps->which, gameMode);
8531             }
8532
8533             SendToProgram("undo\n", cps);
8534           }
8535           return;
8536         }
8537
8538         switch (gameMode) {
8539           case BeginningOfGame:
8540             /* Extra move from before last reset; ignore */
8541             if (appData.debugMode) {
8542                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8543             }
8544             return;
8545
8546           case EndOfGame:
8547           case IcsIdle:
8548           default:
8549             /* Extra move after we tried to stop.  The mode test is
8550                not a reliable way of detecting this problem, but it's
8551                the best we can do on engines that don't support ping.
8552             */
8553             if (appData.debugMode) {
8554                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8555                         cps->which, gameMode);
8556             }
8557             SendToProgram("undo\n", cps);
8558             return;
8559
8560           case MachinePlaysWhite:
8561           case IcsPlayingWhite:
8562             machineWhite = TRUE;
8563             break;
8564
8565           case MachinePlaysBlack:
8566           case IcsPlayingBlack:
8567             machineWhite = FALSE;
8568             break;
8569
8570           case TwoMachinesPlay:
8571             machineWhite = (cps->twoMachinesColor[0] == 'w');
8572             break;
8573         }
8574         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8575             if (appData.debugMode) {
8576                 fprintf(debugFP,
8577                         "Ignoring move out of turn by %s, gameMode %d"
8578                         ", forwardMost %d\n",
8579                         cps->which, gameMode, forwardMostMove);
8580             }
8581             return;
8582         }
8583
8584         if(cps->alphaRank) AlphaRank(machineMove, 4);
8585
8586         // [HGM] lion: (some very limited) support for Alien protocol
8587         killX = killY = -1;
8588         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8589             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8590             return;
8591         } else if(firstLeg[0]) { // there was a previous leg;
8592             // only support case where same piece makes two step (and don't even test that!)
8593             char buf[20], *p = machineMove+1, *q = buf+1, f;
8594             safeStrCpy(buf, machineMove, 20);
8595             while(isdigit(*q)) q++; // find start of to-square
8596             safeStrCpy(machineMove, firstLeg, 20);
8597             while(isdigit(*p)) p++;
8598             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8599             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8600             firstLeg[0] = NULLCHAR;
8601         }
8602
8603         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8604                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8605             /* Machine move could not be parsed; ignore it. */
8606           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8607                     machineMove, _(cps->which));
8608             DisplayMoveError(buf1);
8609             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8610                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8611             if (gameMode == TwoMachinesPlay) {
8612               GameEnds(machineWhite ? BlackWins : WhiteWins,
8613                        buf1, GE_XBOARD);
8614             }
8615             return;
8616         }
8617
8618         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8619         /* So we have to redo legality test with true e.p. status here,  */
8620         /* to make sure an illegal e.p. capture does not slip through,   */
8621         /* to cause a forfeit on a justified illegal-move complaint      */
8622         /* of the opponent.                                              */
8623         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8624            ChessMove moveType;
8625            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8626                              fromY, fromX, toY, toX, promoChar);
8627             if(moveType == IllegalMove) {
8628               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8629                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8630                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8631                            buf1, GE_XBOARD);
8632                 return;
8633            } else if(!appData.fischerCastling)
8634            /* [HGM] Kludge to handle engines that send FRC-style castling
8635               when they shouldn't (like TSCP-Gothic) */
8636            switch(moveType) {
8637              case WhiteASideCastleFR:
8638              case BlackASideCastleFR:
8639                toX+=2;
8640                currentMoveString[2]++;
8641                break;
8642              case WhiteHSideCastleFR:
8643              case BlackHSideCastleFR:
8644                toX--;
8645                currentMoveString[2]--;
8646                break;
8647              default: ; // nothing to do, but suppresses warning of pedantic compilers
8648            }
8649         }
8650         hintRequested = FALSE;
8651         lastHint[0] = NULLCHAR;
8652         bookRequested = FALSE;
8653         /* Program may be pondering now */
8654         cps->maybeThinking = TRUE;
8655         if (cps->sendTime == 2) cps->sendTime = 1;
8656         if (cps->offeredDraw) cps->offeredDraw--;
8657
8658         /* [AS] Save move info*/
8659         pvInfoList[ forwardMostMove ].score = programStats.score;
8660         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8661         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8662
8663         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8664
8665         /* Test suites abort the 'game' after one move */
8666         if(*appData.finger) {
8667            static FILE *f;
8668            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8669            if(!f) f = fopen(appData.finger, "w");
8670            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8671            else { DisplayFatalError("Bad output file", errno, 0); return; }
8672            free(fen);
8673            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8674         }
8675
8676         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8677         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8678             int count = 0;
8679
8680             while( count < adjudicateLossPlies ) {
8681                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8682
8683                 if( count & 1 ) {
8684                     score = -score; /* Flip score for winning side */
8685                 }
8686
8687                 if( score > adjudicateLossThreshold ) {
8688                     break;
8689                 }
8690
8691                 count++;
8692             }
8693
8694             if( count >= adjudicateLossPlies ) {
8695                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8696
8697                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8698                     "Xboard adjudication",
8699                     GE_XBOARD );
8700
8701                 return;
8702             }
8703         }
8704
8705         if(Adjudicate(cps)) {
8706             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8707             return; // [HGM] adjudicate: for all automatic game ends
8708         }
8709
8710 #if ZIPPY
8711         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8712             first.initDone) {
8713           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8714                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8715                 SendToICS("draw ");
8716                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8717           }
8718           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8719           ics_user_moved = 1;
8720           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8721                 char buf[3*MSG_SIZ];
8722
8723                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8724                         programStats.score / 100.,
8725                         programStats.depth,
8726                         programStats.time / 100.,
8727                         (unsigned int)programStats.nodes,
8728                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8729                         programStats.movelist);
8730                 SendToICS(buf);
8731 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8732           }
8733         }
8734 #endif
8735
8736         /* [AS] Clear stats for next move */
8737         ClearProgramStats();
8738         thinkOutput[0] = NULLCHAR;
8739         hiddenThinkOutputState = 0;
8740
8741         bookHit = NULL;
8742         if (gameMode == TwoMachinesPlay) {
8743             /* [HGM] relaying draw offers moved to after reception of move */
8744             /* and interpreting offer as claim if it brings draw condition */
8745             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8746                 SendToProgram("draw\n", cps->other);
8747             }
8748             if (cps->other->sendTime) {
8749                 SendTimeRemaining(cps->other,
8750                                   cps->other->twoMachinesColor[0] == 'w');
8751             }
8752             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8753             if (firstMove && !bookHit) {
8754                 firstMove = FALSE;
8755                 if (cps->other->useColors) {
8756                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8757                 }
8758                 SendToProgram("go\n", cps->other);
8759             }
8760             cps->other->maybeThinking = TRUE;
8761         }
8762
8763         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8764
8765         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8766
8767         if (!pausing && appData.ringBellAfterMoves) {
8768             if(!roar) RingBell();
8769         }
8770
8771         /*
8772          * Reenable menu items that were disabled while
8773          * machine was thinking
8774          */
8775         if (gameMode != TwoMachinesPlay)
8776             SetUserThinkingEnables();
8777
8778         // [HGM] book: after book hit opponent has received move and is now in force mode
8779         // force the book reply into it, and then fake that it outputted this move by jumping
8780         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8781         if(bookHit) {
8782                 static char bookMove[MSG_SIZ]; // a bit generous?
8783
8784                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8785                 strcat(bookMove, bookHit);
8786                 message = bookMove;
8787                 cps = cps->other;
8788                 programStats.nodes = programStats.depth = programStats.time =
8789                 programStats.score = programStats.got_only_move = 0;
8790                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8791
8792                 if(cps->lastPing != cps->lastPong) {
8793                     savedMessage = message; // args for deferred call
8794                     savedState = cps;
8795                     ScheduleDelayedEvent(DeferredBookMove, 10);
8796                     return;
8797                 }
8798                 goto FakeBookMove;
8799         }
8800
8801         return;
8802     }
8803
8804     /* Set special modes for chess engines.  Later something general
8805      *  could be added here; for now there is just one kludge feature,
8806      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8807      *  when "xboard" is given as an interactive command.
8808      */
8809     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8810         cps->useSigint = FALSE;
8811         cps->useSigterm = FALSE;
8812     }
8813     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8814       ParseFeatures(message+8, cps);
8815       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8816     }
8817
8818     if (!strncmp(message, "setup ", 6) && 
8819         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8820           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8821                                         ) { // [HGM] allow first engine to define opening position
8822       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8823       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8824       *buf = NULLCHAR;
8825       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8826       if(startedFromSetupPosition) return;
8827       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8828       if(dummy >= 3) {
8829         while(message[s] && message[s++] != ' ');
8830         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8831            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8832             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8833             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8834           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8835           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8836         }
8837       }
8838       ParseFEN(boards[0], &dummy, message+s, FALSE);
8839       DrawPosition(TRUE, boards[0]);
8840       startedFromSetupPosition = TRUE;
8841       return;
8842     }
8843     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8844      * want this, I was asked to put it in, and obliged.
8845      */
8846     if (!strncmp(message, "setboard ", 9)) {
8847         Board initial_position;
8848
8849         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8850
8851         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8852             DisplayError(_("Bad FEN received from engine"), 0);
8853             return ;
8854         } else {
8855            Reset(TRUE, FALSE);
8856            CopyBoard(boards[0], initial_position);
8857            initialRulePlies = FENrulePlies;
8858            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8859            else gameMode = MachinePlaysBlack;
8860            DrawPosition(FALSE, boards[currentMove]);
8861         }
8862         return;
8863     }
8864
8865     /*
8866      * Look for communication commands
8867      */
8868     if (!strncmp(message, "telluser ", 9)) {
8869         if(message[9] == '\\' && message[10] == '\\')
8870             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8871         PlayTellSound();
8872         DisplayNote(message + 9);
8873         return;
8874     }
8875     if (!strncmp(message, "tellusererror ", 14)) {
8876         cps->userError = 1;
8877         if(message[14] == '\\' && message[15] == '\\')
8878             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8879         PlayTellSound();
8880         DisplayError(message + 14, 0);
8881         return;
8882     }
8883     if (!strncmp(message, "tellopponent ", 13)) {
8884       if (appData.icsActive) {
8885         if (loggedOn) {
8886           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8887           SendToICS(buf1);
8888         }
8889       } else {
8890         DisplayNote(message + 13);
8891       }
8892       return;
8893     }
8894     if (!strncmp(message, "tellothers ", 11)) {
8895       if (appData.icsActive) {
8896         if (loggedOn) {
8897           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8898           SendToICS(buf1);
8899         }
8900       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8901       return;
8902     }
8903     if (!strncmp(message, "tellall ", 8)) {
8904       if (appData.icsActive) {
8905         if (loggedOn) {
8906           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8907           SendToICS(buf1);
8908         }
8909       } else {
8910         DisplayNote(message + 8);
8911       }
8912       return;
8913     }
8914     if (strncmp(message, "warning", 7) == 0) {
8915         /* Undocumented feature, use tellusererror in new code */
8916         DisplayError(message, 0);
8917         return;
8918     }
8919     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8920         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8921         strcat(realname, " query");
8922         AskQuestion(realname, buf2, buf1, cps->pr);
8923         return;
8924     }
8925     /* Commands from the engine directly to ICS.  We don't allow these to be
8926      *  sent until we are logged on. Crafty kibitzes have been known to
8927      *  interfere with the login process.
8928      */
8929     if (loggedOn) {
8930         if (!strncmp(message, "tellics ", 8)) {
8931             SendToICS(message + 8);
8932             SendToICS("\n");
8933             return;
8934         }
8935         if (!strncmp(message, "tellicsnoalias ", 15)) {
8936             SendToICS(ics_prefix);
8937             SendToICS(message + 15);
8938             SendToICS("\n");
8939             return;
8940         }
8941         /* The following are for backward compatibility only */
8942         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8943             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8944             SendToICS(ics_prefix);
8945             SendToICS(message);
8946             SendToICS("\n");
8947             return;
8948         }
8949     }
8950     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8951         if(initPing == cps->lastPong) {
8952             if(gameInfo.variant == VariantUnknown) {
8953                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8954                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8955                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8956             }
8957             initPing = -1;
8958         }
8959         return;
8960     }
8961     if(!strncmp(message, "highlight ", 10)) {
8962         if(appData.testLegality && appData.markers) return;
8963         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8964         return;
8965     }
8966     if(!strncmp(message, "click ", 6)) {
8967         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8968         if(appData.testLegality || !appData.oneClick) return;
8969         sscanf(message+6, "%c%d%c", &f, &y, &c);
8970         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8971         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8972         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8973         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8974         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8975         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8976             LeftClick(Release, lastLeftX, lastLeftY);
8977         controlKey  = (c == ',');
8978         LeftClick(Press, x, y);
8979         LeftClick(Release, x, y);
8980         first.highlight = f;
8981         return;
8982     }
8983     /*
8984      * If the move is illegal, cancel it and redraw the board.
8985      * Also deal with other error cases.  Matching is rather loose
8986      * here to accommodate engines written before the spec.
8987      */
8988     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8989         strncmp(message, "Error", 5) == 0) {
8990         if (StrStr(message, "name") ||
8991             StrStr(message, "rating") || StrStr(message, "?") ||
8992             StrStr(message, "result") || StrStr(message, "board") ||
8993             StrStr(message, "bk") || StrStr(message, "computer") ||
8994             StrStr(message, "variant") || StrStr(message, "hint") ||
8995             StrStr(message, "random") || StrStr(message, "depth") ||
8996             StrStr(message, "accepted")) {
8997             return;
8998         }
8999         if (StrStr(message, "protover")) {
9000           /* Program is responding to input, so it's apparently done
9001              initializing, and this error message indicates it is
9002              protocol version 1.  So we don't need to wait any longer
9003              for it to initialize and send feature commands. */
9004           FeatureDone(cps, 1);
9005           cps->protocolVersion = 1;
9006           return;
9007         }
9008         cps->maybeThinking = FALSE;
9009
9010         if (StrStr(message, "draw")) {
9011             /* Program doesn't have "draw" command */
9012             cps->sendDrawOffers = 0;
9013             return;
9014         }
9015         if (cps->sendTime != 1 &&
9016             (StrStr(message, "time") || StrStr(message, "otim"))) {
9017           /* Program apparently doesn't have "time" or "otim" command */
9018           cps->sendTime = 0;
9019           return;
9020         }
9021         if (StrStr(message, "analyze")) {
9022             cps->analysisSupport = FALSE;
9023             cps->analyzing = FALSE;
9024 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9025             EditGameEvent(); // [HGM] try to preserve loaded game
9026             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9027             DisplayError(buf2, 0);
9028             return;
9029         }
9030         if (StrStr(message, "(no matching move)st")) {
9031           /* Special kludge for GNU Chess 4 only */
9032           cps->stKludge = TRUE;
9033           SendTimeControl(cps, movesPerSession, timeControl,
9034                           timeIncrement, appData.searchDepth,
9035                           searchTime);
9036           return;
9037         }
9038         if (StrStr(message, "(no matching move)sd")) {
9039           /* Special kludge for GNU Chess 4 only */
9040           cps->sdKludge = TRUE;
9041           SendTimeControl(cps, movesPerSession, timeControl,
9042                           timeIncrement, appData.searchDepth,
9043                           searchTime);
9044           return;
9045         }
9046         if (!StrStr(message, "llegal")) {
9047             return;
9048         }
9049         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9050             gameMode == IcsIdle) return;
9051         if (forwardMostMove <= backwardMostMove) return;
9052         if (pausing) PauseEvent();
9053       if(appData.forceIllegal) {
9054             // [HGM] illegal: machine refused move; force position after move into it
9055           SendToProgram("force\n", cps);
9056           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9057                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9058                 // when black is to move, while there might be nothing on a2 or black
9059                 // might already have the move. So send the board as if white has the move.
9060                 // But first we must change the stm of the engine, as it refused the last move
9061                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9062                 if(WhiteOnMove(forwardMostMove)) {
9063                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9064                     SendBoard(cps, forwardMostMove); // kludgeless board
9065                 } else {
9066                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9067                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9068                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9069                 }
9070           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9071             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9072                  gameMode == TwoMachinesPlay)
9073               SendToProgram("go\n", cps);
9074             return;
9075       } else
9076         if (gameMode == PlayFromGameFile) {
9077             /* Stop reading this game file */
9078             gameMode = EditGame;
9079             ModeHighlight();
9080         }
9081         /* [HGM] illegal-move claim should forfeit game when Xboard */
9082         /* only passes fully legal moves                            */
9083         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9084             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9085                                 "False illegal-move claim", GE_XBOARD );
9086             return; // do not take back move we tested as valid
9087         }
9088         currentMove = forwardMostMove-1;
9089         DisplayMove(currentMove-1); /* before DisplayMoveError */
9090         SwitchClocks(forwardMostMove-1); // [HGM] race
9091         DisplayBothClocks();
9092         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9093                 parseList[currentMove], _(cps->which));
9094         DisplayMoveError(buf1);
9095         DrawPosition(FALSE, boards[currentMove]);
9096
9097         SetUserThinkingEnables();
9098         return;
9099     }
9100     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9101         /* Program has a broken "time" command that
9102            outputs a string not ending in newline.
9103            Don't use it. */
9104         cps->sendTime = 0;
9105     }
9106
9107     /*
9108      * If chess program startup fails, exit with an error message.
9109      * Attempts to recover here are futile. [HGM] Well, we try anyway
9110      */
9111     if ((StrStr(message, "unknown host") != NULL)
9112         || (StrStr(message, "No remote directory") != NULL)
9113         || (StrStr(message, "not found") != NULL)
9114         || (StrStr(message, "No such file") != NULL)
9115         || (StrStr(message, "can't alloc") != NULL)
9116         || (StrStr(message, "Permission denied") != NULL)) {
9117
9118         cps->maybeThinking = FALSE;
9119         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9120                 _(cps->which), cps->program, cps->host, message);
9121         RemoveInputSource(cps->isr);
9122         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9123             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9124             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9125         }
9126         return;
9127     }
9128
9129     /*
9130      * Look for hint output
9131      */
9132     if (sscanf(message, "Hint: %s", buf1) == 1) {
9133         if (cps == &first && hintRequested) {
9134             hintRequested = FALSE;
9135             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9136                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9137                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9138                                     PosFlags(forwardMostMove),
9139                                     fromY, fromX, toY, toX, promoChar, buf1);
9140                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9141                 DisplayInformation(buf2);
9142             } else {
9143                 /* Hint move could not be parsed!? */
9144               snprintf(buf2, sizeof(buf2),
9145                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9146                         buf1, _(cps->which));
9147                 DisplayError(buf2, 0);
9148             }
9149         } else {
9150           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9151         }
9152         return;
9153     }
9154
9155     /*
9156      * Ignore other messages if game is not in progress
9157      */
9158     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9159         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9160
9161     /*
9162      * look for win, lose, draw, or draw offer
9163      */
9164     if (strncmp(message, "1-0", 3) == 0) {
9165         char *p, *q, *r = "";
9166         p = strchr(message, '{');
9167         if (p) {
9168             q = strchr(p, '}');
9169             if (q) {
9170                 *q = NULLCHAR;
9171                 r = p + 1;
9172             }
9173         }
9174         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9175         return;
9176     } else if (strncmp(message, "0-1", 3) == 0) {
9177         char *p, *q, *r = "";
9178         p = strchr(message, '{');
9179         if (p) {
9180             q = strchr(p, '}');
9181             if (q) {
9182                 *q = NULLCHAR;
9183                 r = p + 1;
9184             }
9185         }
9186         /* Kludge for Arasan 4.1 bug */
9187         if (strcmp(r, "Black resigns") == 0) {
9188             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9189             return;
9190         }
9191         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9192         return;
9193     } else if (strncmp(message, "1/2", 3) == 0) {
9194         char *p, *q, *r = "";
9195         p = strchr(message, '{');
9196         if (p) {
9197             q = strchr(p, '}');
9198             if (q) {
9199                 *q = NULLCHAR;
9200                 r = p + 1;
9201             }
9202         }
9203
9204         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9205         return;
9206
9207     } else if (strncmp(message, "White resign", 12) == 0) {
9208         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9209         return;
9210     } else if (strncmp(message, "Black resign", 12) == 0) {
9211         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9212         return;
9213     } else if (strncmp(message, "White matches", 13) == 0 ||
9214                strncmp(message, "Black matches", 13) == 0   ) {
9215         /* [HGM] ignore GNUShogi noises */
9216         return;
9217     } else if (strncmp(message, "White", 5) == 0 &&
9218                message[5] != '(' &&
9219                StrStr(message, "Black") == NULL) {
9220         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9221         return;
9222     } else if (strncmp(message, "Black", 5) == 0 &&
9223                message[5] != '(') {
9224         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9225         return;
9226     } else if (strcmp(message, "resign") == 0 ||
9227                strcmp(message, "computer resigns") == 0) {
9228         switch (gameMode) {
9229           case MachinePlaysBlack:
9230           case IcsPlayingBlack:
9231             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9232             break;
9233           case MachinePlaysWhite:
9234           case IcsPlayingWhite:
9235             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9236             break;
9237           case TwoMachinesPlay:
9238             if (cps->twoMachinesColor[0] == 'w')
9239               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9240             else
9241               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9242             break;
9243           default:
9244             /* can't happen */
9245             break;
9246         }
9247         return;
9248     } else if (strncmp(message, "opponent mates", 14) == 0) {
9249         switch (gameMode) {
9250           case MachinePlaysBlack:
9251           case IcsPlayingBlack:
9252             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9253             break;
9254           case MachinePlaysWhite:
9255           case IcsPlayingWhite:
9256             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9257             break;
9258           case TwoMachinesPlay:
9259             if (cps->twoMachinesColor[0] == 'w')
9260               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9261             else
9262               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9263             break;
9264           default:
9265             /* can't happen */
9266             break;
9267         }
9268         return;
9269     } else if (strncmp(message, "computer mates", 14) == 0) {
9270         switch (gameMode) {
9271           case MachinePlaysBlack:
9272           case IcsPlayingBlack:
9273             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9274             break;
9275           case MachinePlaysWhite:
9276           case IcsPlayingWhite:
9277             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9278             break;
9279           case TwoMachinesPlay:
9280             if (cps->twoMachinesColor[0] == 'w')
9281               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9282             else
9283               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9284             break;
9285           default:
9286             /* can't happen */
9287             break;
9288         }
9289         return;
9290     } else if (strncmp(message, "checkmate", 9) == 0) {
9291         if (WhiteOnMove(forwardMostMove)) {
9292             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9293         } else {
9294             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9295         }
9296         return;
9297     } else if (strstr(message, "Draw") != NULL ||
9298                strstr(message, "game is a draw") != NULL) {
9299         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9300         return;
9301     } else if (strstr(message, "offer") != NULL &&
9302                strstr(message, "draw") != NULL) {
9303 #if ZIPPY
9304         if (appData.zippyPlay && first.initDone) {
9305             /* Relay offer to ICS */
9306             SendToICS(ics_prefix);
9307             SendToICS("draw\n");
9308         }
9309 #endif
9310         cps->offeredDraw = 2; /* valid until this engine moves twice */
9311         if (gameMode == TwoMachinesPlay) {
9312             if (cps->other->offeredDraw) {
9313                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9314             /* [HGM] in two-machine mode we delay relaying draw offer      */
9315             /* until after we also have move, to see if it is really claim */
9316             }
9317         } else if (gameMode == MachinePlaysWhite ||
9318                    gameMode == MachinePlaysBlack) {
9319           if (userOfferedDraw) {
9320             DisplayInformation(_("Machine accepts your draw offer"));
9321             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9322           } else {
9323             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9324           }
9325         }
9326     }
9327
9328
9329     /*
9330      * Look for thinking output
9331      */
9332     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9333           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9334                                 ) {
9335         int plylev, mvleft, mvtot, curscore, time;
9336         char mvname[MOVE_LEN];
9337         u64 nodes; // [DM]
9338         char plyext;
9339         int ignore = FALSE;
9340         int prefixHint = FALSE;
9341         mvname[0] = NULLCHAR;
9342
9343         switch (gameMode) {
9344           case MachinePlaysBlack:
9345           case IcsPlayingBlack:
9346             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9347             break;
9348           case MachinePlaysWhite:
9349           case IcsPlayingWhite:
9350             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9351             break;
9352           case AnalyzeMode:
9353           case AnalyzeFile:
9354             break;
9355           case IcsObserving: /* [DM] icsEngineAnalyze */
9356             if (!appData.icsEngineAnalyze) ignore = TRUE;
9357             break;
9358           case TwoMachinesPlay:
9359             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9360                 ignore = TRUE;
9361             }
9362             break;
9363           default:
9364             ignore = TRUE;
9365             break;
9366         }
9367
9368         if (!ignore) {
9369             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9370             buf1[0] = NULLCHAR;
9371             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9372                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9373
9374                 if (plyext != ' ' && plyext != '\t') {
9375                     time *= 100;
9376                 }
9377
9378                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9379                 if( cps->scoreIsAbsolute &&
9380                     ( gameMode == MachinePlaysBlack ||
9381                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9382                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9383                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9384                      !WhiteOnMove(currentMove)
9385                     ) )
9386                 {
9387                     curscore = -curscore;
9388                 }
9389
9390                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9391
9392                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9393                         char buf[MSG_SIZ];
9394                         FILE *f;
9395                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9396                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9397                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9398                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9399                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9400                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9401                                 fclose(f);
9402                         }
9403                         else
9404                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9405                           DisplayError(_("failed writing PV"), 0);
9406                 }
9407
9408                 tempStats.depth = plylev;
9409                 tempStats.nodes = nodes;
9410                 tempStats.time = time;
9411                 tempStats.score = curscore;
9412                 tempStats.got_only_move = 0;
9413
9414                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9415                         int ticklen;
9416
9417                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9418                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9419                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9420                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9421                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9422                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9423                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9424                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9425                 }
9426
9427                 /* Buffer overflow protection */
9428                 if (pv[0] != NULLCHAR) {
9429                     if (strlen(pv) >= sizeof(tempStats.movelist)
9430                         && appData.debugMode) {
9431                         fprintf(debugFP,
9432                                 "PV is too long; using the first %u bytes.\n",
9433                                 (unsigned) sizeof(tempStats.movelist) - 1);
9434                     }
9435
9436                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9437                 } else {
9438                     sprintf(tempStats.movelist, " no PV\n");
9439                 }
9440
9441                 if (tempStats.seen_stat) {
9442                     tempStats.ok_to_send = 1;
9443                 }
9444
9445                 if (strchr(tempStats.movelist, '(') != NULL) {
9446                     tempStats.line_is_book = 1;
9447                     tempStats.nr_moves = 0;
9448                     tempStats.moves_left = 0;
9449                 } else {
9450                     tempStats.line_is_book = 0;
9451                 }
9452
9453                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9454                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9455
9456                 SendProgramStatsToFrontend( cps, &tempStats );
9457
9458                 /*
9459                     [AS] Protect the thinkOutput buffer from overflow... this
9460                     is only useful if buf1 hasn't overflowed first!
9461                 */
9462                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9463                          plylev,
9464                          (gameMode == TwoMachinesPlay ?
9465                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9466                          ((double) curscore) / 100.0,
9467                          prefixHint ? lastHint : "",
9468                          prefixHint ? " " : "" );
9469
9470                 if( buf1[0] != NULLCHAR ) {
9471                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9472
9473                     if( strlen(pv) > max_len ) {
9474                         if( appData.debugMode) {
9475                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9476                         }
9477                         pv[max_len+1] = '\0';
9478                     }
9479
9480                     strcat( thinkOutput, pv);
9481                 }
9482
9483                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9484                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9485                     DisplayMove(currentMove - 1);
9486                 }
9487                 return;
9488
9489             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9490                 /* crafty (9.25+) says "(only move) <move>"
9491                  * if there is only 1 legal move
9492                  */
9493                 sscanf(p, "(only move) %s", buf1);
9494                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9495                 sprintf(programStats.movelist, "%s (only move)", buf1);
9496                 programStats.depth = 1;
9497                 programStats.nr_moves = 1;
9498                 programStats.moves_left = 1;
9499                 programStats.nodes = 1;
9500                 programStats.time = 1;
9501                 programStats.got_only_move = 1;
9502
9503                 /* Not really, but we also use this member to
9504                    mean "line isn't going to change" (Crafty
9505                    isn't searching, so stats won't change) */
9506                 programStats.line_is_book = 1;
9507
9508                 SendProgramStatsToFrontend( cps, &programStats );
9509
9510                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9511                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9512                     DisplayMove(currentMove - 1);
9513                 }
9514                 return;
9515             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9516                               &time, &nodes, &plylev, &mvleft,
9517                               &mvtot, mvname) >= 5) {
9518                 /* The stat01: line is from Crafty (9.29+) in response
9519                    to the "." command */
9520                 programStats.seen_stat = 1;
9521                 cps->maybeThinking = TRUE;
9522
9523                 if (programStats.got_only_move || !appData.periodicUpdates)
9524                   return;
9525
9526                 programStats.depth = plylev;
9527                 programStats.time = time;
9528                 programStats.nodes = nodes;
9529                 programStats.moves_left = mvleft;
9530                 programStats.nr_moves = mvtot;
9531                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9532                 programStats.ok_to_send = 1;
9533                 programStats.movelist[0] = '\0';
9534
9535                 SendProgramStatsToFrontend( cps, &programStats );
9536
9537                 return;
9538
9539             } else if (strncmp(message,"++",2) == 0) {
9540                 /* Crafty 9.29+ outputs this */
9541                 programStats.got_fail = 2;
9542                 return;
9543
9544             } else if (strncmp(message,"--",2) == 0) {
9545                 /* Crafty 9.29+ outputs this */
9546                 programStats.got_fail = 1;
9547                 return;
9548
9549             } else if (thinkOutput[0] != NULLCHAR &&
9550                        strncmp(message, "    ", 4) == 0) {
9551                 unsigned message_len;
9552
9553                 p = message;
9554                 while (*p && *p == ' ') p++;
9555
9556                 message_len = strlen( p );
9557
9558                 /* [AS] Avoid buffer overflow */
9559                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9560                     strcat(thinkOutput, " ");
9561                     strcat(thinkOutput, p);
9562                 }
9563
9564                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9565                     strcat(programStats.movelist, " ");
9566                     strcat(programStats.movelist, p);
9567                 }
9568
9569                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9570                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9571                     DisplayMove(currentMove - 1);
9572                 }
9573                 return;
9574             }
9575         }
9576         else {
9577             buf1[0] = NULLCHAR;
9578
9579             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9580                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9581             {
9582                 ChessProgramStats cpstats;
9583
9584                 if (plyext != ' ' && plyext != '\t') {
9585                     time *= 100;
9586                 }
9587
9588                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9589                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9590                     curscore = -curscore;
9591                 }
9592
9593                 cpstats.depth = plylev;
9594                 cpstats.nodes = nodes;
9595                 cpstats.time = time;
9596                 cpstats.score = curscore;
9597                 cpstats.got_only_move = 0;
9598                 cpstats.movelist[0] = '\0';
9599
9600                 if (buf1[0] != NULLCHAR) {
9601                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9602                 }
9603
9604                 cpstats.ok_to_send = 0;
9605                 cpstats.line_is_book = 0;
9606                 cpstats.nr_moves = 0;
9607                 cpstats.moves_left = 0;
9608
9609                 SendProgramStatsToFrontend( cps, &cpstats );
9610             }
9611         }
9612     }
9613 }
9614
9615
9616 /* Parse a game score from the character string "game", and
9617    record it as the history of the current game.  The game
9618    score is NOT assumed to start from the standard position.
9619    The display is not updated in any way.
9620    */
9621 void
9622 ParseGameHistory (char *game)
9623 {
9624     ChessMove moveType;
9625     int fromX, fromY, toX, toY, boardIndex;
9626     char promoChar;
9627     char *p, *q;
9628     char buf[MSG_SIZ];
9629
9630     if (appData.debugMode)
9631       fprintf(debugFP, "Parsing game history: %s\n", game);
9632
9633     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9634     gameInfo.site = StrSave(appData.icsHost);
9635     gameInfo.date = PGNDate();
9636     gameInfo.round = StrSave("-");
9637
9638     /* Parse out names of players */
9639     while (*game == ' ') game++;
9640     p = buf;
9641     while (*game != ' ') *p++ = *game++;
9642     *p = NULLCHAR;
9643     gameInfo.white = StrSave(buf);
9644     while (*game == ' ') game++;
9645     p = buf;
9646     while (*game != ' ' && *game != '\n') *p++ = *game++;
9647     *p = NULLCHAR;
9648     gameInfo.black = StrSave(buf);
9649
9650     /* Parse moves */
9651     boardIndex = blackPlaysFirst ? 1 : 0;
9652     yynewstr(game);
9653     for (;;) {
9654         yyboardindex = boardIndex;
9655         moveType = (ChessMove) Myylex();
9656         switch (moveType) {
9657           case IllegalMove:             /* maybe suicide chess, etc. */
9658   if (appData.debugMode) {
9659     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9660     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9661     setbuf(debugFP, NULL);
9662   }
9663           case WhitePromotion:
9664           case BlackPromotion:
9665           case WhiteNonPromotion:
9666           case BlackNonPromotion:
9667           case NormalMove:
9668           case FirstLeg:
9669           case WhiteCapturesEnPassant:
9670           case BlackCapturesEnPassant:
9671           case WhiteKingSideCastle:
9672           case WhiteQueenSideCastle:
9673           case BlackKingSideCastle:
9674           case BlackQueenSideCastle:
9675           case WhiteKingSideCastleWild:
9676           case WhiteQueenSideCastleWild:
9677           case BlackKingSideCastleWild:
9678           case BlackQueenSideCastleWild:
9679           /* PUSH Fabien */
9680           case WhiteHSideCastleFR:
9681           case WhiteASideCastleFR:
9682           case BlackHSideCastleFR:
9683           case BlackASideCastleFR:
9684           /* POP Fabien */
9685             fromX = currentMoveString[0] - AAA;
9686             fromY = currentMoveString[1] - ONE;
9687             toX = currentMoveString[2] - AAA;
9688             toY = currentMoveString[3] - ONE;
9689             promoChar = currentMoveString[4];
9690             break;
9691           case WhiteDrop:
9692           case BlackDrop:
9693             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9694             fromX = moveType == WhiteDrop ?
9695               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9696             (int) CharToPiece(ToLower(currentMoveString[0]));
9697             fromY = DROP_RANK;
9698             toX = currentMoveString[2] - AAA;
9699             toY = currentMoveString[3] - ONE;
9700             promoChar = NULLCHAR;
9701             break;
9702           case AmbiguousMove:
9703             /* bug? */
9704             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9705   if (appData.debugMode) {
9706     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9707     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9708     setbuf(debugFP, NULL);
9709   }
9710             DisplayError(buf, 0);
9711             return;
9712           case ImpossibleMove:
9713             /* bug? */
9714             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9715   if (appData.debugMode) {
9716     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9717     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9718     setbuf(debugFP, NULL);
9719   }
9720             DisplayError(buf, 0);
9721             return;
9722           case EndOfFile:
9723             if (boardIndex < backwardMostMove) {
9724                 /* Oops, gap.  How did that happen? */
9725                 DisplayError(_("Gap in move list"), 0);
9726                 return;
9727             }
9728             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9729             if (boardIndex > forwardMostMove) {
9730                 forwardMostMove = boardIndex;
9731             }
9732             return;
9733           case ElapsedTime:
9734             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9735                 strcat(parseList[boardIndex-1], " ");
9736                 strcat(parseList[boardIndex-1], yy_text);
9737             }
9738             continue;
9739           case Comment:
9740           case PGNTag:
9741           case NAG:
9742           default:
9743             /* ignore */
9744             continue;
9745           case WhiteWins:
9746           case BlackWins:
9747           case GameIsDrawn:
9748           case GameUnfinished:
9749             if (gameMode == IcsExamining) {
9750                 if (boardIndex < backwardMostMove) {
9751                     /* Oops, gap.  How did that happen? */
9752                     return;
9753                 }
9754                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9755                 return;
9756             }
9757             gameInfo.result = moveType;
9758             p = strchr(yy_text, '{');
9759             if (p == NULL) p = strchr(yy_text, '(');
9760             if (p == NULL) {
9761                 p = yy_text;
9762                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9763             } else {
9764                 q = strchr(p, *p == '{' ? '}' : ')');
9765                 if (q != NULL) *q = NULLCHAR;
9766                 p++;
9767             }
9768             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9769             gameInfo.resultDetails = StrSave(p);
9770             continue;
9771         }
9772         if (boardIndex >= forwardMostMove &&
9773             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9774             backwardMostMove = blackPlaysFirst ? 1 : 0;
9775             return;
9776         }
9777         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9778                                  fromY, fromX, toY, toX, promoChar,
9779                                  parseList[boardIndex]);
9780         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9781         /* currentMoveString is set as a side-effect of yylex */
9782         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9783         strcat(moveList[boardIndex], "\n");
9784         boardIndex++;
9785         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9786         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9787           case MT_NONE:
9788           case MT_STALEMATE:
9789           default:
9790             break;
9791           case MT_CHECK:
9792             if(!IS_SHOGI(gameInfo.variant))
9793                 strcat(parseList[boardIndex - 1], "+");
9794             break;
9795           case MT_CHECKMATE:
9796           case MT_STAINMATE:
9797             strcat(parseList[boardIndex - 1], "#");
9798             break;
9799         }
9800     }
9801 }
9802
9803
9804 /* Apply a move to the given board  */
9805 void
9806 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9807 {
9808   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9809   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9810
9811     /* [HGM] compute & store e.p. status and castling rights for new position */
9812     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9813
9814       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9815       oldEP = (signed char)board[EP_STATUS];
9816       board[EP_STATUS] = EP_NONE;
9817
9818   if (fromY == DROP_RANK) {
9819         /* must be first */
9820         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9821             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9822             return;
9823         }
9824         piece = board[toY][toX] = (ChessSquare) fromX;
9825   } else {
9826 //      ChessSquare victim;
9827       int i;
9828
9829       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9830 //           victim = board[killY][killX],
9831            board[killY][killX] = EmptySquare,
9832            board[EP_STATUS] = EP_CAPTURE;
9833
9834       if( board[toY][toX] != EmptySquare ) {
9835            board[EP_STATUS] = EP_CAPTURE;
9836            if( (fromX != toX || fromY != toY) && // not igui!
9837                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9838                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9839                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9840            }
9841       }
9842
9843       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9844            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9845                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9846       } else
9847       if( board[fromY][fromX] == WhitePawn ) {
9848            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9849                board[EP_STATUS] = EP_PAWN_MOVE;
9850            if( toY-fromY==2) {
9851                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9852                         gameInfo.variant != VariantBerolina || toX < fromX)
9853                       board[EP_STATUS] = toX | berolina;
9854                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9855                         gameInfo.variant != VariantBerolina || toX > fromX)
9856                       board[EP_STATUS] = toX;
9857            }
9858       } else
9859       if( board[fromY][fromX] == BlackPawn ) {
9860            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9861                board[EP_STATUS] = EP_PAWN_MOVE;
9862            if( toY-fromY== -2) {
9863                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9864                         gameInfo.variant != VariantBerolina || toX < fromX)
9865                       board[EP_STATUS] = toX | berolina;
9866                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9867                         gameInfo.variant != VariantBerolina || toX > fromX)
9868                       board[EP_STATUS] = toX;
9869            }
9870        }
9871
9872        for(i=0; i<nrCastlingRights; i++) {
9873            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9874               board[CASTLING][i] == toX   && castlingRank[i] == toY
9875              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9876        }
9877
9878        if(gameInfo.variant == VariantSChess) { // update virginity
9879            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9880            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9881            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9882            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9883        }
9884
9885      if (fromX == toX && fromY == toY) return;
9886
9887      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9888      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9889      if(gameInfo.variant == VariantKnightmate)
9890          king += (int) WhiteUnicorn - (int) WhiteKing;
9891
9892     /* Code added by Tord: */
9893     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9894     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9895         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9896       board[fromY][fromX] = EmptySquare;
9897       board[toY][toX] = EmptySquare;
9898       if((toX > fromX) != (piece == WhiteRook)) {
9899         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9900       } else {
9901         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9902       }
9903     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9904                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9905       board[fromY][fromX] = EmptySquare;
9906       board[toY][toX] = EmptySquare;
9907       if((toX > fromX) != (piece == BlackRook)) {
9908         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9909       } else {
9910         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9911       }
9912     /* End of code added by Tord */
9913
9914     } else if (board[fromY][fromX] == king
9915         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9916         && toY == fromY && toX > fromX+1) {
9917         board[fromY][fromX] = EmptySquare;
9918         board[toY][toX] = king;
9919         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9920         board[fromY][BOARD_RGHT-1] = EmptySquare;
9921     } else if (board[fromY][fromX] == king
9922         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9923                && toY == fromY && toX < fromX-1) {
9924         board[fromY][fromX] = EmptySquare;
9925         board[toY][toX] = king;
9926         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9927         board[fromY][BOARD_LEFT] = EmptySquare;
9928     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9929                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9930                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9931                ) {
9932         /* white pawn promotion */
9933         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9934         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9935             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9936         board[fromY][fromX] = EmptySquare;
9937     } else if ((fromY >= BOARD_HEIGHT>>1)
9938                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9939                && (toX != fromX)
9940                && gameInfo.variant != VariantXiangqi
9941                && gameInfo.variant != VariantBerolina
9942                && (board[fromY][fromX] == WhitePawn)
9943                && (board[toY][toX] == EmptySquare)) {
9944         board[fromY][fromX] = EmptySquare;
9945         board[toY][toX] = WhitePawn;
9946         captured = board[toY - 1][toX];
9947         board[toY - 1][toX] = EmptySquare;
9948     } else if ((fromY == BOARD_HEIGHT-4)
9949                && (toX == fromX)
9950                && gameInfo.variant == VariantBerolina
9951                && (board[fromY][fromX] == WhitePawn)
9952                && (board[toY][toX] == EmptySquare)) {
9953         board[fromY][fromX] = EmptySquare;
9954         board[toY][toX] = WhitePawn;
9955         if(oldEP & EP_BEROLIN_A) {
9956                 captured = board[fromY][fromX-1];
9957                 board[fromY][fromX-1] = EmptySquare;
9958         }else{  captured = board[fromY][fromX+1];
9959                 board[fromY][fromX+1] = EmptySquare;
9960         }
9961     } else if (board[fromY][fromX] == king
9962         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9963                && toY == fromY && toX > fromX+1) {
9964         board[fromY][fromX] = EmptySquare;
9965         board[toY][toX] = king;
9966         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9967         board[fromY][BOARD_RGHT-1] = EmptySquare;
9968     } else if (board[fromY][fromX] == king
9969         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9970                && toY == fromY && toX < fromX-1) {
9971         board[fromY][fromX] = EmptySquare;
9972         board[toY][toX] = king;
9973         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9974         board[fromY][BOARD_LEFT] = EmptySquare;
9975     } else if (fromY == 7 && fromX == 3
9976                && board[fromY][fromX] == BlackKing
9977                && toY == 7 && toX == 5) {
9978         board[fromY][fromX] = EmptySquare;
9979         board[toY][toX] = BlackKing;
9980         board[fromY][7] = EmptySquare;
9981         board[toY][4] = BlackRook;
9982     } else if (fromY == 7 && fromX == 3
9983                && board[fromY][fromX] == BlackKing
9984                && toY == 7 && toX == 1) {
9985         board[fromY][fromX] = EmptySquare;
9986         board[toY][toX] = BlackKing;
9987         board[fromY][0] = EmptySquare;
9988         board[toY][2] = BlackRook;
9989     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9990                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9991                && toY < promoRank && promoChar
9992                ) {
9993         /* black pawn promotion */
9994         board[toY][toX] = CharToPiece(ToLower(promoChar));
9995         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9996             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9997         board[fromY][fromX] = EmptySquare;
9998     } else if ((fromY < BOARD_HEIGHT>>1)
9999                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
10000                && (toX != fromX)
10001                && gameInfo.variant != VariantXiangqi
10002                && gameInfo.variant != VariantBerolina
10003                && (board[fromY][fromX] == BlackPawn)
10004                && (board[toY][toX] == EmptySquare)) {
10005         board[fromY][fromX] = EmptySquare;
10006         board[toY][toX] = BlackPawn;
10007         captured = board[toY + 1][toX];
10008         board[toY + 1][toX] = EmptySquare;
10009     } else if ((fromY == 3)
10010                && (toX == fromX)
10011                && gameInfo.variant == VariantBerolina
10012                && (board[fromY][fromX] == BlackPawn)
10013                && (board[toY][toX] == EmptySquare)) {
10014         board[fromY][fromX] = EmptySquare;
10015         board[toY][toX] = BlackPawn;
10016         if(oldEP & EP_BEROLIN_A) {
10017                 captured = board[fromY][fromX-1];
10018                 board[fromY][fromX-1] = EmptySquare;
10019         }else{  captured = board[fromY][fromX+1];
10020                 board[fromY][fromX+1] = EmptySquare;
10021         }
10022     } else {
10023         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10024         board[fromY][fromX] = EmptySquare;
10025         board[toY][toX] = piece;
10026     }
10027   }
10028
10029     if (gameInfo.holdingsWidth != 0) {
10030
10031       /* !!A lot more code needs to be written to support holdings  */
10032       /* [HGM] OK, so I have written it. Holdings are stored in the */
10033       /* penultimate board files, so they are automaticlly stored   */
10034       /* in the game history.                                       */
10035       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10036                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10037         /* Delete from holdings, by decreasing count */
10038         /* and erasing image if necessary            */
10039         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10040         if(p < (int) BlackPawn) { /* white drop */
10041              p -= (int)WhitePawn;
10042                  p = PieceToNumber((ChessSquare)p);
10043              if(p >= gameInfo.holdingsSize) p = 0;
10044              if(--board[p][BOARD_WIDTH-2] <= 0)
10045                   board[p][BOARD_WIDTH-1] = EmptySquare;
10046              if((int)board[p][BOARD_WIDTH-2] < 0)
10047                         board[p][BOARD_WIDTH-2] = 0;
10048         } else {                  /* black drop */
10049              p -= (int)BlackPawn;
10050                  p = PieceToNumber((ChessSquare)p);
10051              if(p >= gameInfo.holdingsSize) p = 0;
10052              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10053                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10054              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10055                         board[BOARD_HEIGHT-1-p][1] = 0;
10056         }
10057       }
10058       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10059           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10060         /* [HGM] holdings: Add to holdings, if holdings exist */
10061         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10062                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10063                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10064         }
10065         p = (int) captured;
10066         if (p >= (int) BlackPawn) {
10067           p -= (int)BlackPawn;
10068           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10069                   /* in Shogi restore piece to its original  first */
10070                   captured = (ChessSquare) (DEMOTED captured);
10071                   p = DEMOTED p;
10072           }
10073           p = PieceToNumber((ChessSquare)p);
10074           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10075           board[p][BOARD_WIDTH-2]++;
10076           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10077         } else {
10078           p -= (int)WhitePawn;
10079           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10080                   captured = (ChessSquare) (DEMOTED captured);
10081                   p = DEMOTED p;
10082           }
10083           p = PieceToNumber((ChessSquare)p);
10084           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10085           board[BOARD_HEIGHT-1-p][1]++;
10086           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10087         }
10088       }
10089     } else if (gameInfo.variant == VariantAtomic) {
10090       if (captured != EmptySquare) {
10091         int y, x;
10092         for (y = toY-1; y <= toY+1; y++) {
10093           for (x = toX-1; x <= toX+1; x++) {
10094             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10095                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10096               board[y][x] = EmptySquare;
10097             }
10098           }
10099         }
10100         board[toY][toX] = EmptySquare;
10101       }
10102     }
10103
10104     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10105         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10106     } else
10107     if(promoChar == '+') {
10108         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10109         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10110         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10111           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10112     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10113         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10114         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10115            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10116         board[toY][toX] = newPiece;
10117     }
10118     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10119                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10120         // [HGM] superchess: take promotion piece out of holdings
10121         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10122         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10123             if(!--board[k][BOARD_WIDTH-2])
10124                 board[k][BOARD_WIDTH-1] = EmptySquare;
10125         } else {
10126             if(!--board[BOARD_HEIGHT-1-k][1])
10127                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10128         }
10129     }
10130 }
10131
10132 /* Updates forwardMostMove */
10133 void
10134 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10135 {
10136     int x = toX, y = toY;
10137     char *s = parseList[forwardMostMove];
10138     ChessSquare p = boards[forwardMostMove][toY][toX];
10139 //    forwardMostMove++; // [HGM] bare: moved downstream
10140
10141     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10142     (void) CoordsToAlgebraic(boards[forwardMostMove],
10143                              PosFlags(forwardMostMove),
10144                              fromY, fromX, y, x, promoChar,
10145                              s);
10146     if(killX >= 0 && killY >= 0)
10147         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10148
10149     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10150         int timeLeft; static int lastLoadFlag=0; int king, piece;
10151         piece = boards[forwardMostMove][fromY][fromX];
10152         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10153         if(gameInfo.variant == VariantKnightmate)
10154             king += (int) WhiteUnicorn - (int) WhiteKing;
10155         if(forwardMostMove == 0) {
10156             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10157                 fprintf(serverMoves, "%s;", UserName());
10158             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10159                 fprintf(serverMoves, "%s;", second.tidy);
10160             fprintf(serverMoves, "%s;", first.tidy);
10161             if(gameMode == MachinePlaysWhite)
10162                 fprintf(serverMoves, "%s;", UserName());
10163             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10164                 fprintf(serverMoves, "%s;", second.tidy);
10165         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10166         lastLoadFlag = loadFlag;
10167         // print base move
10168         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10169         // print castling suffix
10170         if( toY == fromY && piece == king ) {
10171             if(toX-fromX > 1)
10172                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10173             if(fromX-toX >1)
10174                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10175         }
10176         // e.p. suffix
10177         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10178              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10179              boards[forwardMostMove][toY][toX] == EmptySquare
10180              && fromX != toX && fromY != toY)
10181                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10182         // promotion suffix
10183         if(promoChar != NULLCHAR) {
10184             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10185                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10186                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10187             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10188         }
10189         if(!loadFlag) {
10190                 char buf[MOVE_LEN*2], *p; int len;
10191             fprintf(serverMoves, "/%d/%d",
10192                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10193             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10194             else                      timeLeft = blackTimeRemaining/1000;
10195             fprintf(serverMoves, "/%d", timeLeft);
10196                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10197                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10198                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10199                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10200             fprintf(serverMoves, "/%s", buf);
10201         }
10202         fflush(serverMoves);
10203     }
10204
10205     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10206         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10207       return;
10208     }
10209     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10210     if (commentList[forwardMostMove+1] != NULL) {
10211         free(commentList[forwardMostMove+1]);
10212         commentList[forwardMostMove+1] = NULL;
10213     }
10214     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10215     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10216     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10217     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10218     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10219     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10220     adjustedClock = FALSE;
10221     gameInfo.result = GameUnfinished;
10222     if (gameInfo.resultDetails != NULL) {
10223         free(gameInfo.resultDetails);
10224         gameInfo.resultDetails = NULL;
10225     }
10226     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10227                               moveList[forwardMostMove - 1]);
10228     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10229       case MT_NONE:
10230       case MT_STALEMATE:
10231       default:
10232         break;
10233       case MT_CHECK:
10234         if(!IS_SHOGI(gameInfo.variant))
10235             strcat(parseList[forwardMostMove - 1], "+");
10236         break;
10237       case MT_CHECKMATE:
10238       case MT_STAINMATE:
10239         strcat(parseList[forwardMostMove - 1], "#");
10240         break;
10241     }
10242 }
10243
10244 /* Updates currentMove if not pausing */
10245 void
10246 ShowMove (int fromX, int fromY, int toX, int toY)
10247 {
10248     int instant = (gameMode == PlayFromGameFile) ?
10249         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10250     if(appData.noGUI) return;
10251     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10252         if (!instant) {
10253             if (forwardMostMove == currentMove + 1) {
10254                 AnimateMove(boards[forwardMostMove - 1],
10255                             fromX, fromY, toX, toY);
10256             }
10257         }
10258         currentMove = forwardMostMove;
10259     }
10260
10261     killX = killY = -1; // [HGM] lion: used up
10262
10263     if (instant) return;
10264
10265     DisplayMove(currentMove - 1);
10266     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10267             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10268                 SetHighlights(fromX, fromY, toX, toY);
10269             }
10270     }
10271     DrawPosition(FALSE, boards[currentMove]);
10272     DisplayBothClocks();
10273     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10274 }
10275
10276 void
10277 SendEgtPath (ChessProgramState *cps)
10278 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10279         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10280
10281         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10282
10283         while(*p) {
10284             char c, *q = name+1, *r, *s;
10285
10286             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10287             while(*p && *p != ',') *q++ = *p++;
10288             *q++ = ':'; *q = 0;
10289             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10290                 strcmp(name, ",nalimov:") == 0 ) {
10291                 // take nalimov path from the menu-changeable option first, if it is defined
10292               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10293                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10294             } else
10295             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10296                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10297                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10298                 s = r = StrStr(s, ":") + 1; // beginning of path info
10299                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10300                 c = *r; *r = 0;             // temporarily null-terminate path info
10301                     *--q = 0;               // strip of trailig ':' from name
10302                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10303                 *r = c;
10304                 SendToProgram(buf,cps);     // send egtbpath command for this format
10305             }
10306             if(*p == ',') p++; // read away comma to position for next format name
10307         }
10308 }
10309
10310 static int
10311 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10312 {
10313       int width = 8, height = 8, holdings = 0;             // most common sizes
10314       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10315       // correct the deviations default for each variant
10316       if( v == VariantXiangqi ) width = 9,  height = 10;
10317       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10318       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10319       if( v == VariantCapablanca || v == VariantCapaRandom ||
10320           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10321                                 width = 10;
10322       if( v == VariantCourier ) width = 12;
10323       if( v == VariantSuper )                            holdings = 8;
10324       if( v == VariantGreat )   width = 10,              holdings = 8;
10325       if( v == VariantSChess )                           holdings = 7;
10326       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10327       if( v == VariantChuChess) width = 10, height = 10;
10328       if( v == VariantChu )     width = 12, height = 12;
10329       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10330              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10331              holdingsSize >= 0 && holdingsSize != holdings;
10332 }
10333
10334 char variantError[MSG_SIZ];
10335
10336 char *
10337 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10338 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10339       char *p, *variant = VariantName(v);
10340       static char b[MSG_SIZ];
10341       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10342            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10343                                                holdingsSize, variant); // cook up sized variant name
10344            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10345            if(StrStr(list, b) == NULL) {
10346                // specific sized variant not known, check if general sizing allowed
10347                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10348                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10349                             boardWidth, boardHeight, holdingsSize, engine);
10350                    return NULL;
10351                }
10352                /* [HGM] here we really should compare with the maximum supported board size */
10353            }
10354       } else snprintf(b, MSG_SIZ,"%s", variant);
10355       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10356       p = StrStr(list, b);
10357       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10358       if(p == NULL) {
10359           // occurs not at all in list, or only as sub-string
10360           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10361           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10362               int l = strlen(variantError);
10363               char *q;
10364               while(p != list && p[-1] != ',') p--;
10365               q = strchr(p, ',');
10366               if(q) *q = NULLCHAR;
10367               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10368               if(q) *q= ',';
10369           }
10370           return NULL;
10371       }
10372       return b;
10373 }
10374
10375 void
10376 InitChessProgram (ChessProgramState *cps, int setup)
10377 /* setup needed to setup FRC opening position */
10378 {
10379     char buf[MSG_SIZ], *b;
10380     if (appData.noChessProgram) return;
10381     hintRequested = FALSE;
10382     bookRequested = FALSE;
10383
10384     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10385     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10386     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10387     if(cps->memSize) { /* [HGM] memory */
10388       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10389         SendToProgram(buf, cps);
10390     }
10391     SendEgtPath(cps); /* [HGM] EGT */
10392     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10393       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10394         SendToProgram(buf, cps);
10395     }
10396
10397     SendToProgram(cps->initString, cps);
10398     if (gameInfo.variant != VariantNormal &&
10399         gameInfo.variant != VariantLoadable
10400         /* [HGM] also send variant if board size non-standard */
10401         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10402
10403       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10404                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10405       if (b == NULL) {
10406         DisplayFatalError(variantError, 0, 1);
10407         return;
10408       }
10409
10410       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10411       SendToProgram(buf, cps);
10412     }
10413     currentlyInitializedVariant = gameInfo.variant;
10414
10415     /* [HGM] send opening position in FRC to first engine */
10416     if(setup) {
10417           SendToProgram("force\n", cps);
10418           SendBoard(cps, 0);
10419           /* engine is now in force mode! Set flag to wake it up after first move. */
10420           setboardSpoiledMachineBlack = 1;
10421     }
10422
10423     if (cps->sendICS) {
10424       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10425       SendToProgram(buf, cps);
10426     }
10427     cps->maybeThinking = FALSE;
10428     cps->offeredDraw = 0;
10429     if (!appData.icsActive) {
10430         SendTimeControl(cps, movesPerSession, timeControl,
10431                         timeIncrement, appData.searchDepth,
10432                         searchTime);
10433     }
10434     if (appData.showThinking
10435         // [HGM] thinking: four options require thinking output to be sent
10436         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10437                                 ) {
10438         SendToProgram("post\n", cps);
10439     }
10440     SendToProgram("hard\n", cps);
10441     if (!appData.ponderNextMove) {
10442         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10443            it without being sure what state we are in first.  "hard"
10444            is not a toggle, so that one is OK.
10445          */
10446         SendToProgram("easy\n", cps);
10447     }
10448     if (cps->usePing) {
10449       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10450       SendToProgram(buf, cps);
10451     }
10452     cps->initDone = TRUE;
10453     ClearEngineOutputPane(cps == &second);
10454 }
10455
10456
10457 void
10458 ResendOptions (ChessProgramState *cps)
10459 { // send the stored value of the options
10460   int i;
10461   char buf[MSG_SIZ];
10462   Option *opt = cps->option;
10463   for(i=0; i<cps->nrOptions; i++, opt++) {
10464       switch(opt->type) {
10465         case Spin:
10466         case Slider:
10467         case CheckBox:
10468             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10469           break;
10470         case ComboBox:
10471           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10472           break;
10473         default:
10474             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10475           break;
10476         case Button:
10477         case SaveButton:
10478           continue;
10479       }
10480       SendToProgram(buf, cps);
10481   }
10482 }
10483
10484 void
10485 StartChessProgram (ChessProgramState *cps)
10486 {
10487     char buf[MSG_SIZ];
10488     int err;
10489
10490     if (appData.noChessProgram) return;
10491     cps->initDone = FALSE;
10492
10493     if (strcmp(cps->host, "localhost") == 0) {
10494         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10495     } else if (*appData.remoteShell == NULLCHAR) {
10496         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10497     } else {
10498         if (*appData.remoteUser == NULLCHAR) {
10499           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10500                     cps->program);
10501         } else {
10502           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10503                     cps->host, appData.remoteUser, cps->program);
10504         }
10505         err = StartChildProcess(buf, "", &cps->pr);
10506     }
10507
10508     if (err != 0) {
10509       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10510         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10511         if(cps != &first) return;
10512         appData.noChessProgram = TRUE;
10513         ThawUI();
10514         SetNCPMode();
10515 //      DisplayFatalError(buf, err, 1);
10516 //      cps->pr = NoProc;
10517 //      cps->isr = NULL;
10518         return;
10519     }
10520
10521     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10522     if (cps->protocolVersion > 1) {
10523       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10524       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10525         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10526         cps->comboCnt = 0;  //                and values of combo boxes
10527       }
10528       SendToProgram(buf, cps);
10529       if(cps->reload) ResendOptions(cps);
10530     } else {
10531       SendToProgram("xboard\n", cps);
10532     }
10533 }
10534
10535 void
10536 TwoMachinesEventIfReady P((void))
10537 {
10538   static int curMess = 0;
10539   if (first.lastPing != first.lastPong) {
10540     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10541     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10542     return;
10543   }
10544   if (second.lastPing != second.lastPong) {
10545     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10546     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10547     return;
10548   }
10549   DisplayMessage("", ""); curMess = 0;
10550   TwoMachinesEvent();
10551 }
10552
10553 char *
10554 MakeName (char *template)
10555 {
10556     time_t clock;
10557     struct tm *tm;
10558     static char buf[MSG_SIZ];
10559     char *p = buf;
10560     int i;
10561
10562     clock = time((time_t *)NULL);
10563     tm = localtime(&clock);
10564
10565     while(*p++ = *template++) if(p[-1] == '%') {
10566         switch(*template++) {
10567           case 0:   *p = 0; return buf;
10568           case 'Y': i = tm->tm_year+1900; break;
10569           case 'y': i = tm->tm_year-100; break;
10570           case 'M': i = tm->tm_mon+1; break;
10571           case 'd': i = tm->tm_mday; break;
10572           case 'h': i = tm->tm_hour; break;
10573           case 'm': i = tm->tm_min; break;
10574           case 's': i = tm->tm_sec; break;
10575           default:  i = 0;
10576         }
10577         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10578     }
10579     return buf;
10580 }
10581
10582 int
10583 CountPlayers (char *p)
10584 {
10585     int n = 0;
10586     while(p = strchr(p, '\n')) p++, n++; // count participants
10587     return n;
10588 }
10589
10590 FILE *
10591 WriteTourneyFile (char *results, FILE *f)
10592 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10593     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10594     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10595         // create a file with tournament description
10596         fprintf(f, "-participants {%s}\n", appData.participants);
10597         fprintf(f, "-seedBase %d\n", appData.seedBase);
10598         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10599         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10600         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10601         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10602         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10603         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10604         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10605         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10606         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10607         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10608         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10609         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10610         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10611         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10612         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10613         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10614         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10615         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10616         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10617         fprintf(f, "-smpCores %d\n", appData.smpCores);
10618         if(searchTime > 0)
10619                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10620         else {
10621                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10622                 fprintf(f, "-tc %s\n", appData.timeControl);
10623                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10624         }
10625         fprintf(f, "-results \"%s\"\n", results);
10626     }
10627     return f;
10628 }
10629
10630 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10631
10632 void
10633 Substitute (char *participants, int expunge)
10634 {
10635     int i, changed, changes=0, nPlayers=0;
10636     char *p, *q, *r, buf[MSG_SIZ];
10637     if(participants == NULL) return;
10638     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10639     r = p = participants; q = appData.participants;
10640     while(*p && *p == *q) {
10641         if(*p == '\n') r = p+1, nPlayers++;
10642         p++; q++;
10643     }
10644     if(*p) { // difference
10645         while(*p && *p++ != '\n');
10646         while(*q && *q++ != '\n');
10647       changed = nPlayers;
10648         changes = 1 + (strcmp(p, q) != 0);
10649     }
10650     if(changes == 1) { // a single engine mnemonic was changed
10651         q = r; while(*q) nPlayers += (*q++ == '\n');
10652         p = buf; while(*r && (*p = *r++) != '\n') p++;
10653         *p = NULLCHAR;
10654         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10655         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10656         if(mnemonic[i]) { // The substitute is valid
10657             FILE *f;
10658             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10659                 flock(fileno(f), LOCK_EX);
10660                 ParseArgsFromFile(f);
10661                 fseek(f, 0, SEEK_SET);
10662                 FREE(appData.participants); appData.participants = participants;
10663                 if(expunge) { // erase results of replaced engine
10664                     int len = strlen(appData.results), w, b, dummy;
10665                     for(i=0; i<len; i++) {
10666                         Pairing(i, nPlayers, &w, &b, &dummy);
10667                         if((w == changed || b == changed) && appData.results[i] == '*') {
10668                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10669                             fclose(f);
10670                             return;
10671                         }
10672                     }
10673                     for(i=0; i<len; i++) {
10674                         Pairing(i, nPlayers, &w, &b, &dummy);
10675                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10676                     }
10677                 }
10678                 WriteTourneyFile(appData.results, f);
10679                 fclose(f); // release lock
10680                 return;
10681             }
10682         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10683     }
10684     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10685     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10686     free(participants);
10687     return;
10688 }
10689
10690 int
10691 CheckPlayers (char *participants)
10692 {
10693         int i;
10694         char buf[MSG_SIZ], *p;
10695         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10696         while(p = strchr(participants, '\n')) {
10697             *p = NULLCHAR;
10698             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10699             if(!mnemonic[i]) {
10700                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10701                 *p = '\n';
10702                 DisplayError(buf, 0);
10703                 return 1;
10704             }
10705             *p = '\n';
10706             participants = p + 1;
10707         }
10708         return 0;
10709 }
10710
10711 int
10712 CreateTourney (char *name)
10713 {
10714         FILE *f;
10715         if(matchMode && strcmp(name, appData.tourneyFile)) {
10716              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10717         }
10718         if(name[0] == NULLCHAR) {
10719             if(appData.participants[0])
10720                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10721             return 0;
10722         }
10723         f = fopen(name, "r");
10724         if(f) { // file exists
10725             ASSIGN(appData.tourneyFile, name);
10726             ParseArgsFromFile(f); // parse it
10727         } else {
10728             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10729             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10730                 DisplayError(_("Not enough participants"), 0);
10731                 return 0;
10732             }
10733             if(CheckPlayers(appData.participants)) return 0;
10734             ASSIGN(appData.tourneyFile, name);
10735             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10736             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10737         }
10738         fclose(f);
10739         appData.noChessProgram = FALSE;
10740         appData.clockMode = TRUE;
10741         SetGNUMode();
10742         return 1;
10743 }
10744
10745 int
10746 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10747 {
10748     char buf[MSG_SIZ], *p, *q;
10749     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10750     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10751     skip = !all && group[0]; // if group requested, we start in skip mode
10752     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10753         p = names; q = buf; header = 0;
10754         while(*p && *p != '\n') *q++ = *p++;
10755         *q = 0;
10756         if(*p == '\n') p++;
10757         if(buf[0] == '#') {
10758             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10759             depth++; // we must be entering a new group
10760             if(all) continue; // suppress printing group headers when complete list requested
10761             header = 1;
10762             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10763         }
10764         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10765         if(engineList[i]) free(engineList[i]);
10766         engineList[i] = strdup(buf);
10767         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10768         if(engineMnemonic[i]) free(engineMnemonic[i]);
10769         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10770             strcat(buf, " (");
10771             sscanf(q + 8, "%s", buf + strlen(buf));
10772             strcat(buf, ")");
10773         }
10774         engineMnemonic[i] = strdup(buf);
10775         i++;
10776     }
10777     engineList[i] = engineMnemonic[i] = NULL;
10778     return i;
10779 }
10780
10781 // following implemented as macro to avoid type limitations
10782 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10783
10784 void
10785 SwapEngines (int n)
10786 {   // swap settings for first engine and other engine (so far only some selected options)
10787     int h;
10788     char *p;
10789     if(n == 0) return;
10790     SWAP(directory, p)
10791     SWAP(chessProgram, p)
10792     SWAP(isUCI, h)
10793     SWAP(hasOwnBookUCI, h)
10794     SWAP(protocolVersion, h)
10795     SWAP(reuse, h)
10796     SWAP(scoreIsAbsolute, h)
10797     SWAP(timeOdds, h)
10798     SWAP(logo, p)
10799     SWAP(pgnName, p)
10800     SWAP(pvSAN, h)
10801     SWAP(engOptions, p)
10802     SWAP(engInitString, p)
10803     SWAP(computerString, p)
10804     SWAP(features, p)
10805     SWAP(fenOverride, p)
10806     SWAP(NPS, h)
10807     SWAP(accumulateTC, h)
10808     SWAP(drawDepth, h)
10809     SWAP(host, p)
10810 }
10811
10812 int
10813 GetEngineLine (char *s, int n)
10814 {
10815     int i;
10816     char buf[MSG_SIZ];
10817     extern char *icsNames;
10818     if(!s || !*s) return 0;
10819     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10820     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10821     if(!mnemonic[i]) return 0;
10822     if(n == 11) return 1; // just testing if there was a match
10823     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10824     if(n == 1) SwapEngines(n);
10825     ParseArgsFromString(buf);
10826     if(n == 1) SwapEngines(n);
10827     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10828         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10829         ParseArgsFromString(buf);
10830     }
10831     return 1;
10832 }
10833
10834 int
10835 SetPlayer (int player, char *p)
10836 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10837     int i;
10838     char buf[MSG_SIZ], *engineName;
10839     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10840     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10841     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10842     if(mnemonic[i]) {
10843         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10844         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10845         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10846         ParseArgsFromString(buf);
10847     } else { // no engine with this nickname is installed!
10848         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10849         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10850         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10851         ModeHighlight();
10852         DisplayError(buf, 0);
10853         return 0;
10854     }
10855     free(engineName);
10856     return i;
10857 }
10858
10859 char *recentEngines;
10860
10861 void
10862 RecentEngineEvent (int nr)
10863 {
10864     int n;
10865 //    SwapEngines(1); // bump first to second
10866 //    ReplaceEngine(&second, 1); // and load it there
10867     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10868     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10869     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10870         ReplaceEngine(&first, 0);
10871         FloatToFront(&appData.recentEngineList, command[n]);
10872     }
10873 }
10874
10875 int
10876 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10877 {   // determine players from game number
10878     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10879
10880     if(appData.tourneyType == 0) {
10881         roundsPerCycle = (nPlayers - 1) | 1;
10882         pairingsPerRound = nPlayers / 2;
10883     } else if(appData.tourneyType > 0) {
10884         roundsPerCycle = nPlayers - appData.tourneyType;
10885         pairingsPerRound = appData.tourneyType;
10886     }
10887     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10888     gamesPerCycle = gamesPerRound * roundsPerCycle;
10889     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10890     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10891     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10892     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10893     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10894     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10895
10896     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10897     if(appData.roundSync) *syncInterval = gamesPerRound;
10898
10899     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10900
10901     if(appData.tourneyType == 0) {
10902         if(curPairing == (nPlayers-1)/2 ) {
10903             *whitePlayer = curRound;
10904             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10905         } else {
10906             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10907             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10908             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10909             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10910         }
10911     } else if(appData.tourneyType > 1) {
10912         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10913         *whitePlayer = curRound + appData.tourneyType;
10914     } else if(appData.tourneyType > 0) {
10915         *whitePlayer = curPairing;
10916         *blackPlayer = curRound + appData.tourneyType;
10917     }
10918
10919     // take care of white/black alternation per round.
10920     // For cycles and games this is already taken care of by default, derived from matchGame!
10921     return curRound & 1;
10922 }
10923
10924 int
10925 NextTourneyGame (int nr, int *swapColors)
10926 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10927     char *p, *q;
10928     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10929     FILE *tf;
10930     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10931     tf = fopen(appData.tourneyFile, "r");
10932     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10933     ParseArgsFromFile(tf); fclose(tf);
10934     InitTimeControls(); // TC might be altered from tourney file
10935
10936     nPlayers = CountPlayers(appData.participants); // count participants
10937     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10938     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10939
10940     if(syncInterval) {
10941         p = q = appData.results;
10942         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10943         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10944             DisplayMessage(_("Waiting for other game(s)"),"");
10945             waitingForGame = TRUE;
10946             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10947             return 0;
10948         }
10949         waitingForGame = FALSE;
10950     }
10951
10952     if(appData.tourneyType < 0) {
10953         if(nr>=0 && !pairingReceived) {
10954             char buf[1<<16];
10955             if(pairing.pr == NoProc) {
10956                 if(!appData.pairingEngine[0]) {
10957                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10958                     return 0;
10959                 }
10960                 StartChessProgram(&pairing); // starts the pairing engine
10961             }
10962             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10963             SendToProgram(buf, &pairing);
10964             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10965             SendToProgram(buf, &pairing);
10966             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10967         }
10968         pairingReceived = 0;                              // ... so we continue here
10969         *swapColors = 0;
10970         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10971         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10972         matchGame = 1; roundNr = nr / syncInterval + 1;
10973     }
10974
10975     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10976
10977     // redefine engines, engine dir, etc.
10978     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10979     if(first.pr == NoProc) {
10980       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10981       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10982     }
10983     if(second.pr == NoProc) {
10984       SwapEngines(1);
10985       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10986       SwapEngines(1);         // and make that valid for second engine by swapping
10987       InitEngine(&second, 1);
10988     }
10989     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10990     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10991     return OK;
10992 }
10993
10994 void
10995 NextMatchGame ()
10996 {   // performs game initialization that does not invoke engines, and then tries to start the game
10997     int res, firstWhite, swapColors = 0;
10998     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10999     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
11000         char buf[MSG_SIZ];
11001         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11002         if(strcmp(buf, currentDebugFile)) { // name has changed
11003             FILE *f = fopen(buf, "w");
11004             if(f) { // if opening the new file failed, just keep using the old one
11005                 ASSIGN(currentDebugFile, buf);
11006                 fclose(debugFP);
11007                 debugFP = f;
11008             }
11009             if(appData.serverFileName) {
11010                 if(serverFP) fclose(serverFP);
11011                 serverFP = fopen(appData.serverFileName, "w");
11012                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11013                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11014             }
11015         }
11016     }
11017     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11018     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11019     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11020     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11021     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11022     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11023     Reset(FALSE, first.pr != NoProc);
11024     res = LoadGameOrPosition(matchGame); // setup game
11025     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11026     if(!res) return; // abort when bad game/pos file
11027     TwoMachinesEvent();
11028 }
11029
11030 void
11031 UserAdjudicationEvent (int result)
11032 {
11033     ChessMove gameResult = GameIsDrawn;
11034
11035     if( result > 0 ) {
11036         gameResult = WhiteWins;
11037     }
11038     else if( result < 0 ) {
11039         gameResult = BlackWins;
11040     }
11041
11042     if( gameMode == TwoMachinesPlay ) {
11043         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11044     }
11045 }
11046
11047
11048 // [HGM] save: calculate checksum of game to make games easily identifiable
11049 int
11050 StringCheckSum (char *s)
11051 {
11052         int i = 0;
11053         if(s==NULL) return 0;
11054         while(*s) i = i*259 + *s++;
11055         return i;
11056 }
11057
11058 int
11059 GameCheckSum ()
11060 {
11061         int i, sum=0;
11062         for(i=backwardMostMove; i<forwardMostMove; i++) {
11063                 sum += pvInfoList[i].depth;
11064                 sum += StringCheckSum(parseList[i]);
11065                 sum += StringCheckSum(commentList[i]);
11066                 sum *= 261;
11067         }
11068         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11069         return sum + StringCheckSum(commentList[i]);
11070 } // end of save patch
11071
11072 void
11073 GameEnds (ChessMove result, char *resultDetails, int whosays)
11074 {
11075     GameMode nextGameMode;
11076     int isIcsGame;
11077     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11078
11079     if(endingGame) return; /* [HGM] crash: forbid recursion */
11080     endingGame = 1;
11081     if(twoBoards) { // [HGM] dual: switch back to one board
11082         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11083         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11084     }
11085     if (appData.debugMode) {
11086       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11087               result, resultDetails ? resultDetails : "(null)", whosays);
11088     }
11089
11090     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11091
11092     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11093
11094     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11095         /* If we are playing on ICS, the server decides when the
11096            game is over, but the engine can offer to draw, claim
11097            a draw, or resign.
11098          */
11099 #if ZIPPY
11100         if (appData.zippyPlay && first.initDone) {
11101             if (result == GameIsDrawn) {
11102                 /* In case draw still needs to be claimed */
11103                 SendToICS(ics_prefix);
11104                 SendToICS("draw\n");
11105             } else if (StrCaseStr(resultDetails, "resign")) {
11106                 SendToICS(ics_prefix);
11107                 SendToICS("resign\n");
11108             }
11109         }
11110 #endif
11111         endingGame = 0; /* [HGM] crash */
11112         return;
11113     }
11114
11115     /* If we're loading the game from a file, stop */
11116     if (whosays == GE_FILE) {
11117       (void) StopLoadGameTimer();
11118       gameFileFP = NULL;
11119     }
11120
11121     /* Cancel draw offers */
11122     first.offeredDraw = second.offeredDraw = 0;
11123
11124     /* If this is an ICS game, only ICS can really say it's done;
11125        if not, anyone can. */
11126     isIcsGame = (gameMode == IcsPlayingWhite ||
11127                  gameMode == IcsPlayingBlack ||
11128                  gameMode == IcsObserving    ||
11129                  gameMode == IcsExamining);
11130
11131     if (!isIcsGame || whosays == GE_ICS) {
11132         /* OK -- not an ICS game, or ICS said it was done */
11133         StopClocks();
11134         if (!isIcsGame && !appData.noChessProgram)
11135           SetUserThinkingEnables();
11136
11137         /* [HGM] if a machine claims the game end we verify this claim */
11138         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11139             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11140                 char claimer;
11141                 ChessMove trueResult = (ChessMove) -1;
11142
11143                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11144                                             first.twoMachinesColor[0] :
11145                                             second.twoMachinesColor[0] ;
11146
11147                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11148                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11149                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11150                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11151                 } else
11152                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11153                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11154                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11155                 } else
11156                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11157                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11158                 }
11159
11160                 // now verify win claims, but not in drop games, as we don't understand those yet
11161                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11162                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11163                     (result == WhiteWins && claimer == 'w' ||
11164                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11165                       if (appData.debugMode) {
11166                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11167                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11168                       }
11169                       if(result != trueResult) {
11170                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11171                               result = claimer == 'w' ? BlackWins : WhiteWins;
11172                               resultDetails = buf;
11173                       }
11174                 } else
11175                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11176                     && (forwardMostMove <= backwardMostMove ||
11177                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11178                         (claimer=='b')==(forwardMostMove&1))
11179                                                                                   ) {
11180                       /* [HGM] verify: draws that were not flagged are false claims */
11181                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11182                       result = claimer == 'w' ? BlackWins : WhiteWins;
11183                       resultDetails = buf;
11184                 }
11185                 /* (Claiming a loss is accepted no questions asked!) */
11186             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11187                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11188                 result = GameUnfinished;
11189                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11190             }
11191             /* [HGM] bare: don't allow bare King to win */
11192             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11193                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11194                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11195                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11196                && result != GameIsDrawn)
11197             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11198                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11199                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11200                         if(p >= 0 && p <= (int)WhiteKing) k++;
11201                 }
11202                 if (appData.debugMode) {
11203                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11204                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11205                 }
11206                 if(k <= 1) {
11207                         result = GameIsDrawn;
11208                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11209                         resultDetails = buf;
11210                 }
11211             }
11212         }
11213
11214
11215         if(serverMoves != NULL && !loadFlag) { char c = '=';
11216             if(result==WhiteWins) c = '+';
11217             if(result==BlackWins) c = '-';
11218             if(resultDetails != NULL)
11219                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11220         }
11221         if (resultDetails != NULL) {
11222             gameInfo.result = result;
11223             gameInfo.resultDetails = StrSave(resultDetails);
11224
11225             /* display last move only if game was not loaded from file */
11226             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11227                 DisplayMove(currentMove - 1);
11228
11229             if (forwardMostMove != 0) {
11230                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11231                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11232                                                                 ) {
11233                     if (*appData.saveGameFile != NULLCHAR) {
11234                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11235                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11236                         else
11237                         SaveGameToFile(appData.saveGameFile, TRUE);
11238                     } else if (appData.autoSaveGames) {
11239                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11240                     }
11241                     if (*appData.savePositionFile != NULLCHAR) {
11242                         SavePositionToFile(appData.savePositionFile);
11243                     }
11244                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11245                 }
11246             }
11247
11248             /* Tell program how game ended in case it is learning */
11249             /* [HGM] Moved this to after saving the PGN, just in case */
11250             /* engine died and we got here through time loss. In that */
11251             /* case we will get a fatal error writing the pipe, which */
11252             /* would otherwise lose us the PGN.                       */
11253             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11254             /* output during GameEnds should never be fatal anymore   */
11255             if (gameMode == MachinePlaysWhite ||
11256                 gameMode == MachinePlaysBlack ||
11257                 gameMode == TwoMachinesPlay ||
11258                 gameMode == IcsPlayingWhite ||
11259                 gameMode == IcsPlayingBlack ||
11260                 gameMode == BeginningOfGame) {
11261                 char buf[MSG_SIZ];
11262                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11263                         resultDetails);
11264                 if (first.pr != NoProc) {
11265                     SendToProgram(buf, &first);
11266                 }
11267                 if (second.pr != NoProc &&
11268                     gameMode == TwoMachinesPlay) {
11269                     SendToProgram(buf, &second);
11270                 }
11271             }
11272         }
11273
11274         if (appData.icsActive) {
11275             if (appData.quietPlay &&
11276                 (gameMode == IcsPlayingWhite ||
11277                  gameMode == IcsPlayingBlack)) {
11278                 SendToICS(ics_prefix);
11279                 SendToICS("set shout 1\n");
11280             }
11281             nextGameMode = IcsIdle;
11282             ics_user_moved = FALSE;
11283             /* clean up premove.  It's ugly when the game has ended and the
11284              * premove highlights are still on the board.
11285              */
11286             if (gotPremove) {
11287               gotPremove = FALSE;
11288               ClearPremoveHighlights();
11289               DrawPosition(FALSE, boards[currentMove]);
11290             }
11291             if (whosays == GE_ICS) {
11292                 switch (result) {
11293                 case WhiteWins:
11294                     if (gameMode == IcsPlayingWhite)
11295                         PlayIcsWinSound();
11296                     else if(gameMode == IcsPlayingBlack)
11297                         PlayIcsLossSound();
11298                     break;
11299                 case BlackWins:
11300                     if (gameMode == IcsPlayingBlack)
11301                         PlayIcsWinSound();
11302                     else if(gameMode == IcsPlayingWhite)
11303                         PlayIcsLossSound();
11304                     break;
11305                 case GameIsDrawn:
11306                     PlayIcsDrawSound();
11307                     break;
11308                 default:
11309                     PlayIcsUnfinishedSound();
11310                 }
11311             }
11312             if(appData.quitNext) { ExitEvent(0); return; }
11313         } else if (gameMode == EditGame ||
11314                    gameMode == PlayFromGameFile ||
11315                    gameMode == AnalyzeMode ||
11316                    gameMode == AnalyzeFile) {
11317             nextGameMode = gameMode;
11318         } else {
11319             nextGameMode = EndOfGame;
11320         }
11321         pausing = FALSE;
11322         ModeHighlight();
11323     } else {
11324         nextGameMode = gameMode;
11325     }
11326
11327     if (appData.noChessProgram) {
11328         gameMode = nextGameMode;
11329         ModeHighlight();
11330         endingGame = 0; /* [HGM] crash */
11331         return;
11332     }
11333
11334     if (first.reuse) {
11335         /* Put first chess program into idle state */
11336         if (first.pr != NoProc &&
11337             (gameMode == MachinePlaysWhite ||
11338              gameMode == MachinePlaysBlack ||
11339              gameMode == TwoMachinesPlay ||
11340              gameMode == IcsPlayingWhite ||
11341              gameMode == IcsPlayingBlack ||
11342              gameMode == BeginningOfGame)) {
11343             SendToProgram("force\n", &first);
11344             if (first.usePing) {
11345               char buf[MSG_SIZ];
11346               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11347               SendToProgram(buf, &first);
11348             }
11349         }
11350     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11351         /* Kill off first chess program */
11352         if (first.isr != NULL)
11353           RemoveInputSource(first.isr);
11354         first.isr = NULL;
11355
11356         if (first.pr != NoProc) {
11357             ExitAnalyzeMode();
11358             DoSleep( appData.delayBeforeQuit );
11359             SendToProgram("quit\n", &first);
11360             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11361             first.reload = TRUE;
11362         }
11363         first.pr = NoProc;
11364     }
11365     if (second.reuse) {
11366         /* Put second chess program into idle state */
11367         if (second.pr != NoProc &&
11368             gameMode == TwoMachinesPlay) {
11369             SendToProgram("force\n", &second);
11370             if (second.usePing) {
11371               char buf[MSG_SIZ];
11372               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11373               SendToProgram(buf, &second);
11374             }
11375         }
11376     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11377         /* Kill off second chess program */
11378         if (second.isr != NULL)
11379           RemoveInputSource(second.isr);
11380         second.isr = NULL;
11381
11382         if (second.pr != NoProc) {
11383             DoSleep( appData.delayBeforeQuit );
11384             SendToProgram("quit\n", &second);
11385             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11386             second.reload = TRUE;
11387         }
11388         second.pr = NoProc;
11389     }
11390
11391     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11392         char resChar = '=';
11393         switch (result) {
11394         case WhiteWins:
11395           resChar = '+';
11396           if (first.twoMachinesColor[0] == 'w') {
11397             first.matchWins++;
11398           } else {
11399             second.matchWins++;
11400           }
11401           break;
11402         case BlackWins:
11403           resChar = '-';
11404           if (first.twoMachinesColor[0] == 'b') {
11405             first.matchWins++;
11406           } else {
11407             second.matchWins++;
11408           }
11409           break;
11410         case GameUnfinished:
11411           resChar = ' ';
11412         default:
11413           break;
11414         }
11415
11416         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11417         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11418             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11419             ReserveGame(nextGame, resChar); // sets nextGame
11420             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11421             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11422         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11423
11424         if (nextGame <= appData.matchGames && !abortMatch) {
11425             gameMode = nextGameMode;
11426             matchGame = nextGame; // this will be overruled in tourney mode!
11427             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11428             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11429             endingGame = 0; /* [HGM] crash */
11430             return;
11431         } else {
11432             gameMode = nextGameMode;
11433             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11434                      first.tidy, second.tidy,
11435                      first.matchWins, second.matchWins,
11436                      appData.matchGames - (first.matchWins + second.matchWins));
11437             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11438             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11439             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11440             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11441                 first.twoMachinesColor = "black\n";
11442                 second.twoMachinesColor = "white\n";
11443             } else {
11444                 first.twoMachinesColor = "white\n";
11445                 second.twoMachinesColor = "black\n";
11446             }
11447         }
11448     }
11449     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11450         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11451       ExitAnalyzeMode();
11452     gameMode = nextGameMode;
11453     ModeHighlight();
11454     endingGame = 0;  /* [HGM] crash */
11455     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11456         if(matchMode == TRUE) { // match through command line: exit with or without popup
11457             if(ranking) {
11458                 ToNrEvent(forwardMostMove);
11459                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11460                 else ExitEvent(0);
11461             } else DisplayFatalError(buf, 0, 0);
11462         } else { // match through menu; just stop, with or without popup
11463             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11464             ModeHighlight();
11465             if(ranking){
11466                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11467             } else DisplayNote(buf);
11468       }
11469       if(ranking) free(ranking);
11470     }
11471 }
11472
11473 /* Assumes program was just initialized (initString sent).
11474    Leaves program in force mode. */
11475 void
11476 FeedMovesToProgram (ChessProgramState *cps, int upto)
11477 {
11478     int i;
11479
11480     if (appData.debugMode)
11481       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11482               startedFromSetupPosition ? "position and " : "",
11483               backwardMostMove, upto, cps->which);
11484     if(currentlyInitializedVariant != gameInfo.variant) {
11485       char buf[MSG_SIZ];
11486         // [HGM] variantswitch: make engine aware of new variant
11487         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11488                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11489                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11490         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11491         SendToProgram(buf, cps);
11492         currentlyInitializedVariant = gameInfo.variant;
11493     }
11494     SendToProgram("force\n", cps);
11495     if (startedFromSetupPosition) {
11496         SendBoard(cps, backwardMostMove);
11497     if (appData.debugMode) {
11498         fprintf(debugFP, "feedMoves\n");
11499     }
11500     }
11501     for (i = backwardMostMove; i < upto; i++) {
11502         SendMoveToProgram(i, cps);
11503     }
11504 }
11505
11506
11507 int
11508 ResurrectChessProgram ()
11509 {
11510      /* The chess program may have exited.
11511         If so, restart it and feed it all the moves made so far. */
11512     static int doInit = 0;
11513
11514     if (appData.noChessProgram) return 1;
11515
11516     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11517         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11518         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11519         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11520     } else {
11521         if (first.pr != NoProc) return 1;
11522         StartChessProgram(&first);
11523     }
11524     InitChessProgram(&first, FALSE);
11525     FeedMovesToProgram(&first, currentMove);
11526
11527     if (!first.sendTime) {
11528         /* can't tell gnuchess what its clock should read,
11529            so we bow to its notion. */
11530         ResetClocks();
11531         timeRemaining[0][currentMove] = whiteTimeRemaining;
11532         timeRemaining[1][currentMove] = blackTimeRemaining;
11533     }
11534
11535     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11536                 appData.icsEngineAnalyze) && first.analysisSupport) {
11537       SendToProgram("analyze\n", &first);
11538       first.analyzing = TRUE;
11539     }
11540     return 1;
11541 }
11542
11543 /*
11544  * Button procedures
11545  */
11546 void
11547 Reset (int redraw, int init)
11548 {
11549     int i;
11550
11551     if (appData.debugMode) {
11552         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11553                 redraw, init, gameMode);
11554     }
11555     CleanupTail(); // [HGM] vari: delete any stored variations
11556     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11557     pausing = pauseExamInvalid = FALSE;
11558     startedFromSetupPosition = blackPlaysFirst = FALSE;
11559     firstMove = TRUE;
11560     whiteFlag = blackFlag = FALSE;
11561     userOfferedDraw = FALSE;
11562     hintRequested = bookRequested = FALSE;
11563     first.maybeThinking = FALSE;
11564     second.maybeThinking = FALSE;
11565     first.bookSuspend = FALSE; // [HGM] book
11566     second.bookSuspend = FALSE;
11567     thinkOutput[0] = NULLCHAR;
11568     lastHint[0] = NULLCHAR;
11569     ClearGameInfo(&gameInfo);
11570     gameInfo.variant = StringToVariant(appData.variant);
11571     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11572     ics_user_moved = ics_clock_paused = FALSE;
11573     ics_getting_history = H_FALSE;
11574     ics_gamenum = -1;
11575     white_holding[0] = black_holding[0] = NULLCHAR;
11576     ClearProgramStats();
11577     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11578
11579     ResetFrontEnd();
11580     ClearHighlights();
11581     flipView = appData.flipView;
11582     ClearPremoveHighlights();
11583     gotPremove = FALSE;
11584     alarmSounded = FALSE;
11585     killX = killY = -1; // [HGM] lion
11586
11587     GameEnds(EndOfFile, NULL, GE_PLAYER);
11588     if(appData.serverMovesName != NULL) {
11589         /* [HGM] prepare to make moves file for broadcasting */
11590         clock_t t = clock();
11591         if(serverMoves != NULL) fclose(serverMoves);
11592         serverMoves = fopen(appData.serverMovesName, "r");
11593         if(serverMoves != NULL) {
11594             fclose(serverMoves);
11595             /* delay 15 sec before overwriting, so all clients can see end */
11596             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11597         }
11598         serverMoves = fopen(appData.serverMovesName, "w");
11599     }
11600
11601     ExitAnalyzeMode();
11602     gameMode = BeginningOfGame;
11603     ModeHighlight();
11604     if(appData.icsActive) gameInfo.variant = VariantNormal;
11605     currentMove = forwardMostMove = backwardMostMove = 0;
11606     MarkTargetSquares(1);
11607     InitPosition(redraw);
11608     for (i = 0; i < MAX_MOVES; i++) {
11609         if (commentList[i] != NULL) {
11610             free(commentList[i]);
11611             commentList[i] = NULL;
11612         }
11613     }
11614     ResetClocks();
11615     timeRemaining[0][0] = whiteTimeRemaining;
11616     timeRemaining[1][0] = blackTimeRemaining;
11617
11618     if (first.pr == NoProc) {
11619         StartChessProgram(&first);
11620     }
11621     if (init) {
11622             InitChessProgram(&first, startedFromSetupPosition);
11623     }
11624     DisplayTitle("");
11625     DisplayMessage("", "");
11626     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11627     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11628     ClearMap();        // [HGM] exclude: invalidate map
11629 }
11630
11631 void
11632 AutoPlayGameLoop ()
11633 {
11634     for (;;) {
11635         if (!AutoPlayOneMove())
11636           return;
11637         if (matchMode || appData.timeDelay == 0)
11638           continue;
11639         if (appData.timeDelay < 0)
11640           return;
11641         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11642         break;
11643     }
11644 }
11645
11646 void
11647 AnalyzeNextGame()
11648 {
11649     ReloadGame(1); // next game
11650 }
11651
11652 int
11653 AutoPlayOneMove ()
11654 {
11655     int fromX, fromY, toX, toY;
11656
11657     if (appData.debugMode) {
11658       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11659     }
11660
11661     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11662       return FALSE;
11663
11664     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11665       pvInfoList[currentMove].depth = programStats.depth;
11666       pvInfoList[currentMove].score = programStats.score;
11667       pvInfoList[currentMove].time  = 0;
11668       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11669       else { // append analysis of final position as comment
11670         char buf[MSG_SIZ];
11671         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11672         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11673       }
11674       programStats.depth = 0;
11675     }
11676
11677     if (currentMove >= forwardMostMove) {
11678       if(gameMode == AnalyzeFile) {
11679           if(appData.loadGameIndex == -1) {
11680             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11681           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11682           } else {
11683           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11684         }
11685       }
11686 //      gameMode = EndOfGame;
11687 //      ModeHighlight();
11688
11689       /* [AS] Clear current move marker at the end of a game */
11690       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11691
11692       return FALSE;
11693     }
11694
11695     toX = moveList[currentMove][2] - AAA;
11696     toY = moveList[currentMove][3] - ONE;
11697
11698     if (moveList[currentMove][1] == '@') {
11699         if (appData.highlightLastMove) {
11700             SetHighlights(-1, -1, toX, toY);
11701         }
11702     } else {
11703         fromX = moveList[currentMove][0] - AAA;
11704         fromY = moveList[currentMove][1] - ONE;
11705
11706         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11707
11708         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11709
11710         if (appData.highlightLastMove) {
11711             SetHighlights(fromX, fromY, toX, toY);
11712         }
11713     }
11714     DisplayMove(currentMove);
11715     SendMoveToProgram(currentMove++, &first);
11716     DisplayBothClocks();
11717     DrawPosition(FALSE, boards[currentMove]);
11718     // [HGM] PV info: always display, routine tests if empty
11719     DisplayComment(currentMove - 1, commentList[currentMove]);
11720     return TRUE;
11721 }
11722
11723
11724 int
11725 LoadGameOneMove (ChessMove readAhead)
11726 {
11727     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11728     char promoChar = NULLCHAR;
11729     ChessMove moveType;
11730     char move[MSG_SIZ];
11731     char *p, *q;
11732
11733     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11734         gameMode != AnalyzeMode && gameMode != Training) {
11735         gameFileFP = NULL;
11736         return FALSE;
11737     }
11738
11739     yyboardindex = forwardMostMove;
11740     if (readAhead != EndOfFile) {
11741       moveType = readAhead;
11742     } else {
11743       if (gameFileFP == NULL)
11744           return FALSE;
11745       moveType = (ChessMove) Myylex();
11746     }
11747
11748     done = FALSE;
11749     switch (moveType) {
11750       case Comment:
11751         if (appData.debugMode)
11752           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11753         p = yy_text;
11754
11755         /* append the comment but don't display it */
11756         AppendComment(currentMove, p, FALSE);
11757         return TRUE;
11758
11759       case WhiteCapturesEnPassant:
11760       case BlackCapturesEnPassant:
11761       case WhitePromotion:
11762       case BlackPromotion:
11763       case WhiteNonPromotion:
11764       case BlackNonPromotion:
11765       case NormalMove:
11766       case FirstLeg:
11767       case WhiteKingSideCastle:
11768       case WhiteQueenSideCastle:
11769       case BlackKingSideCastle:
11770       case BlackQueenSideCastle:
11771       case WhiteKingSideCastleWild:
11772       case WhiteQueenSideCastleWild:
11773       case BlackKingSideCastleWild:
11774       case BlackQueenSideCastleWild:
11775       /* PUSH Fabien */
11776       case WhiteHSideCastleFR:
11777       case WhiteASideCastleFR:
11778       case BlackHSideCastleFR:
11779       case BlackASideCastleFR:
11780       /* POP Fabien */
11781         if (appData.debugMode)
11782           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11783         fromX = currentMoveString[0] - AAA;
11784         fromY = currentMoveString[1] - ONE;
11785         toX = currentMoveString[2] - AAA;
11786         toY = currentMoveString[3] - ONE;
11787         promoChar = currentMoveString[4];
11788         if(promoChar == ';') promoChar = NULLCHAR;
11789         break;
11790
11791       case WhiteDrop:
11792       case BlackDrop:
11793         if (appData.debugMode)
11794           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11795         fromX = moveType == WhiteDrop ?
11796           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11797         (int) CharToPiece(ToLower(currentMoveString[0]));
11798         fromY = DROP_RANK;
11799         toX = currentMoveString[2] - AAA;
11800         toY = currentMoveString[3] - ONE;
11801         break;
11802
11803       case WhiteWins:
11804       case BlackWins:
11805       case GameIsDrawn:
11806       case GameUnfinished:
11807         if (appData.debugMode)
11808           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11809         p = strchr(yy_text, '{');
11810         if (p == NULL) p = strchr(yy_text, '(');
11811         if (p == NULL) {
11812             p = yy_text;
11813             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11814         } else {
11815             q = strchr(p, *p == '{' ? '}' : ')');
11816             if (q != NULL) *q = NULLCHAR;
11817             p++;
11818         }
11819         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11820         GameEnds(moveType, p, GE_FILE);
11821         done = TRUE;
11822         if (cmailMsgLoaded) {
11823             ClearHighlights();
11824             flipView = WhiteOnMove(currentMove);
11825             if (moveType == GameUnfinished) flipView = !flipView;
11826             if (appData.debugMode)
11827               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11828         }
11829         break;
11830
11831       case EndOfFile:
11832         if (appData.debugMode)
11833           fprintf(debugFP, "Parser hit end of file\n");
11834         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11835           case MT_NONE:
11836           case MT_CHECK:
11837             break;
11838           case MT_CHECKMATE:
11839           case MT_STAINMATE:
11840             if (WhiteOnMove(currentMove)) {
11841                 GameEnds(BlackWins, "Black mates", GE_FILE);
11842             } else {
11843                 GameEnds(WhiteWins, "White mates", GE_FILE);
11844             }
11845             break;
11846           case MT_STALEMATE:
11847             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11848             break;
11849         }
11850         done = TRUE;
11851         break;
11852
11853       case MoveNumberOne:
11854         if (lastLoadGameStart == GNUChessGame) {
11855             /* GNUChessGames have numbers, but they aren't move numbers */
11856             if (appData.debugMode)
11857               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11858                       yy_text, (int) moveType);
11859             return LoadGameOneMove(EndOfFile); /* tail recursion */
11860         }
11861         /* else fall thru */
11862
11863       case XBoardGame:
11864       case GNUChessGame:
11865       case PGNTag:
11866         /* Reached start of next game in file */
11867         if (appData.debugMode)
11868           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11869         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11870           case MT_NONE:
11871           case MT_CHECK:
11872             break;
11873           case MT_CHECKMATE:
11874           case MT_STAINMATE:
11875             if (WhiteOnMove(currentMove)) {
11876                 GameEnds(BlackWins, "Black mates", GE_FILE);
11877             } else {
11878                 GameEnds(WhiteWins, "White mates", GE_FILE);
11879             }
11880             break;
11881           case MT_STALEMATE:
11882             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11883             break;
11884         }
11885         done = TRUE;
11886         break;
11887
11888       case PositionDiagram:     /* should not happen; ignore */
11889       case ElapsedTime:         /* ignore */
11890       case NAG:                 /* ignore */
11891         if (appData.debugMode)
11892           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11893                   yy_text, (int) moveType);
11894         return LoadGameOneMove(EndOfFile); /* tail recursion */
11895
11896       case IllegalMove:
11897         if (appData.testLegality) {
11898             if (appData.debugMode)
11899               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11900             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11901                     (forwardMostMove / 2) + 1,
11902                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11903             DisplayError(move, 0);
11904             done = TRUE;
11905         } else {
11906             if (appData.debugMode)
11907               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11908                       yy_text, currentMoveString);
11909             fromX = currentMoveString[0] - AAA;
11910             fromY = currentMoveString[1] - ONE;
11911             toX = currentMoveString[2] - AAA;
11912             toY = currentMoveString[3] - ONE;
11913             promoChar = currentMoveString[4];
11914         }
11915         break;
11916
11917       case AmbiguousMove:
11918         if (appData.debugMode)
11919           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11920         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11921                 (forwardMostMove / 2) + 1,
11922                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11923         DisplayError(move, 0);
11924         done = TRUE;
11925         break;
11926
11927       default:
11928       case ImpossibleMove:
11929         if (appData.debugMode)
11930           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11931         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11932                 (forwardMostMove / 2) + 1,
11933                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11934         DisplayError(move, 0);
11935         done = TRUE;
11936         break;
11937     }
11938
11939     if (done) {
11940         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11941             DrawPosition(FALSE, boards[currentMove]);
11942             DisplayBothClocks();
11943             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11944               DisplayComment(currentMove - 1, commentList[currentMove]);
11945         }
11946         (void) StopLoadGameTimer();
11947         gameFileFP = NULL;
11948         cmailOldMove = forwardMostMove;
11949         return FALSE;
11950     } else {
11951         /* currentMoveString is set as a side-effect of yylex */
11952
11953         thinkOutput[0] = NULLCHAR;
11954         MakeMove(fromX, fromY, toX, toY, promoChar);
11955         killX = killY = -1; // [HGM] lion: used up
11956         currentMove = forwardMostMove;
11957         return TRUE;
11958     }
11959 }
11960
11961 /* Load the nth game from the given file */
11962 int
11963 LoadGameFromFile (char *filename, int n, char *title, int useList)
11964 {
11965     FILE *f;
11966     char buf[MSG_SIZ];
11967
11968     if (strcmp(filename, "-") == 0) {
11969         f = stdin;
11970         title = "stdin";
11971     } else {
11972         f = fopen(filename, "rb");
11973         if (f == NULL) {
11974           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11975             DisplayError(buf, errno);
11976             return FALSE;
11977         }
11978     }
11979     if (fseek(f, 0, 0) == -1) {
11980         /* f is not seekable; probably a pipe */
11981         useList = FALSE;
11982     }
11983     if (useList && n == 0) {
11984         int error = GameListBuild(f);
11985         if (error) {
11986             DisplayError(_("Cannot build game list"), error);
11987         } else if (!ListEmpty(&gameList) &&
11988                    ((ListGame *) gameList.tailPred)->number > 1) {
11989             GameListPopUp(f, title);
11990             return TRUE;
11991         }
11992         GameListDestroy();
11993         n = 1;
11994     }
11995     if (n == 0) n = 1;
11996     return LoadGame(f, n, title, FALSE);
11997 }
11998
11999
12000 void
12001 MakeRegisteredMove ()
12002 {
12003     int fromX, fromY, toX, toY;
12004     char promoChar;
12005     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12006         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12007           case CMAIL_MOVE:
12008           case CMAIL_DRAW:
12009             if (appData.debugMode)
12010               fprintf(debugFP, "Restoring %s for game %d\n",
12011                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12012
12013             thinkOutput[0] = NULLCHAR;
12014             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12015             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12016             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12017             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12018             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12019             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12020             MakeMove(fromX, fromY, toX, toY, promoChar);
12021             ShowMove(fromX, fromY, toX, toY);
12022
12023             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12024               case MT_NONE:
12025               case MT_CHECK:
12026                 break;
12027
12028               case MT_CHECKMATE:
12029               case MT_STAINMATE:
12030                 if (WhiteOnMove(currentMove)) {
12031                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12032                 } else {
12033                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12034                 }
12035                 break;
12036
12037               case MT_STALEMATE:
12038                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12039                 break;
12040             }
12041
12042             break;
12043
12044           case CMAIL_RESIGN:
12045             if (WhiteOnMove(currentMove)) {
12046                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12047             } else {
12048                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12049             }
12050             break;
12051
12052           case CMAIL_ACCEPT:
12053             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12054             break;
12055
12056           default:
12057             break;
12058         }
12059     }
12060
12061     return;
12062 }
12063
12064 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12065 int
12066 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12067 {
12068     int retVal;
12069
12070     if (gameNumber > nCmailGames) {
12071         DisplayError(_("No more games in this message"), 0);
12072         return FALSE;
12073     }
12074     if (f == lastLoadGameFP) {
12075         int offset = gameNumber - lastLoadGameNumber;
12076         if (offset == 0) {
12077             cmailMsg[0] = NULLCHAR;
12078             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12079                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12080                 nCmailMovesRegistered--;
12081             }
12082             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12083             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12084                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12085             }
12086         } else {
12087             if (! RegisterMove()) return FALSE;
12088         }
12089     }
12090
12091     retVal = LoadGame(f, gameNumber, title, useList);
12092
12093     /* Make move registered during previous look at this game, if any */
12094     MakeRegisteredMove();
12095
12096     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12097         commentList[currentMove]
12098           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12099         DisplayComment(currentMove - 1, commentList[currentMove]);
12100     }
12101
12102     return retVal;
12103 }
12104
12105 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12106 int
12107 ReloadGame (int offset)
12108 {
12109     int gameNumber = lastLoadGameNumber + offset;
12110     if (lastLoadGameFP == NULL) {
12111         DisplayError(_("No game has been loaded yet"), 0);
12112         return FALSE;
12113     }
12114     if (gameNumber <= 0) {
12115         DisplayError(_("Can't back up any further"), 0);
12116         return FALSE;
12117     }
12118     if (cmailMsgLoaded) {
12119         return CmailLoadGame(lastLoadGameFP, gameNumber,
12120                              lastLoadGameTitle, lastLoadGameUseList);
12121     } else {
12122         return LoadGame(lastLoadGameFP, gameNumber,
12123                         lastLoadGameTitle, lastLoadGameUseList);
12124     }
12125 }
12126
12127 int keys[EmptySquare+1];
12128
12129 int
12130 PositionMatches (Board b1, Board b2)
12131 {
12132     int r, f, sum=0;
12133     switch(appData.searchMode) {
12134         case 1: return CompareWithRights(b1, b2);
12135         case 2:
12136             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12137                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12138             }
12139             return TRUE;
12140         case 3:
12141             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12142               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12143                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12144             }
12145             return sum==0;
12146         case 4:
12147             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12148                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12149             }
12150             return sum==0;
12151     }
12152     return TRUE;
12153 }
12154
12155 #define Q_PROMO  4
12156 #define Q_EP     3
12157 #define Q_BCASTL 2
12158 #define Q_WCASTL 1
12159
12160 int pieceList[256], quickBoard[256];
12161 ChessSquare pieceType[256] = { EmptySquare };
12162 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12163 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12164 int soughtTotal, turn;
12165 Boolean epOK, flipSearch;
12166
12167 typedef struct {
12168     unsigned char piece, to;
12169 } Move;
12170
12171 #define DSIZE (250000)
12172
12173 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12174 Move *moveDatabase = initialSpace;
12175 unsigned int movePtr, dataSize = DSIZE;
12176
12177 int
12178 MakePieceList (Board board, int *counts)
12179 {
12180     int r, f, n=Q_PROMO, total=0;
12181     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12182     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12183         int sq = f + (r<<4);
12184         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12185             quickBoard[sq] = ++n;
12186             pieceList[n] = sq;
12187             pieceType[n] = board[r][f];
12188             counts[board[r][f]]++;
12189             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12190             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12191             total++;
12192         }
12193     }
12194     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12195     return total;
12196 }
12197
12198 void
12199 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12200 {
12201     int sq = fromX + (fromY<<4);
12202     int piece = quickBoard[sq], rook;
12203     quickBoard[sq] = 0;
12204     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12205     if(piece == pieceList[1] && fromY == toY) {
12206       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12207         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12208         moveDatabase[movePtr++].piece = Q_WCASTL;
12209         quickBoard[sq] = piece;
12210         piece = quickBoard[from]; quickBoard[from] = 0;
12211         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12212       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12213         quickBoard[sq] = 0; // remove Rook
12214         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12215         moveDatabase[movePtr++].piece = Q_WCASTL;
12216         quickBoard[sq] = pieceList[1]; // put King
12217         piece = rook;
12218         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12219       }
12220     } else
12221     if(piece == pieceList[2] && fromY == toY) {
12222       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12223         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12224         moveDatabase[movePtr++].piece = Q_BCASTL;
12225         quickBoard[sq] = piece;
12226         piece = quickBoard[from]; quickBoard[from] = 0;
12227         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12228       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12229         quickBoard[sq] = 0; // remove Rook
12230         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12231         moveDatabase[movePtr++].piece = Q_BCASTL;
12232         quickBoard[sq] = pieceList[2]; // put King
12233         piece = rook;
12234         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12235       }
12236     } else
12237     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12238         quickBoard[(fromY<<4)+toX] = 0;
12239         moveDatabase[movePtr].piece = Q_EP;
12240         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12241         moveDatabase[movePtr].to = sq;
12242     } else
12243     if(promoPiece != pieceType[piece]) {
12244         moveDatabase[movePtr++].piece = Q_PROMO;
12245         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12246     }
12247     moveDatabase[movePtr].piece = piece;
12248     quickBoard[sq] = piece;
12249     movePtr++;
12250 }
12251
12252 int
12253 PackGame (Board board)
12254 {
12255     Move *newSpace = NULL;
12256     moveDatabase[movePtr].piece = 0; // terminate previous game
12257     if(movePtr > dataSize) {
12258         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12259         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12260         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12261         if(newSpace) {
12262             int i;
12263             Move *p = moveDatabase, *q = newSpace;
12264             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12265             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12266             moveDatabase = newSpace;
12267         } else { // calloc failed, we must be out of memory. Too bad...
12268             dataSize = 0; // prevent calloc events for all subsequent games
12269             return 0;     // and signal this one isn't cached
12270         }
12271     }
12272     movePtr++;
12273     MakePieceList(board, counts);
12274     return movePtr;
12275 }
12276
12277 int
12278 QuickCompare (Board board, int *minCounts, int *maxCounts)
12279 {   // compare according to search mode
12280     int r, f;
12281     switch(appData.searchMode)
12282     {
12283       case 1: // exact position match
12284         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12285         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12286             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12287         }
12288         break;
12289       case 2: // can have extra material on empty squares
12290         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12291             if(board[r][f] == EmptySquare) continue;
12292             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12293         }
12294         break;
12295       case 3: // material with exact Pawn structure
12296         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12297             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12298             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12299         } // fall through to material comparison
12300       case 4: // exact material
12301         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12302         break;
12303       case 6: // material range with given imbalance
12304         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12305         // fall through to range comparison
12306       case 5: // material range
12307         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12308     }
12309     return TRUE;
12310 }
12311
12312 int
12313 QuickScan (Board board, Move *move)
12314 {   // reconstruct game,and compare all positions in it
12315     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12316     do {
12317         int piece = move->piece;
12318         int to = move->to, from = pieceList[piece];
12319         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12320           if(!piece) return -1;
12321           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12322             piece = (++move)->piece;
12323             from = pieceList[piece];
12324             counts[pieceType[piece]]--;
12325             pieceType[piece] = (ChessSquare) move->to;
12326             counts[move->to]++;
12327           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12328             counts[pieceType[quickBoard[to]]]--;
12329             quickBoard[to] = 0; total--;
12330             move++;
12331             continue;
12332           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12333             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12334             from  = pieceList[piece]; // so this must be King
12335             quickBoard[from] = 0;
12336             pieceList[piece] = to;
12337             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12338             quickBoard[from] = 0; // rook
12339             quickBoard[to] = piece;
12340             to = move->to; piece = move->piece;
12341             goto aftercastle;
12342           }
12343         }
12344         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12345         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12346         quickBoard[from] = 0;
12347       aftercastle:
12348         quickBoard[to] = piece;
12349         pieceList[piece] = to;
12350         cnt++; turn ^= 3;
12351         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12352            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12353            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12354                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12355           ) {
12356             static int lastCounts[EmptySquare+1];
12357             int i;
12358             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12359             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12360         } else stretch = 0;
12361         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12362         move++;
12363     } while(1);
12364 }
12365
12366 void
12367 InitSearch ()
12368 {
12369     int r, f;
12370     flipSearch = FALSE;
12371     CopyBoard(soughtBoard, boards[currentMove]);
12372     soughtTotal = MakePieceList(soughtBoard, maxSought);
12373     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12374     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12375     CopyBoard(reverseBoard, boards[currentMove]);
12376     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12377         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12378         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12379         reverseBoard[r][f] = piece;
12380     }
12381     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12382     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12383     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12384                  || (boards[currentMove][CASTLING][2] == NoRights ||
12385                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12386                  && (boards[currentMove][CASTLING][5] == NoRights ||
12387                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12388       ) {
12389         flipSearch = TRUE;
12390         CopyBoard(flipBoard, soughtBoard);
12391         CopyBoard(rotateBoard, reverseBoard);
12392         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12393             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12394             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12395         }
12396     }
12397     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12398     if(appData.searchMode >= 5) {
12399         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12400         MakePieceList(soughtBoard, minSought);
12401         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12402     }
12403     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12404         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12405 }
12406
12407 GameInfo dummyInfo;
12408 static int creatingBook;
12409
12410 int
12411 GameContainsPosition (FILE *f, ListGame *lg)
12412 {
12413     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12414     int fromX, fromY, toX, toY;
12415     char promoChar;
12416     static int initDone=FALSE;
12417
12418     // weed out games based on numerical tag comparison
12419     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12420     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12421     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12422     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12423     if(!initDone) {
12424         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12425         initDone = TRUE;
12426     }
12427     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12428     else CopyBoard(boards[scratch], initialPosition); // default start position
12429     if(lg->moves) {
12430         turn = btm + 1;
12431         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12432         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12433     }
12434     if(btm) plyNr++;
12435     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12436     fseek(f, lg->offset, 0);
12437     yynewfile(f);
12438     while(1) {
12439         yyboardindex = scratch;
12440         quickFlag = plyNr+1;
12441         next = Myylex();
12442         quickFlag = 0;
12443         switch(next) {
12444             case PGNTag:
12445                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12446             default:
12447                 continue;
12448
12449             case XBoardGame:
12450             case GNUChessGame:
12451                 if(plyNr) return -1; // after we have seen moves, this is for new game
12452               continue;
12453
12454             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12455             case ImpossibleMove:
12456             case WhiteWins: // game ends here with these four
12457             case BlackWins:
12458             case GameIsDrawn:
12459             case GameUnfinished:
12460                 return -1;
12461
12462             case IllegalMove:
12463                 if(appData.testLegality) return -1;
12464             case WhiteCapturesEnPassant:
12465             case BlackCapturesEnPassant:
12466             case WhitePromotion:
12467             case BlackPromotion:
12468             case WhiteNonPromotion:
12469             case BlackNonPromotion:
12470             case NormalMove:
12471             case FirstLeg:
12472             case WhiteKingSideCastle:
12473             case WhiteQueenSideCastle:
12474             case BlackKingSideCastle:
12475             case BlackQueenSideCastle:
12476             case WhiteKingSideCastleWild:
12477             case WhiteQueenSideCastleWild:
12478             case BlackKingSideCastleWild:
12479             case BlackQueenSideCastleWild:
12480             case WhiteHSideCastleFR:
12481             case WhiteASideCastleFR:
12482             case BlackHSideCastleFR:
12483             case BlackASideCastleFR:
12484                 fromX = currentMoveString[0] - AAA;
12485                 fromY = currentMoveString[1] - ONE;
12486                 toX = currentMoveString[2] - AAA;
12487                 toY = currentMoveString[3] - ONE;
12488                 promoChar = currentMoveString[4];
12489                 break;
12490             case WhiteDrop:
12491             case BlackDrop:
12492                 fromX = next == WhiteDrop ?
12493                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12494                   (int) CharToPiece(ToLower(currentMoveString[0]));
12495                 fromY = DROP_RANK;
12496                 toX = currentMoveString[2] - AAA;
12497                 toY = currentMoveString[3] - ONE;
12498                 promoChar = 0;
12499                 break;
12500         }
12501         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12502         plyNr++;
12503         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12504         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12505         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12506         if(appData.findMirror) {
12507             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12508             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12509         }
12510     }
12511 }
12512
12513 /* Load the nth game from open file f */
12514 int
12515 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12516 {
12517     ChessMove cm;
12518     char buf[MSG_SIZ];
12519     int gn = gameNumber;
12520     ListGame *lg = NULL;
12521     int numPGNTags = 0;
12522     int err, pos = -1;
12523     GameMode oldGameMode;
12524     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12525
12526     if (appData.debugMode)
12527         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12528
12529     if (gameMode == Training )
12530         SetTrainingModeOff();
12531
12532     oldGameMode = gameMode;
12533     if (gameMode != BeginningOfGame) {
12534       Reset(FALSE, TRUE);
12535     }
12536     killX = killY = -1; // [HGM] lion: in case we did not Reset
12537
12538     gameFileFP = f;
12539     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12540         fclose(lastLoadGameFP);
12541     }
12542
12543     if (useList) {
12544         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12545
12546         if (lg) {
12547             fseek(f, lg->offset, 0);
12548             GameListHighlight(gameNumber);
12549             pos = lg->position;
12550             gn = 1;
12551         }
12552         else {
12553             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12554               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12555             else
12556             DisplayError(_("Game number out of range"), 0);
12557             return FALSE;
12558         }
12559     } else {
12560         GameListDestroy();
12561         if (fseek(f, 0, 0) == -1) {
12562             if (f == lastLoadGameFP ?
12563                 gameNumber == lastLoadGameNumber + 1 :
12564                 gameNumber == 1) {
12565                 gn = 1;
12566             } else {
12567                 DisplayError(_("Can't seek on game file"), 0);
12568                 return FALSE;
12569             }
12570         }
12571     }
12572     lastLoadGameFP = f;
12573     lastLoadGameNumber = gameNumber;
12574     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12575     lastLoadGameUseList = useList;
12576
12577     yynewfile(f);
12578
12579     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12580       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12581                 lg->gameInfo.black);
12582             DisplayTitle(buf);
12583     } else if (*title != NULLCHAR) {
12584         if (gameNumber > 1) {
12585           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12586             DisplayTitle(buf);
12587         } else {
12588             DisplayTitle(title);
12589         }
12590     }
12591
12592     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12593         gameMode = PlayFromGameFile;
12594         ModeHighlight();
12595     }
12596
12597     currentMove = forwardMostMove = backwardMostMove = 0;
12598     CopyBoard(boards[0], initialPosition);
12599     StopClocks();
12600
12601     /*
12602      * Skip the first gn-1 games in the file.
12603      * Also skip over anything that precedes an identifiable
12604      * start of game marker, to avoid being confused by
12605      * garbage at the start of the file.  Currently
12606      * recognized start of game markers are the move number "1",
12607      * the pattern "gnuchess .* game", the pattern
12608      * "^[#;%] [^ ]* game file", and a PGN tag block.
12609      * A game that starts with one of the latter two patterns
12610      * will also have a move number 1, possibly
12611      * following a position diagram.
12612      * 5-4-02: Let's try being more lenient and allowing a game to
12613      * start with an unnumbered move.  Does that break anything?
12614      */
12615     cm = lastLoadGameStart = EndOfFile;
12616     while (gn > 0) {
12617         yyboardindex = forwardMostMove;
12618         cm = (ChessMove) Myylex();
12619         switch (cm) {
12620           case EndOfFile:
12621             if (cmailMsgLoaded) {
12622                 nCmailGames = CMAIL_MAX_GAMES - gn;
12623             } else {
12624                 Reset(TRUE, TRUE);
12625                 DisplayError(_("Game not found in file"), 0);
12626             }
12627             return FALSE;
12628
12629           case GNUChessGame:
12630           case XBoardGame:
12631             gn--;
12632             lastLoadGameStart = cm;
12633             break;
12634
12635           case MoveNumberOne:
12636             switch (lastLoadGameStart) {
12637               case GNUChessGame:
12638               case XBoardGame:
12639               case PGNTag:
12640                 break;
12641               case MoveNumberOne:
12642               case EndOfFile:
12643                 gn--;           /* count this game */
12644                 lastLoadGameStart = cm;
12645                 break;
12646               default:
12647                 /* impossible */
12648                 break;
12649             }
12650             break;
12651
12652           case PGNTag:
12653             switch (lastLoadGameStart) {
12654               case GNUChessGame:
12655               case PGNTag:
12656               case MoveNumberOne:
12657               case EndOfFile:
12658                 gn--;           /* count this game */
12659                 lastLoadGameStart = cm;
12660                 break;
12661               case XBoardGame:
12662                 lastLoadGameStart = cm; /* game counted already */
12663                 break;
12664               default:
12665                 /* impossible */
12666                 break;
12667             }
12668             if (gn > 0) {
12669                 do {
12670                     yyboardindex = forwardMostMove;
12671                     cm = (ChessMove) Myylex();
12672                 } while (cm == PGNTag || cm == Comment);
12673             }
12674             break;
12675
12676           case WhiteWins:
12677           case BlackWins:
12678           case GameIsDrawn:
12679             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12680                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12681                     != CMAIL_OLD_RESULT) {
12682                     nCmailResults ++ ;
12683                     cmailResult[  CMAIL_MAX_GAMES
12684                                 - gn - 1] = CMAIL_OLD_RESULT;
12685                 }
12686             }
12687             break;
12688
12689           case NormalMove:
12690           case FirstLeg:
12691             /* Only a NormalMove can be at the start of a game
12692              * without a position diagram. */
12693             if (lastLoadGameStart == EndOfFile ) {
12694               gn--;
12695               lastLoadGameStart = MoveNumberOne;
12696             }
12697             break;
12698
12699           default:
12700             break;
12701         }
12702     }
12703
12704     if (appData.debugMode)
12705       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12706
12707     if (cm == XBoardGame) {
12708         /* Skip any header junk before position diagram and/or move 1 */
12709         for (;;) {
12710             yyboardindex = forwardMostMove;
12711             cm = (ChessMove) Myylex();
12712
12713             if (cm == EndOfFile ||
12714                 cm == GNUChessGame || cm == XBoardGame) {
12715                 /* Empty game; pretend end-of-file and handle later */
12716                 cm = EndOfFile;
12717                 break;
12718             }
12719
12720             if (cm == MoveNumberOne || cm == PositionDiagram ||
12721                 cm == PGNTag || cm == Comment)
12722               break;
12723         }
12724     } else if (cm == GNUChessGame) {
12725         if (gameInfo.event != NULL) {
12726             free(gameInfo.event);
12727         }
12728         gameInfo.event = StrSave(yy_text);
12729     }
12730
12731     startedFromSetupPosition = FALSE;
12732     while (cm == PGNTag) {
12733         if (appData.debugMode)
12734           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12735         err = ParsePGNTag(yy_text, &gameInfo);
12736         if (!err) numPGNTags++;
12737
12738         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12739         if(gameInfo.variant != oldVariant) {
12740             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12741             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12742             InitPosition(TRUE);
12743             oldVariant = gameInfo.variant;
12744             if (appData.debugMode)
12745               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12746         }
12747
12748
12749         if (gameInfo.fen != NULL) {
12750           Board initial_position;
12751           startedFromSetupPosition = TRUE;
12752           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12753             Reset(TRUE, TRUE);
12754             DisplayError(_("Bad FEN position in file"), 0);
12755             return FALSE;
12756           }
12757           CopyBoard(boards[0], initial_position);
12758           if (blackPlaysFirst) {
12759             currentMove = forwardMostMove = backwardMostMove = 1;
12760             CopyBoard(boards[1], initial_position);
12761             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12762             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12763             timeRemaining[0][1] = whiteTimeRemaining;
12764             timeRemaining[1][1] = blackTimeRemaining;
12765             if (commentList[0] != NULL) {
12766               commentList[1] = commentList[0];
12767               commentList[0] = NULL;
12768             }
12769           } else {
12770             currentMove = forwardMostMove = backwardMostMove = 0;
12771           }
12772           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12773           {   int i;
12774               initialRulePlies = FENrulePlies;
12775               for( i=0; i< nrCastlingRights; i++ )
12776                   initialRights[i] = initial_position[CASTLING][i];
12777           }
12778           yyboardindex = forwardMostMove;
12779           free(gameInfo.fen);
12780           gameInfo.fen = NULL;
12781         }
12782
12783         yyboardindex = forwardMostMove;
12784         cm = (ChessMove) Myylex();
12785
12786         /* Handle comments interspersed among the tags */
12787         while (cm == Comment) {
12788             char *p;
12789             if (appData.debugMode)
12790               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12791             p = yy_text;
12792             AppendComment(currentMove, p, FALSE);
12793             yyboardindex = forwardMostMove;
12794             cm = (ChessMove) Myylex();
12795         }
12796     }
12797
12798     /* don't rely on existence of Event tag since if game was
12799      * pasted from clipboard the Event tag may not exist
12800      */
12801     if (numPGNTags > 0){
12802         char *tags;
12803         if (gameInfo.variant == VariantNormal) {
12804           VariantClass v = StringToVariant(gameInfo.event);
12805           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12806           if(v < VariantShogi) gameInfo.variant = v;
12807         }
12808         if (!matchMode) {
12809           if( appData.autoDisplayTags ) {
12810             tags = PGNTags(&gameInfo);
12811             TagsPopUp(tags, CmailMsg());
12812             free(tags);
12813           }
12814         }
12815     } else {
12816         /* Make something up, but don't display it now */
12817         SetGameInfo();
12818         TagsPopDown();
12819     }
12820
12821     if (cm == PositionDiagram) {
12822         int i, j;
12823         char *p;
12824         Board initial_position;
12825
12826         if (appData.debugMode)
12827           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12828
12829         if (!startedFromSetupPosition) {
12830             p = yy_text;
12831             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12832               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12833                 switch (*p) {
12834                   case '{':
12835                   case '[':
12836                   case '-':
12837                   case ' ':
12838                   case '\t':
12839                   case '\n':
12840                   case '\r':
12841                     break;
12842                   default:
12843                     initial_position[i][j++] = CharToPiece(*p);
12844                     break;
12845                 }
12846             while (*p == ' ' || *p == '\t' ||
12847                    *p == '\n' || *p == '\r') p++;
12848
12849             if (strncmp(p, "black", strlen("black"))==0)
12850               blackPlaysFirst = TRUE;
12851             else
12852               blackPlaysFirst = FALSE;
12853             startedFromSetupPosition = TRUE;
12854
12855             CopyBoard(boards[0], initial_position);
12856             if (blackPlaysFirst) {
12857                 currentMove = forwardMostMove = backwardMostMove = 1;
12858                 CopyBoard(boards[1], initial_position);
12859                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12860                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12861                 timeRemaining[0][1] = whiteTimeRemaining;
12862                 timeRemaining[1][1] = blackTimeRemaining;
12863                 if (commentList[0] != NULL) {
12864                     commentList[1] = commentList[0];
12865                     commentList[0] = NULL;
12866                 }
12867             } else {
12868                 currentMove = forwardMostMove = backwardMostMove = 0;
12869             }
12870         }
12871         yyboardindex = forwardMostMove;
12872         cm = (ChessMove) Myylex();
12873     }
12874
12875   if(!creatingBook) {
12876     if (first.pr == NoProc) {
12877         StartChessProgram(&first);
12878     }
12879     InitChessProgram(&first, FALSE);
12880     SendToProgram("force\n", &first);
12881     if (startedFromSetupPosition) {
12882         SendBoard(&first, forwardMostMove);
12883     if (appData.debugMode) {
12884         fprintf(debugFP, "Load Game\n");
12885     }
12886         DisplayBothClocks();
12887     }
12888   }
12889
12890     /* [HGM] server: flag to write setup moves in broadcast file as one */
12891     loadFlag = appData.suppressLoadMoves;
12892
12893     while (cm == Comment) {
12894         char *p;
12895         if (appData.debugMode)
12896           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12897         p = yy_text;
12898         AppendComment(currentMove, p, FALSE);
12899         yyboardindex = forwardMostMove;
12900         cm = (ChessMove) Myylex();
12901     }
12902
12903     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12904         cm == WhiteWins || cm == BlackWins ||
12905         cm == GameIsDrawn || cm == GameUnfinished) {
12906         DisplayMessage("", _("No moves in game"));
12907         if (cmailMsgLoaded) {
12908             if (appData.debugMode)
12909               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12910             ClearHighlights();
12911             flipView = FALSE;
12912         }
12913         DrawPosition(FALSE, boards[currentMove]);
12914         DisplayBothClocks();
12915         gameMode = EditGame;
12916         ModeHighlight();
12917         gameFileFP = NULL;
12918         cmailOldMove = 0;
12919         return TRUE;
12920     }
12921
12922     // [HGM] PV info: routine tests if comment empty
12923     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12924         DisplayComment(currentMove - 1, commentList[currentMove]);
12925     }
12926     if (!matchMode && appData.timeDelay != 0)
12927       DrawPosition(FALSE, boards[currentMove]);
12928
12929     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12930       programStats.ok_to_send = 1;
12931     }
12932
12933     /* if the first token after the PGN tags is a move
12934      * and not move number 1, retrieve it from the parser
12935      */
12936     if (cm != MoveNumberOne)
12937         LoadGameOneMove(cm);
12938
12939     /* load the remaining moves from the file */
12940     while (LoadGameOneMove(EndOfFile)) {
12941       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12942       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12943     }
12944
12945     /* rewind to the start of the game */
12946     currentMove = backwardMostMove;
12947
12948     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12949
12950     if (oldGameMode == AnalyzeFile) {
12951       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12952       AnalyzeFileEvent();
12953     } else
12954     if (oldGameMode == AnalyzeMode) {
12955       AnalyzeFileEvent();
12956     }
12957
12958     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12959         long int w, b; // [HGM] adjourn: restore saved clock times
12960         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12961         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12962             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12963             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12964         }
12965     }
12966
12967     if(creatingBook) return TRUE;
12968     if (!matchMode && pos > 0) {
12969         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12970     } else
12971     if (matchMode || appData.timeDelay == 0) {
12972       ToEndEvent();
12973     } else if (appData.timeDelay > 0) {
12974       AutoPlayGameLoop();
12975     }
12976
12977     if (appData.debugMode)
12978         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12979
12980     loadFlag = 0; /* [HGM] true game starts */
12981     return TRUE;
12982 }
12983
12984 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12985 int
12986 ReloadPosition (int offset)
12987 {
12988     int positionNumber = lastLoadPositionNumber + offset;
12989     if (lastLoadPositionFP == NULL) {
12990         DisplayError(_("No position has been loaded yet"), 0);
12991         return FALSE;
12992     }
12993     if (positionNumber <= 0) {
12994         DisplayError(_("Can't back up any further"), 0);
12995         return FALSE;
12996     }
12997     return LoadPosition(lastLoadPositionFP, positionNumber,
12998                         lastLoadPositionTitle);
12999 }
13000
13001 /* Load the nth position from the given file */
13002 int
13003 LoadPositionFromFile (char *filename, int n, char *title)
13004 {
13005     FILE *f;
13006     char buf[MSG_SIZ];
13007
13008     if (strcmp(filename, "-") == 0) {
13009         return LoadPosition(stdin, n, "stdin");
13010     } else {
13011         f = fopen(filename, "rb");
13012         if (f == NULL) {
13013             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13014             DisplayError(buf, errno);
13015             return FALSE;
13016         } else {
13017             return LoadPosition(f, n, title);
13018         }
13019     }
13020 }
13021
13022 /* Load the nth position from the given open file, and close it */
13023 int
13024 LoadPosition (FILE *f, int positionNumber, char *title)
13025 {
13026     char *p, line[MSG_SIZ];
13027     Board initial_position;
13028     int i, j, fenMode, pn;
13029
13030     if (gameMode == Training )
13031         SetTrainingModeOff();
13032
13033     if (gameMode != BeginningOfGame) {
13034         Reset(FALSE, TRUE);
13035     }
13036     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13037         fclose(lastLoadPositionFP);
13038     }
13039     if (positionNumber == 0) positionNumber = 1;
13040     lastLoadPositionFP = f;
13041     lastLoadPositionNumber = positionNumber;
13042     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13043     if (first.pr == NoProc && !appData.noChessProgram) {
13044       StartChessProgram(&first);
13045       InitChessProgram(&first, FALSE);
13046     }
13047     pn = positionNumber;
13048     if (positionNumber < 0) {
13049         /* Negative position number means to seek to that byte offset */
13050         if (fseek(f, -positionNumber, 0) == -1) {
13051             DisplayError(_("Can't seek on position file"), 0);
13052             return FALSE;
13053         };
13054         pn = 1;
13055     } else {
13056         if (fseek(f, 0, 0) == -1) {
13057             if (f == lastLoadPositionFP ?
13058                 positionNumber == lastLoadPositionNumber + 1 :
13059                 positionNumber == 1) {
13060                 pn = 1;
13061             } else {
13062                 DisplayError(_("Can't seek on position file"), 0);
13063                 return FALSE;
13064             }
13065         }
13066     }
13067     /* See if this file is FEN or old-style xboard */
13068     if (fgets(line, MSG_SIZ, f) == NULL) {
13069         DisplayError(_("Position not found in file"), 0);
13070         return FALSE;
13071     }
13072     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13073     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13074
13075     if (pn >= 2) {
13076         if (fenMode || line[0] == '#') pn--;
13077         while (pn > 0) {
13078             /* skip positions before number pn */
13079             if (fgets(line, MSG_SIZ, f) == NULL) {
13080                 Reset(TRUE, TRUE);
13081                 DisplayError(_("Position not found in file"), 0);
13082                 return FALSE;
13083             }
13084             if (fenMode || line[0] == '#') pn--;
13085         }
13086     }
13087
13088     if (fenMode) {
13089         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13090             DisplayError(_("Bad FEN position in file"), 0);
13091             return FALSE;
13092         }
13093     } else {
13094         (void) fgets(line, MSG_SIZ, f);
13095         (void) fgets(line, MSG_SIZ, f);
13096
13097         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13098             (void) fgets(line, MSG_SIZ, f);
13099             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13100                 if (*p == ' ')
13101                   continue;
13102                 initial_position[i][j++] = CharToPiece(*p);
13103             }
13104         }
13105
13106         blackPlaysFirst = FALSE;
13107         if (!feof(f)) {
13108             (void) fgets(line, MSG_SIZ, f);
13109             if (strncmp(line, "black", strlen("black"))==0)
13110               blackPlaysFirst = TRUE;
13111         }
13112     }
13113     startedFromSetupPosition = TRUE;
13114
13115     CopyBoard(boards[0], initial_position);
13116     if (blackPlaysFirst) {
13117         currentMove = forwardMostMove = backwardMostMove = 1;
13118         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13119         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13120         CopyBoard(boards[1], initial_position);
13121         DisplayMessage("", _("Black to play"));
13122     } else {
13123         currentMove = forwardMostMove = backwardMostMove = 0;
13124         DisplayMessage("", _("White to play"));
13125     }
13126     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13127     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13128         SendToProgram("force\n", &first);
13129         SendBoard(&first, forwardMostMove);
13130     }
13131     if (appData.debugMode) {
13132 int i, j;
13133   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13134   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13135         fprintf(debugFP, "Load Position\n");
13136     }
13137
13138     if (positionNumber > 1) {
13139       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13140         DisplayTitle(line);
13141     } else {
13142         DisplayTitle(title);
13143     }
13144     gameMode = EditGame;
13145     ModeHighlight();
13146     ResetClocks();
13147     timeRemaining[0][1] = whiteTimeRemaining;
13148     timeRemaining[1][1] = blackTimeRemaining;
13149     DrawPosition(FALSE, boards[currentMove]);
13150
13151     return TRUE;
13152 }
13153
13154
13155 void
13156 CopyPlayerNameIntoFileName (char **dest, char *src)
13157 {
13158     while (*src != NULLCHAR && *src != ',') {
13159         if (*src == ' ') {
13160             *(*dest)++ = '_';
13161             src++;
13162         } else {
13163             *(*dest)++ = *src++;
13164         }
13165     }
13166 }
13167
13168 char *
13169 DefaultFileName (char *ext)
13170 {
13171     static char def[MSG_SIZ];
13172     char *p;
13173
13174     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13175         p = def;
13176         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13177         *p++ = '-';
13178         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13179         *p++ = '.';
13180         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13181     } else {
13182         def[0] = NULLCHAR;
13183     }
13184     return def;
13185 }
13186
13187 /* Save the current game to the given file */
13188 int
13189 SaveGameToFile (char *filename, int append)
13190 {
13191     FILE *f;
13192     char buf[MSG_SIZ];
13193     int result, i, t,tot=0;
13194
13195     if (strcmp(filename, "-") == 0) {
13196         return SaveGame(stdout, 0, NULL);
13197     } else {
13198         for(i=0; i<10; i++) { // upto 10 tries
13199              f = fopen(filename, append ? "a" : "w");
13200              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13201              if(f || errno != 13) break;
13202              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13203              tot += t;
13204         }
13205         if (f == NULL) {
13206             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13207             DisplayError(buf, errno);
13208             return FALSE;
13209         } else {
13210             safeStrCpy(buf, lastMsg, MSG_SIZ);
13211             DisplayMessage(_("Waiting for access to save file"), "");
13212             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13213             DisplayMessage(_("Saving game"), "");
13214             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13215             result = SaveGame(f, 0, NULL);
13216             DisplayMessage(buf, "");
13217             return result;
13218         }
13219     }
13220 }
13221
13222 char *
13223 SavePart (char *str)
13224 {
13225     static char buf[MSG_SIZ];
13226     char *p;
13227
13228     p = strchr(str, ' ');
13229     if (p == NULL) return str;
13230     strncpy(buf, str, p - str);
13231     buf[p - str] = NULLCHAR;
13232     return buf;
13233 }
13234
13235 #define PGN_MAX_LINE 75
13236
13237 #define PGN_SIDE_WHITE  0
13238 #define PGN_SIDE_BLACK  1
13239
13240 static int
13241 FindFirstMoveOutOfBook (int side)
13242 {
13243     int result = -1;
13244
13245     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13246         int index = backwardMostMove;
13247         int has_book_hit = 0;
13248
13249         if( (index % 2) != side ) {
13250             index++;
13251         }
13252
13253         while( index < forwardMostMove ) {
13254             /* Check to see if engine is in book */
13255             int depth = pvInfoList[index].depth;
13256             int score = pvInfoList[index].score;
13257             int in_book = 0;
13258
13259             if( depth <= 2 ) {
13260                 in_book = 1;
13261             }
13262             else if( score == 0 && depth == 63 ) {
13263                 in_book = 1; /* Zappa */
13264             }
13265             else if( score == 2 && depth == 99 ) {
13266                 in_book = 1; /* Abrok */
13267             }
13268
13269             has_book_hit += in_book;
13270
13271             if( ! in_book ) {
13272                 result = index;
13273
13274                 break;
13275             }
13276
13277             index += 2;
13278         }
13279     }
13280
13281     return result;
13282 }
13283
13284 void
13285 GetOutOfBookInfo (char * buf)
13286 {
13287     int oob[2];
13288     int i;
13289     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13290
13291     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13292     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13293
13294     *buf = '\0';
13295
13296     if( oob[0] >= 0 || oob[1] >= 0 ) {
13297         for( i=0; i<2; i++ ) {
13298             int idx = oob[i];
13299
13300             if( idx >= 0 ) {
13301                 if( i > 0 && oob[0] >= 0 ) {
13302                     strcat( buf, "   " );
13303                 }
13304
13305                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13306                 sprintf( buf+strlen(buf), "%s%.2f",
13307                     pvInfoList[idx].score >= 0 ? "+" : "",
13308                     pvInfoList[idx].score / 100.0 );
13309             }
13310         }
13311     }
13312 }
13313
13314 /* Save game in PGN style and close the file */
13315 int
13316 SaveGamePGN (FILE *f)
13317 {
13318     int i, offset, linelen, newblock;
13319 //    char *movetext;
13320     char numtext[32];
13321     int movelen, numlen, blank;
13322     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13323
13324     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13325
13326     PrintPGNTags(f, &gameInfo);
13327
13328     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13329
13330     if (backwardMostMove > 0 || startedFromSetupPosition) {
13331         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13332         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13333         fprintf(f, "\n{--------------\n");
13334         PrintPosition(f, backwardMostMove);
13335         fprintf(f, "--------------}\n");
13336         free(fen);
13337     }
13338     else {
13339         /* [AS] Out of book annotation */
13340         if( appData.saveOutOfBookInfo ) {
13341             char buf[64];
13342
13343             GetOutOfBookInfo( buf );
13344
13345             if( buf[0] != '\0' ) {
13346                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13347             }
13348         }
13349
13350         fprintf(f, "\n");
13351     }
13352
13353     i = backwardMostMove;
13354     linelen = 0;
13355     newblock = TRUE;
13356
13357     while (i < forwardMostMove) {
13358         /* Print comments preceding this move */
13359         if (commentList[i] != NULL) {
13360             if (linelen > 0) fprintf(f, "\n");
13361             fprintf(f, "%s", commentList[i]);
13362             linelen = 0;
13363             newblock = TRUE;
13364         }
13365
13366         /* Format move number */
13367         if ((i % 2) == 0)
13368           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13369         else
13370           if (newblock)
13371             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13372           else
13373             numtext[0] = NULLCHAR;
13374
13375         numlen = strlen(numtext);
13376         newblock = FALSE;
13377
13378         /* Print move number */
13379         blank = linelen > 0 && numlen > 0;
13380         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13381             fprintf(f, "\n");
13382             linelen = 0;
13383             blank = 0;
13384         }
13385         if (blank) {
13386             fprintf(f, " ");
13387             linelen++;
13388         }
13389         fprintf(f, "%s", numtext);
13390         linelen += numlen;
13391
13392         /* Get move */
13393         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13394         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13395
13396         /* Print move */
13397         blank = linelen > 0 && movelen > 0;
13398         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13399             fprintf(f, "\n");
13400             linelen = 0;
13401             blank = 0;
13402         }
13403         if (blank) {
13404             fprintf(f, " ");
13405             linelen++;
13406         }
13407         fprintf(f, "%s", move_buffer);
13408         linelen += movelen;
13409
13410         /* [AS] Add PV info if present */
13411         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13412             /* [HGM] add time */
13413             char buf[MSG_SIZ]; int seconds;
13414
13415             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13416
13417             if( seconds <= 0)
13418               buf[0] = 0;
13419             else
13420               if( seconds < 30 )
13421                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13422               else
13423                 {
13424                   seconds = (seconds + 4)/10; // round to full seconds
13425                   if( seconds < 60 )
13426                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13427                   else
13428                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13429                 }
13430
13431             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13432                       pvInfoList[i].score >= 0 ? "+" : "",
13433                       pvInfoList[i].score / 100.0,
13434                       pvInfoList[i].depth,
13435                       buf );
13436
13437             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13438
13439             /* Print score/depth */
13440             blank = linelen > 0 && movelen > 0;
13441             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13442                 fprintf(f, "\n");
13443                 linelen = 0;
13444                 blank = 0;
13445             }
13446             if (blank) {
13447                 fprintf(f, " ");
13448                 linelen++;
13449             }
13450             fprintf(f, "%s", move_buffer);
13451             linelen += movelen;
13452         }
13453
13454         i++;
13455     }
13456
13457     /* Start a new line */
13458     if (linelen > 0) fprintf(f, "\n");
13459
13460     /* Print comments after last move */
13461     if (commentList[i] != NULL) {
13462         fprintf(f, "%s\n", commentList[i]);
13463     }
13464
13465     /* Print result */
13466     if (gameInfo.resultDetails != NULL &&
13467         gameInfo.resultDetails[0] != NULLCHAR) {
13468         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13469         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13470            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13471             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13472         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13473     } else {
13474         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13475     }
13476
13477     fclose(f);
13478     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13479     return TRUE;
13480 }
13481
13482 /* Save game in old style and close the file */
13483 int
13484 SaveGameOldStyle (FILE *f)
13485 {
13486     int i, offset;
13487     time_t tm;
13488
13489     tm = time((time_t *) NULL);
13490
13491     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13492     PrintOpponents(f);
13493
13494     if (backwardMostMove > 0 || startedFromSetupPosition) {
13495         fprintf(f, "\n[--------------\n");
13496         PrintPosition(f, backwardMostMove);
13497         fprintf(f, "--------------]\n");
13498     } else {
13499         fprintf(f, "\n");
13500     }
13501
13502     i = backwardMostMove;
13503     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13504
13505     while (i < forwardMostMove) {
13506         if (commentList[i] != NULL) {
13507             fprintf(f, "[%s]\n", commentList[i]);
13508         }
13509
13510         if ((i % 2) == 1) {
13511             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13512             i++;
13513         } else {
13514             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13515             i++;
13516             if (commentList[i] != NULL) {
13517                 fprintf(f, "\n");
13518                 continue;
13519             }
13520             if (i >= forwardMostMove) {
13521                 fprintf(f, "\n");
13522                 break;
13523             }
13524             fprintf(f, "%s\n", parseList[i]);
13525             i++;
13526         }
13527     }
13528
13529     if (commentList[i] != NULL) {
13530         fprintf(f, "[%s]\n", commentList[i]);
13531     }
13532
13533     /* This isn't really the old style, but it's close enough */
13534     if (gameInfo.resultDetails != NULL &&
13535         gameInfo.resultDetails[0] != NULLCHAR) {
13536         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13537                 gameInfo.resultDetails);
13538     } else {
13539         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13540     }
13541
13542     fclose(f);
13543     return TRUE;
13544 }
13545
13546 /* Save the current game to open file f and close the file */
13547 int
13548 SaveGame (FILE *f, int dummy, char *dummy2)
13549 {
13550     if (gameMode == EditPosition) EditPositionDone(TRUE);
13551     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13552     if (appData.oldSaveStyle)
13553       return SaveGameOldStyle(f);
13554     else
13555       return SaveGamePGN(f);
13556 }
13557
13558 /* Save the current position to the given file */
13559 int
13560 SavePositionToFile (char *filename)
13561 {
13562     FILE *f;
13563     char buf[MSG_SIZ];
13564
13565     if (strcmp(filename, "-") == 0) {
13566         return SavePosition(stdout, 0, NULL);
13567     } else {
13568         f = fopen(filename, "a");
13569         if (f == NULL) {
13570             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13571             DisplayError(buf, errno);
13572             return FALSE;
13573         } else {
13574             safeStrCpy(buf, lastMsg, MSG_SIZ);
13575             DisplayMessage(_("Waiting for access to save file"), "");
13576             flock(fileno(f), LOCK_EX); // [HGM] lock
13577             DisplayMessage(_("Saving position"), "");
13578             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13579             SavePosition(f, 0, NULL);
13580             DisplayMessage(buf, "");
13581             return TRUE;
13582         }
13583     }
13584 }
13585
13586 /* Save the current position to the given open file and close the file */
13587 int
13588 SavePosition (FILE *f, int dummy, char *dummy2)
13589 {
13590     time_t tm;
13591     char *fen;
13592
13593     if (gameMode == EditPosition) EditPositionDone(TRUE);
13594     if (appData.oldSaveStyle) {
13595         tm = time((time_t *) NULL);
13596
13597         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13598         PrintOpponents(f);
13599         fprintf(f, "[--------------\n");
13600         PrintPosition(f, currentMove);
13601         fprintf(f, "--------------]\n");
13602     } else {
13603         fen = PositionToFEN(currentMove, NULL, 1);
13604         fprintf(f, "%s\n", fen);
13605         free(fen);
13606     }
13607     fclose(f);
13608     return TRUE;
13609 }
13610
13611 void
13612 ReloadCmailMsgEvent (int unregister)
13613 {
13614 #if !WIN32
13615     static char *inFilename = NULL;
13616     static char *outFilename;
13617     int i;
13618     struct stat inbuf, outbuf;
13619     int status;
13620
13621     /* Any registered moves are unregistered if unregister is set, */
13622     /* i.e. invoked by the signal handler */
13623     if (unregister) {
13624         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13625             cmailMoveRegistered[i] = FALSE;
13626             if (cmailCommentList[i] != NULL) {
13627                 free(cmailCommentList[i]);
13628                 cmailCommentList[i] = NULL;
13629             }
13630         }
13631         nCmailMovesRegistered = 0;
13632     }
13633
13634     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13635         cmailResult[i] = CMAIL_NOT_RESULT;
13636     }
13637     nCmailResults = 0;
13638
13639     if (inFilename == NULL) {
13640         /* Because the filenames are static they only get malloced once  */
13641         /* and they never get freed                                      */
13642         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13643         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13644
13645         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13646         sprintf(outFilename, "%s.out", appData.cmailGameName);
13647     }
13648
13649     status = stat(outFilename, &outbuf);
13650     if (status < 0) {
13651         cmailMailedMove = FALSE;
13652     } else {
13653         status = stat(inFilename, &inbuf);
13654         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13655     }
13656
13657     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13658        counts the games, notes how each one terminated, etc.
13659
13660        It would be nice to remove this kludge and instead gather all
13661        the information while building the game list.  (And to keep it
13662        in the game list nodes instead of having a bunch of fixed-size
13663        parallel arrays.)  Note this will require getting each game's
13664        termination from the PGN tags, as the game list builder does
13665        not process the game moves.  --mann
13666        */
13667     cmailMsgLoaded = TRUE;
13668     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13669
13670     /* Load first game in the file or popup game menu */
13671     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13672
13673 #endif /* !WIN32 */
13674     return;
13675 }
13676
13677 int
13678 RegisterMove ()
13679 {
13680     FILE *f;
13681     char string[MSG_SIZ];
13682
13683     if (   cmailMailedMove
13684         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13685         return TRUE;            /* Allow free viewing  */
13686     }
13687
13688     /* Unregister move to ensure that we don't leave RegisterMove        */
13689     /* with the move registered when the conditions for registering no   */
13690     /* longer hold                                                       */
13691     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13692         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13693         nCmailMovesRegistered --;
13694
13695         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13696           {
13697               free(cmailCommentList[lastLoadGameNumber - 1]);
13698               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13699           }
13700     }
13701
13702     if (cmailOldMove == -1) {
13703         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13704         return FALSE;
13705     }
13706
13707     if (currentMove > cmailOldMove + 1) {
13708         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13709         return FALSE;
13710     }
13711
13712     if (currentMove < cmailOldMove) {
13713         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13714         return FALSE;
13715     }
13716
13717     if (forwardMostMove > currentMove) {
13718         /* Silently truncate extra moves */
13719         TruncateGame();
13720     }
13721
13722     if (   (currentMove == cmailOldMove + 1)
13723         || (   (currentMove == cmailOldMove)
13724             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13725                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13726         if (gameInfo.result != GameUnfinished) {
13727             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13728         }
13729
13730         if (commentList[currentMove] != NULL) {
13731             cmailCommentList[lastLoadGameNumber - 1]
13732               = StrSave(commentList[currentMove]);
13733         }
13734         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13735
13736         if (appData.debugMode)
13737           fprintf(debugFP, "Saving %s for game %d\n",
13738                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13739
13740         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13741
13742         f = fopen(string, "w");
13743         if (appData.oldSaveStyle) {
13744             SaveGameOldStyle(f); /* also closes the file */
13745
13746             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13747             f = fopen(string, "w");
13748             SavePosition(f, 0, NULL); /* also closes the file */
13749         } else {
13750             fprintf(f, "{--------------\n");
13751             PrintPosition(f, currentMove);
13752             fprintf(f, "--------------}\n\n");
13753
13754             SaveGame(f, 0, NULL); /* also closes the file*/
13755         }
13756
13757         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13758         nCmailMovesRegistered ++;
13759     } else if (nCmailGames == 1) {
13760         DisplayError(_("You have not made a move yet"), 0);
13761         return FALSE;
13762     }
13763
13764     return TRUE;
13765 }
13766
13767 void
13768 MailMoveEvent ()
13769 {
13770 #if !WIN32
13771     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13772     FILE *commandOutput;
13773     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13774     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13775     int nBuffers;
13776     int i;
13777     int archived;
13778     char *arcDir;
13779
13780     if (! cmailMsgLoaded) {
13781         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13782         return;
13783     }
13784
13785     if (nCmailGames == nCmailResults) {
13786         DisplayError(_("No unfinished games"), 0);
13787         return;
13788     }
13789
13790 #if CMAIL_PROHIBIT_REMAIL
13791     if (cmailMailedMove) {
13792       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);
13793         DisplayError(msg, 0);
13794         return;
13795     }
13796 #endif
13797
13798     if (! (cmailMailedMove || RegisterMove())) return;
13799
13800     if (   cmailMailedMove
13801         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13802       snprintf(string, MSG_SIZ, partCommandString,
13803                appData.debugMode ? " -v" : "", appData.cmailGameName);
13804         commandOutput = popen(string, "r");
13805
13806         if (commandOutput == NULL) {
13807             DisplayError(_("Failed to invoke cmail"), 0);
13808         } else {
13809             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13810                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13811             }
13812             if (nBuffers > 1) {
13813                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13814                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13815                 nBytes = MSG_SIZ - 1;
13816             } else {
13817                 (void) memcpy(msg, buffer, nBytes);
13818             }
13819             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13820
13821             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13822                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13823
13824                 archived = TRUE;
13825                 for (i = 0; i < nCmailGames; i ++) {
13826                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13827                         archived = FALSE;
13828                     }
13829                 }
13830                 if (   archived
13831                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13832                         != NULL)) {
13833                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13834                            arcDir,
13835                            appData.cmailGameName,
13836                            gameInfo.date);
13837                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13838                     cmailMsgLoaded = FALSE;
13839                 }
13840             }
13841
13842             DisplayInformation(msg);
13843             pclose(commandOutput);
13844         }
13845     } else {
13846         if ((*cmailMsg) != '\0') {
13847             DisplayInformation(cmailMsg);
13848         }
13849     }
13850
13851     return;
13852 #endif /* !WIN32 */
13853 }
13854
13855 char *
13856 CmailMsg ()
13857 {
13858 #if WIN32
13859     return NULL;
13860 #else
13861     int  prependComma = 0;
13862     char number[5];
13863     char string[MSG_SIZ];       /* Space for game-list */
13864     int  i;
13865
13866     if (!cmailMsgLoaded) return "";
13867
13868     if (cmailMailedMove) {
13869       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13870     } else {
13871         /* Create a list of games left */
13872       snprintf(string, MSG_SIZ, "[");
13873         for (i = 0; i < nCmailGames; i ++) {
13874             if (! (   cmailMoveRegistered[i]
13875                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13876                 if (prependComma) {
13877                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13878                 } else {
13879                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13880                     prependComma = 1;
13881                 }
13882
13883                 strcat(string, number);
13884             }
13885         }
13886         strcat(string, "]");
13887
13888         if (nCmailMovesRegistered + nCmailResults == 0) {
13889             switch (nCmailGames) {
13890               case 1:
13891                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13892                 break;
13893
13894               case 2:
13895                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13896                 break;
13897
13898               default:
13899                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13900                          nCmailGames);
13901                 break;
13902             }
13903         } else {
13904             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13905               case 1:
13906                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13907                          string);
13908                 break;
13909
13910               case 0:
13911                 if (nCmailResults == nCmailGames) {
13912                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13913                 } else {
13914                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13915                 }
13916                 break;
13917
13918               default:
13919                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13920                          string);
13921             }
13922         }
13923     }
13924     return cmailMsg;
13925 #endif /* WIN32 */
13926 }
13927
13928 void
13929 ResetGameEvent ()
13930 {
13931     if (gameMode == Training)
13932       SetTrainingModeOff();
13933
13934     Reset(TRUE, TRUE);
13935     cmailMsgLoaded = FALSE;
13936     if (appData.icsActive) {
13937       SendToICS(ics_prefix);
13938       SendToICS("refresh\n");
13939     }
13940 }
13941
13942 void
13943 ExitEvent (int status)
13944 {
13945     exiting++;
13946     if (exiting > 2) {
13947       /* Give up on clean exit */
13948       exit(status);
13949     }
13950     if (exiting > 1) {
13951       /* Keep trying for clean exit */
13952       return;
13953     }
13954
13955     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13956
13957     if (telnetISR != NULL) {
13958       RemoveInputSource(telnetISR);
13959     }
13960     if (icsPR != NoProc) {
13961       DestroyChildProcess(icsPR, TRUE);
13962     }
13963
13964     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13965     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13966
13967     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13968     /* make sure this other one finishes before killing it!                  */
13969     if(endingGame) { int count = 0;
13970         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13971         while(endingGame && count++ < 10) DoSleep(1);
13972         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13973     }
13974
13975     /* Kill off chess programs */
13976     if (first.pr != NoProc) {
13977         ExitAnalyzeMode();
13978
13979         DoSleep( appData.delayBeforeQuit );
13980         SendToProgram("quit\n", &first);
13981         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
13982     }
13983     if (second.pr != NoProc) {
13984         DoSleep( appData.delayBeforeQuit );
13985         SendToProgram("quit\n", &second);
13986         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
13987     }
13988     if (first.isr != NULL) {
13989         RemoveInputSource(first.isr);
13990     }
13991     if (second.isr != NULL) {
13992         RemoveInputSource(second.isr);
13993     }
13994
13995     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13996     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13997
13998     ShutDownFrontEnd();
13999     exit(status);
14000 }
14001
14002 void
14003 PauseEngine (ChessProgramState *cps)
14004 {
14005     SendToProgram("pause\n", cps);
14006     cps->pause = 2;
14007 }
14008
14009 void
14010 UnPauseEngine (ChessProgramState *cps)
14011 {
14012     SendToProgram("resume\n", cps);
14013     cps->pause = 1;
14014 }
14015
14016 void
14017 PauseEvent ()
14018 {
14019     if (appData.debugMode)
14020         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14021     if (pausing) {
14022         pausing = FALSE;
14023         ModeHighlight();
14024         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14025             StartClocks();
14026             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14027                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14028                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14029             }
14030             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14031             HandleMachineMove(stashedInputMove, stalledEngine);
14032             stalledEngine = NULL;
14033             return;
14034         }
14035         if (gameMode == MachinePlaysWhite ||
14036             gameMode == TwoMachinesPlay   ||
14037             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14038             if(first.pause)  UnPauseEngine(&first);
14039             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14040             if(second.pause) UnPauseEngine(&second);
14041             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14042             StartClocks();
14043         } else {
14044             DisplayBothClocks();
14045         }
14046         if (gameMode == PlayFromGameFile) {
14047             if (appData.timeDelay >= 0)
14048                 AutoPlayGameLoop();
14049         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14050             Reset(FALSE, TRUE);
14051             SendToICS(ics_prefix);
14052             SendToICS("refresh\n");
14053         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14054             ForwardInner(forwardMostMove);
14055         }
14056         pauseExamInvalid = FALSE;
14057     } else {
14058         switch (gameMode) {
14059           default:
14060             return;
14061           case IcsExamining:
14062             pauseExamForwardMostMove = forwardMostMove;
14063             pauseExamInvalid = FALSE;
14064             /* fall through */
14065           case IcsObserving:
14066           case IcsPlayingWhite:
14067           case IcsPlayingBlack:
14068             pausing = TRUE;
14069             ModeHighlight();
14070             return;
14071           case PlayFromGameFile:
14072             (void) StopLoadGameTimer();
14073             pausing = TRUE;
14074             ModeHighlight();
14075             break;
14076           case BeginningOfGame:
14077             if (appData.icsActive) return;
14078             /* else fall through */
14079           case MachinePlaysWhite:
14080           case MachinePlaysBlack:
14081           case TwoMachinesPlay:
14082             if (forwardMostMove == 0)
14083               return;           /* don't pause if no one has moved */
14084             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14085                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14086                 if(onMove->pause) {           // thinking engine can be paused
14087                     PauseEngine(onMove);      // do it
14088                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14089                         PauseEngine(onMove->other);
14090                     else
14091                         SendToProgram("easy\n", onMove->other);
14092                     StopClocks();
14093                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14094             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14095                 if(first.pause) {
14096                     PauseEngine(&first);
14097                     StopClocks();
14098                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14099             } else { // human on move, pause pondering by either method
14100                 if(first.pause)
14101                     PauseEngine(&first);
14102                 else if(appData.ponderNextMove)
14103                     SendToProgram("easy\n", &first);
14104                 StopClocks();
14105             }
14106             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14107           case AnalyzeMode:
14108             pausing = TRUE;
14109             ModeHighlight();
14110             break;
14111         }
14112     }
14113 }
14114
14115 void
14116 EditCommentEvent ()
14117 {
14118     char title[MSG_SIZ];
14119
14120     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14121       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14122     } else {
14123       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14124                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14125                parseList[currentMove - 1]);
14126     }
14127
14128     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14129 }
14130
14131
14132 void
14133 EditTagsEvent ()
14134 {
14135     char *tags = PGNTags(&gameInfo);
14136     bookUp = FALSE;
14137     EditTagsPopUp(tags, NULL);
14138     free(tags);
14139 }
14140
14141 void
14142 ToggleSecond ()
14143 {
14144   if(second.analyzing) {
14145     SendToProgram("exit\n", &second);
14146     second.analyzing = FALSE;
14147   } else {
14148     if (second.pr == NoProc) StartChessProgram(&second);
14149     InitChessProgram(&second, FALSE);
14150     FeedMovesToProgram(&second, currentMove);
14151
14152     SendToProgram("analyze\n", &second);
14153     second.analyzing = TRUE;
14154   }
14155 }
14156
14157 /* Toggle ShowThinking */
14158 void
14159 ToggleShowThinking()
14160 {
14161   appData.showThinking = !appData.showThinking;
14162   ShowThinkingEvent();
14163 }
14164
14165 int
14166 AnalyzeModeEvent ()
14167 {
14168     char buf[MSG_SIZ];
14169
14170     if (!first.analysisSupport) {
14171       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14172       DisplayError(buf, 0);
14173       return 0;
14174     }
14175     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14176     if (appData.icsActive) {
14177         if (gameMode != IcsObserving) {
14178           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14179             DisplayError(buf, 0);
14180             /* secure check */
14181             if (appData.icsEngineAnalyze) {
14182                 if (appData.debugMode)
14183                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14184                 ExitAnalyzeMode();
14185                 ModeHighlight();
14186             }
14187             return 0;
14188         }
14189         /* if enable, user wants to disable icsEngineAnalyze */
14190         if (appData.icsEngineAnalyze) {
14191                 ExitAnalyzeMode();
14192                 ModeHighlight();
14193                 return 0;
14194         }
14195         appData.icsEngineAnalyze = TRUE;
14196         if (appData.debugMode)
14197             fprintf(debugFP, "ICS engine analyze starting... \n");
14198     }
14199
14200     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14201     if (appData.noChessProgram || gameMode == AnalyzeMode)
14202       return 0;
14203
14204     if (gameMode != AnalyzeFile) {
14205         if (!appData.icsEngineAnalyze) {
14206                EditGameEvent();
14207                if (gameMode != EditGame) return 0;
14208         }
14209         if (!appData.showThinking) ToggleShowThinking();
14210         ResurrectChessProgram();
14211         SendToProgram("analyze\n", &first);
14212         first.analyzing = TRUE;
14213         /*first.maybeThinking = TRUE;*/
14214         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14215         EngineOutputPopUp();
14216     }
14217     if (!appData.icsEngineAnalyze) {
14218         gameMode = AnalyzeMode;
14219         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14220     }
14221     pausing = FALSE;
14222     ModeHighlight();
14223     SetGameInfo();
14224
14225     StartAnalysisClock();
14226     GetTimeMark(&lastNodeCountTime);
14227     lastNodeCount = 0;
14228     return 1;
14229 }
14230
14231 void
14232 AnalyzeFileEvent ()
14233 {
14234     if (appData.noChessProgram || gameMode == AnalyzeFile)
14235       return;
14236
14237     if (!first.analysisSupport) {
14238       char buf[MSG_SIZ];
14239       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14240       DisplayError(buf, 0);
14241       return;
14242     }
14243
14244     if (gameMode != AnalyzeMode) {
14245         keepInfo = 1; // mere annotating should not alter PGN tags
14246         EditGameEvent();
14247         keepInfo = 0;
14248         if (gameMode != EditGame) return;
14249         if (!appData.showThinking) ToggleShowThinking();
14250         ResurrectChessProgram();
14251         SendToProgram("analyze\n", &first);
14252         first.analyzing = TRUE;
14253         /*first.maybeThinking = TRUE;*/
14254         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14255         EngineOutputPopUp();
14256     }
14257     gameMode = AnalyzeFile;
14258     pausing = FALSE;
14259     ModeHighlight();
14260
14261     StartAnalysisClock();
14262     GetTimeMark(&lastNodeCountTime);
14263     lastNodeCount = 0;
14264     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14265     AnalysisPeriodicEvent(1);
14266 }
14267
14268 void
14269 MachineWhiteEvent ()
14270 {
14271     char buf[MSG_SIZ];
14272     char *bookHit = NULL;
14273
14274     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14275       return;
14276
14277
14278     if (gameMode == PlayFromGameFile ||
14279         gameMode == TwoMachinesPlay  ||
14280         gameMode == Training         ||
14281         gameMode == AnalyzeMode      ||
14282         gameMode == EndOfGame)
14283         EditGameEvent();
14284
14285     if (gameMode == EditPosition)
14286         EditPositionDone(TRUE);
14287
14288     if (!WhiteOnMove(currentMove)) {
14289         DisplayError(_("It is not White's turn"), 0);
14290         return;
14291     }
14292
14293     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14294       ExitAnalyzeMode();
14295
14296     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14297         gameMode == AnalyzeFile)
14298         TruncateGame();
14299
14300     ResurrectChessProgram();    /* in case it isn't running */
14301     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14302         gameMode = MachinePlaysWhite;
14303         ResetClocks();
14304     } else
14305     gameMode = MachinePlaysWhite;
14306     pausing = FALSE;
14307     ModeHighlight();
14308     SetGameInfo();
14309     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14310     DisplayTitle(buf);
14311     if (first.sendName) {
14312       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14313       SendToProgram(buf, &first);
14314     }
14315     if (first.sendTime) {
14316       if (first.useColors) {
14317         SendToProgram("black\n", &first); /*gnu kludge*/
14318       }
14319       SendTimeRemaining(&first, TRUE);
14320     }
14321     if (first.useColors) {
14322       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14323     }
14324     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14325     SetMachineThinkingEnables();
14326     first.maybeThinking = TRUE;
14327     StartClocks();
14328     firstMove = FALSE;
14329
14330     if (appData.autoFlipView && !flipView) {
14331       flipView = !flipView;
14332       DrawPosition(FALSE, NULL);
14333       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14334     }
14335
14336     if(bookHit) { // [HGM] book: simulate book reply
14337         static char bookMove[MSG_SIZ]; // a bit generous?
14338
14339         programStats.nodes = programStats.depth = programStats.time =
14340         programStats.score = programStats.got_only_move = 0;
14341         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14342
14343         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14344         strcat(bookMove, bookHit);
14345         HandleMachineMove(bookMove, &first);
14346     }
14347 }
14348
14349 void
14350 MachineBlackEvent ()
14351 {
14352   char buf[MSG_SIZ];
14353   char *bookHit = NULL;
14354
14355     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14356         return;
14357
14358
14359     if (gameMode == PlayFromGameFile ||
14360         gameMode == TwoMachinesPlay  ||
14361         gameMode == Training         ||
14362         gameMode == AnalyzeMode      ||
14363         gameMode == EndOfGame)
14364         EditGameEvent();
14365
14366     if (gameMode == EditPosition)
14367         EditPositionDone(TRUE);
14368
14369     if (WhiteOnMove(currentMove)) {
14370         DisplayError(_("It is not Black's turn"), 0);
14371         return;
14372     }
14373
14374     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14375       ExitAnalyzeMode();
14376
14377     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14378         gameMode == AnalyzeFile)
14379         TruncateGame();
14380
14381     ResurrectChessProgram();    /* in case it isn't running */
14382     gameMode = MachinePlaysBlack;
14383     pausing = FALSE;
14384     ModeHighlight();
14385     SetGameInfo();
14386     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14387     DisplayTitle(buf);
14388     if (first.sendName) {
14389       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14390       SendToProgram(buf, &first);
14391     }
14392     if (first.sendTime) {
14393       if (first.useColors) {
14394         SendToProgram("white\n", &first); /*gnu kludge*/
14395       }
14396       SendTimeRemaining(&first, FALSE);
14397     }
14398     if (first.useColors) {
14399       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14400     }
14401     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14402     SetMachineThinkingEnables();
14403     first.maybeThinking = TRUE;
14404     StartClocks();
14405
14406     if (appData.autoFlipView && flipView) {
14407       flipView = !flipView;
14408       DrawPosition(FALSE, NULL);
14409       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14410     }
14411     if(bookHit) { // [HGM] book: simulate book reply
14412         static char bookMove[MSG_SIZ]; // a bit generous?
14413
14414         programStats.nodes = programStats.depth = programStats.time =
14415         programStats.score = programStats.got_only_move = 0;
14416         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14417
14418         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14419         strcat(bookMove, bookHit);
14420         HandleMachineMove(bookMove, &first);
14421     }
14422 }
14423
14424
14425 void
14426 DisplayTwoMachinesTitle ()
14427 {
14428     char buf[MSG_SIZ];
14429     if (appData.matchGames > 0) {
14430         if(appData.tourneyFile[0]) {
14431           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14432                    gameInfo.white, _("vs."), gameInfo.black,
14433                    nextGame+1, appData.matchGames+1,
14434                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14435         } else
14436         if (first.twoMachinesColor[0] == 'w') {
14437           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14438                    gameInfo.white, _("vs."),  gameInfo.black,
14439                    first.matchWins, second.matchWins,
14440                    matchGame - 1 - (first.matchWins + second.matchWins));
14441         } else {
14442           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14443                    gameInfo.white, _("vs."), gameInfo.black,
14444                    second.matchWins, first.matchWins,
14445                    matchGame - 1 - (first.matchWins + second.matchWins));
14446         }
14447     } else {
14448       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14449     }
14450     DisplayTitle(buf);
14451 }
14452
14453 void
14454 SettingsMenuIfReady ()
14455 {
14456   if (second.lastPing != second.lastPong) {
14457     DisplayMessage("", _("Waiting for second chess program"));
14458     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14459     return;
14460   }
14461   ThawUI();
14462   DisplayMessage("", "");
14463   SettingsPopUp(&second);
14464 }
14465
14466 int
14467 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14468 {
14469     char buf[MSG_SIZ];
14470     if (cps->pr == NoProc) {
14471         StartChessProgram(cps);
14472         if (cps->protocolVersion == 1) {
14473           retry();
14474           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14475         } else {
14476           /* kludge: allow timeout for initial "feature" command */
14477           if(retry != TwoMachinesEventIfReady) FreezeUI();
14478           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14479           DisplayMessage("", buf);
14480           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14481         }
14482         return 1;
14483     }
14484     return 0;
14485 }
14486
14487 void
14488 TwoMachinesEvent P((void))
14489 {
14490     int i;
14491     char buf[MSG_SIZ];
14492     ChessProgramState *onmove;
14493     char *bookHit = NULL;
14494     static int stalling = 0;
14495     TimeMark now;
14496     long wait;
14497
14498     if (appData.noChessProgram) return;
14499
14500     switch (gameMode) {
14501       case TwoMachinesPlay:
14502         return;
14503       case MachinePlaysWhite:
14504       case MachinePlaysBlack:
14505         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14506             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14507             return;
14508         }
14509         /* fall through */
14510       case BeginningOfGame:
14511       case PlayFromGameFile:
14512       case EndOfGame:
14513         EditGameEvent();
14514         if (gameMode != EditGame) return;
14515         break;
14516       case EditPosition:
14517         EditPositionDone(TRUE);
14518         break;
14519       case AnalyzeMode:
14520       case AnalyzeFile:
14521         ExitAnalyzeMode();
14522         break;
14523       case EditGame:
14524       default:
14525         break;
14526     }
14527
14528 //    forwardMostMove = currentMove;
14529     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14530     startingEngine = TRUE;
14531
14532     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14533
14534     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14535     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14536       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14537       return;
14538     }
14539     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14540
14541     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14542                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14543         startingEngine = FALSE;
14544         DisplayError("second engine does not play this", 0);
14545         return;
14546     }
14547
14548     if(!stalling) {
14549       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14550       SendToProgram("force\n", &second);
14551       stalling = 1;
14552       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14553       return;
14554     }
14555     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14556     if(appData.matchPause>10000 || appData.matchPause<10)
14557                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14558     wait = SubtractTimeMarks(&now, &pauseStart);
14559     if(wait < appData.matchPause) {
14560         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14561         return;
14562     }
14563     // we are now committed to starting the game
14564     stalling = 0;
14565     DisplayMessage("", "");
14566     if (startedFromSetupPosition) {
14567         SendBoard(&second, backwardMostMove);
14568     if (appData.debugMode) {
14569         fprintf(debugFP, "Two Machines\n");
14570     }
14571     }
14572     for (i = backwardMostMove; i < forwardMostMove; i++) {
14573         SendMoveToProgram(i, &second);
14574     }
14575
14576     gameMode = TwoMachinesPlay;
14577     pausing = startingEngine = FALSE;
14578     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14579     SetGameInfo();
14580     DisplayTwoMachinesTitle();
14581     firstMove = TRUE;
14582     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14583         onmove = &first;
14584     } else {
14585         onmove = &second;
14586     }
14587     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14588     SendToProgram(first.computerString, &first);
14589     if (first.sendName) {
14590       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14591       SendToProgram(buf, &first);
14592     }
14593     SendToProgram(second.computerString, &second);
14594     if (second.sendName) {
14595       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14596       SendToProgram(buf, &second);
14597     }
14598
14599     ResetClocks();
14600     if (!first.sendTime || !second.sendTime) {
14601         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14602         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14603     }
14604     if (onmove->sendTime) {
14605       if (onmove->useColors) {
14606         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14607       }
14608       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14609     }
14610     if (onmove->useColors) {
14611       SendToProgram(onmove->twoMachinesColor, onmove);
14612     }
14613     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14614 //    SendToProgram("go\n", onmove);
14615     onmove->maybeThinking = TRUE;
14616     SetMachineThinkingEnables();
14617
14618     StartClocks();
14619
14620     if(bookHit) { // [HGM] book: simulate book reply
14621         static char bookMove[MSG_SIZ]; // a bit generous?
14622
14623         programStats.nodes = programStats.depth = programStats.time =
14624         programStats.score = programStats.got_only_move = 0;
14625         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14626
14627         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14628         strcat(bookMove, bookHit);
14629         savedMessage = bookMove; // args for deferred call
14630         savedState = onmove;
14631         ScheduleDelayedEvent(DeferredBookMove, 1);
14632     }
14633 }
14634
14635 void
14636 TrainingEvent ()
14637 {
14638     if (gameMode == Training) {
14639       SetTrainingModeOff();
14640       gameMode = PlayFromGameFile;
14641       DisplayMessage("", _("Training mode off"));
14642     } else {
14643       gameMode = Training;
14644       animateTraining = appData.animate;
14645
14646       /* make sure we are not already at the end of the game */
14647       if (currentMove < forwardMostMove) {
14648         SetTrainingModeOn();
14649         DisplayMessage("", _("Training mode on"));
14650       } else {
14651         gameMode = PlayFromGameFile;
14652         DisplayError(_("Already at end of game"), 0);
14653       }
14654     }
14655     ModeHighlight();
14656 }
14657
14658 void
14659 IcsClientEvent ()
14660 {
14661     if (!appData.icsActive) return;
14662     switch (gameMode) {
14663       case IcsPlayingWhite:
14664       case IcsPlayingBlack:
14665       case IcsObserving:
14666       case IcsIdle:
14667       case BeginningOfGame:
14668       case IcsExamining:
14669         return;
14670
14671       case EditGame:
14672         break;
14673
14674       case EditPosition:
14675         EditPositionDone(TRUE);
14676         break;
14677
14678       case AnalyzeMode:
14679       case AnalyzeFile:
14680         ExitAnalyzeMode();
14681         break;
14682
14683       default:
14684         EditGameEvent();
14685         break;
14686     }
14687
14688     gameMode = IcsIdle;
14689     ModeHighlight();
14690     return;
14691 }
14692
14693 void
14694 EditGameEvent ()
14695 {
14696     int i;
14697
14698     switch (gameMode) {
14699       case Training:
14700         SetTrainingModeOff();
14701         break;
14702       case MachinePlaysWhite:
14703       case MachinePlaysBlack:
14704       case BeginningOfGame:
14705         SendToProgram("force\n", &first);
14706         SetUserThinkingEnables();
14707         break;
14708       case PlayFromGameFile:
14709         (void) StopLoadGameTimer();
14710         if (gameFileFP != NULL) {
14711             gameFileFP = NULL;
14712         }
14713         break;
14714       case EditPosition:
14715         EditPositionDone(TRUE);
14716         break;
14717       case AnalyzeMode:
14718       case AnalyzeFile:
14719         ExitAnalyzeMode();
14720         SendToProgram("force\n", &first);
14721         break;
14722       case TwoMachinesPlay:
14723         GameEnds(EndOfFile, NULL, GE_PLAYER);
14724         ResurrectChessProgram();
14725         SetUserThinkingEnables();
14726         break;
14727       case EndOfGame:
14728         ResurrectChessProgram();
14729         break;
14730       case IcsPlayingBlack:
14731       case IcsPlayingWhite:
14732         DisplayError(_("Warning: You are still playing a game"), 0);
14733         break;
14734       case IcsObserving:
14735         DisplayError(_("Warning: You are still observing a game"), 0);
14736         break;
14737       case IcsExamining:
14738         DisplayError(_("Warning: You are still examining a game"), 0);
14739         break;
14740       case IcsIdle:
14741         break;
14742       case EditGame:
14743       default:
14744         return;
14745     }
14746
14747     pausing = FALSE;
14748     StopClocks();
14749     first.offeredDraw = second.offeredDraw = 0;
14750
14751     if (gameMode == PlayFromGameFile) {
14752         whiteTimeRemaining = timeRemaining[0][currentMove];
14753         blackTimeRemaining = timeRemaining[1][currentMove];
14754         DisplayTitle("");
14755     }
14756
14757     if (gameMode == MachinePlaysWhite ||
14758         gameMode == MachinePlaysBlack ||
14759         gameMode == TwoMachinesPlay ||
14760         gameMode == EndOfGame) {
14761         i = forwardMostMove;
14762         while (i > currentMove) {
14763             SendToProgram("undo\n", &first);
14764             i--;
14765         }
14766         if(!adjustedClock) {
14767         whiteTimeRemaining = timeRemaining[0][currentMove];
14768         blackTimeRemaining = timeRemaining[1][currentMove];
14769         DisplayBothClocks();
14770         }
14771         if (whiteFlag || blackFlag) {
14772             whiteFlag = blackFlag = 0;
14773         }
14774         DisplayTitle("");
14775     }
14776
14777     gameMode = EditGame;
14778     ModeHighlight();
14779     SetGameInfo();
14780 }
14781
14782
14783 void
14784 EditPositionEvent ()
14785 {
14786     if (gameMode == EditPosition) {
14787         EditGameEvent();
14788         return;
14789     }
14790
14791     EditGameEvent();
14792     if (gameMode != EditGame) return;
14793
14794     gameMode = EditPosition;
14795     ModeHighlight();
14796     SetGameInfo();
14797     if (currentMove > 0)
14798       CopyBoard(boards[0], boards[currentMove]);
14799
14800     blackPlaysFirst = !WhiteOnMove(currentMove);
14801     ResetClocks();
14802     currentMove = forwardMostMove = backwardMostMove = 0;
14803     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14804     DisplayMove(-1);
14805     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14806 }
14807
14808 void
14809 ExitAnalyzeMode ()
14810 {
14811     /* [DM] icsEngineAnalyze - possible call from other functions */
14812     if (appData.icsEngineAnalyze) {
14813         appData.icsEngineAnalyze = FALSE;
14814
14815         DisplayMessage("",_("Close ICS engine analyze..."));
14816     }
14817     if (first.analysisSupport && first.analyzing) {
14818       SendToBoth("exit\n");
14819       first.analyzing = second.analyzing = FALSE;
14820     }
14821     thinkOutput[0] = NULLCHAR;
14822 }
14823
14824 void
14825 EditPositionDone (Boolean fakeRights)
14826 {
14827     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14828
14829     startedFromSetupPosition = TRUE;
14830     InitChessProgram(&first, FALSE);
14831     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14832       boards[0][EP_STATUS] = EP_NONE;
14833       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14834       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14835         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14836         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14837       } else boards[0][CASTLING][2] = NoRights;
14838       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14839         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14840         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14841       } else boards[0][CASTLING][5] = NoRights;
14842       if(gameInfo.variant == VariantSChess) {
14843         int i;
14844         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14845           boards[0][VIRGIN][i] = 0;
14846           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14847           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14848         }
14849       }
14850     }
14851     SendToProgram("force\n", &first);
14852     if (blackPlaysFirst) {
14853         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14854         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14855         currentMove = forwardMostMove = backwardMostMove = 1;
14856         CopyBoard(boards[1], boards[0]);
14857     } else {
14858         currentMove = forwardMostMove = backwardMostMove = 0;
14859     }
14860     SendBoard(&first, forwardMostMove);
14861     if (appData.debugMode) {
14862         fprintf(debugFP, "EditPosDone\n");
14863     }
14864     DisplayTitle("");
14865     DisplayMessage("", "");
14866     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14867     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14868     gameMode = EditGame;
14869     ModeHighlight();
14870     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14871     ClearHighlights(); /* [AS] */
14872 }
14873
14874 /* Pause for `ms' milliseconds */
14875 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14876 void
14877 TimeDelay (long ms)
14878 {
14879     TimeMark m1, m2;
14880
14881     GetTimeMark(&m1);
14882     do {
14883         GetTimeMark(&m2);
14884     } while (SubtractTimeMarks(&m2, &m1) < ms);
14885 }
14886
14887 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14888 void
14889 SendMultiLineToICS (char *buf)
14890 {
14891     char temp[MSG_SIZ+1], *p;
14892     int len;
14893
14894     len = strlen(buf);
14895     if (len > MSG_SIZ)
14896       len = MSG_SIZ;
14897
14898     strncpy(temp, buf, len);
14899     temp[len] = 0;
14900
14901     p = temp;
14902     while (*p) {
14903         if (*p == '\n' || *p == '\r')
14904           *p = ' ';
14905         ++p;
14906     }
14907
14908     strcat(temp, "\n");
14909     SendToICS(temp);
14910     SendToPlayer(temp, strlen(temp));
14911 }
14912
14913 void
14914 SetWhiteToPlayEvent ()
14915 {
14916     if (gameMode == EditPosition) {
14917         blackPlaysFirst = FALSE;
14918         DisplayBothClocks();    /* works because currentMove is 0 */
14919     } else if (gameMode == IcsExamining) {
14920         SendToICS(ics_prefix);
14921         SendToICS("tomove white\n");
14922     }
14923 }
14924
14925 void
14926 SetBlackToPlayEvent ()
14927 {
14928     if (gameMode == EditPosition) {
14929         blackPlaysFirst = TRUE;
14930         currentMove = 1;        /* kludge */
14931         DisplayBothClocks();
14932         currentMove = 0;
14933     } else if (gameMode == IcsExamining) {
14934         SendToICS(ics_prefix);
14935         SendToICS("tomove black\n");
14936     }
14937 }
14938
14939 void
14940 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14941 {
14942     char buf[MSG_SIZ];
14943     ChessSquare piece = boards[0][y][x];
14944     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14945     static int lastVariant;
14946
14947     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14948
14949     switch (selection) {
14950       case ClearBoard:
14951         CopyBoard(currentBoard, boards[0]);
14952         CopyBoard(menuBoard, initialPosition);
14953         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14954             SendToICS(ics_prefix);
14955             SendToICS("bsetup clear\n");
14956         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14957             SendToICS(ics_prefix);
14958             SendToICS("clearboard\n");
14959         } else {
14960             int nonEmpty = 0;
14961             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14962                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14963                 for (y = 0; y < BOARD_HEIGHT; y++) {
14964                     if (gameMode == IcsExamining) {
14965                         if (boards[currentMove][y][x] != EmptySquare) {
14966                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14967                                     AAA + x, ONE + y);
14968                             SendToICS(buf);
14969                         }
14970                     } else {
14971                         if(boards[0][y][x] != p) nonEmpty++;
14972                         boards[0][y][x] = p;
14973                     }
14974                 }
14975                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14976             }
14977             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14978                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14979                     ChessSquare p = menuBoard[0][x];
14980                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14981                     p = menuBoard[BOARD_HEIGHT-1][x];
14982                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14983                 }
14984                 DisplayMessage("Clicking clock again restores position", "");
14985                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14986                 if(!nonEmpty) { // asked to clear an empty board
14987                     CopyBoard(boards[0], menuBoard);
14988                 } else
14989                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14990                     CopyBoard(boards[0], initialPosition);
14991                 } else
14992                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14993                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14994                     CopyBoard(boards[0], erasedBoard);
14995                 } else
14996                     CopyBoard(erasedBoard, currentBoard);
14997
14998             }
14999         }
15000         if (gameMode == EditPosition) {
15001             DrawPosition(FALSE, boards[0]);
15002         }
15003         break;
15004
15005       case WhitePlay:
15006         SetWhiteToPlayEvent();
15007         break;
15008
15009       case BlackPlay:
15010         SetBlackToPlayEvent();
15011         break;
15012
15013       case EmptySquare:
15014         if (gameMode == IcsExamining) {
15015             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15016             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15017             SendToICS(buf);
15018         } else {
15019             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15020                 if(x == BOARD_LEFT-2) {
15021                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15022                     boards[0][y][1] = 0;
15023                 } else
15024                 if(x == BOARD_RGHT+1) {
15025                     if(y >= gameInfo.holdingsSize) break;
15026                     boards[0][y][BOARD_WIDTH-2] = 0;
15027                 } else break;
15028             }
15029             boards[0][y][x] = EmptySquare;
15030             DrawPosition(FALSE, boards[0]);
15031         }
15032         break;
15033
15034       case PromotePiece:
15035         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15036            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15037             selection = (ChessSquare) (PROMOTED piece);
15038         } else if(piece == EmptySquare) selection = WhiteSilver;
15039         else selection = (ChessSquare)((int)piece - 1);
15040         goto defaultlabel;
15041
15042       case DemotePiece:
15043         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15044            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15045             selection = (ChessSquare) (DEMOTED piece);
15046         } else if(piece == EmptySquare) selection = BlackSilver;
15047         else selection = (ChessSquare)((int)piece + 1);
15048         goto defaultlabel;
15049
15050       case WhiteQueen:
15051       case BlackQueen:
15052         if(gameInfo.variant == VariantShatranj ||
15053            gameInfo.variant == VariantXiangqi  ||
15054            gameInfo.variant == VariantCourier  ||
15055            gameInfo.variant == VariantASEAN    ||
15056            gameInfo.variant == VariantMakruk     )
15057             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15058         goto defaultlabel;
15059
15060       case WhiteKing:
15061       case BlackKing:
15062         if(gameInfo.variant == VariantXiangqi)
15063             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15064         if(gameInfo.variant == VariantKnightmate)
15065             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15066       default:
15067         defaultlabel:
15068         if (gameMode == IcsExamining) {
15069             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15070             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15071                      PieceToChar(selection), AAA + x, ONE + y);
15072             SendToICS(buf);
15073         } else {
15074             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15075                 int n;
15076                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15077                     n = PieceToNumber(selection - BlackPawn);
15078                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15079                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15080                     boards[0][BOARD_HEIGHT-1-n][1]++;
15081                 } else
15082                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15083                     n = PieceToNumber(selection);
15084                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15085                     boards[0][n][BOARD_WIDTH-1] = selection;
15086                     boards[0][n][BOARD_WIDTH-2]++;
15087                 }
15088             } else
15089             boards[0][y][x] = selection;
15090             DrawPosition(TRUE, boards[0]);
15091             ClearHighlights();
15092             fromX = fromY = -1;
15093         }
15094         break;
15095     }
15096 }
15097
15098
15099 void
15100 DropMenuEvent (ChessSquare selection, int x, int y)
15101 {
15102     ChessMove moveType;
15103
15104     switch (gameMode) {
15105       case IcsPlayingWhite:
15106       case MachinePlaysBlack:
15107         if (!WhiteOnMove(currentMove)) {
15108             DisplayMoveError(_("It is Black's turn"));
15109             return;
15110         }
15111         moveType = WhiteDrop;
15112         break;
15113       case IcsPlayingBlack:
15114       case MachinePlaysWhite:
15115         if (WhiteOnMove(currentMove)) {
15116             DisplayMoveError(_("It is White's turn"));
15117             return;
15118         }
15119         moveType = BlackDrop;
15120         break;
15121       case EditGame:
15122         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15123         break;
15124       default:
15125         return;
15126     }
15127
15128     if (moveType == BlackDrop && selection < BlackPawn) {
15129       selection = (ChessSquare) ((int) selection
15130                                  + (int) BlackPawn - (int) WhitePawn);
15131     }
15132     if (boards[currentMove][y][x] != EmptySquare) {
15133         DisplayMoveError(_("That square is occupied"));
15134         return;
15135     }
15136
15137     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15138 }
15139
15140 void
15141 AcceptEvent ()
15142 {
15143     /* Accept a pending offer of any kind from opponent */
15144
15145     if (appData.icsActive) {
15146         SendToICS(ics_prefix);
15147         SendToICS("accept\n");
15148     } else if (cmailMsgLoaded) {
15149         if (currentMove == cmailOldMove &&
15150             commentList[cmailOldMove] != NULL &&
15151             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15152                    "Black offers a draw" : "White offers a draw")) {
15153             TruncateGame();
15154             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15155             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15156         } else {
15157             DisplayError(_("There is no pending offer on this move"), 0);
15158             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15159         }
15160     } else {
15161         /* Not used for offers from chess program */
15162     }
15163 }
15164
15165 void
15166 DeclineEvent ()
15167 {
15168     /* Decline a pending offer of any kind from opponent */
15169
15170     if (appData.icsActive) {
15171         SendToICS(ics_prefix);
15172         SendToICS("decline\n");
15173     } else if (cmailMsgLoaded) {
15174         if (currentMove == cmailOldMove &&
15175             commentList[cmailOldMove] != NULL &&
15176             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15177                    "Black offers a draw" : "White offers a draw")) {
15178 #ifdef NOTDEF
15179             AppendComment(cmailOldMove, "Draw declined", TRUE);
15180             DisplayComment(cmailOldMove - 1, "Draw declined");
15181 #endif /*NOTDEF*/
15182         } else {
15183             DisplayError(_("There is no pending offer on this move"), 0);
15184         }
15185     } else {
15186         /* Not used for offers from chess program */
15187     }
15188 }
15189
15190 void
15191 RematchEvent ()
15192 {
15193     /* Issue ICS rematch command */
15194     if (appData.icsActive) {
15195         SendToICS(ics_prefix);
15196         SendToICS("rematch\n");
15197     }
15198 }
15199
15200 void
15201 CallFlagEvent ()
15202 {
15203     /* Call your opponent's flag (claim a win on time) */
15204     if (appData.icsActive) {
15205         SendToICS(ics_prefix);
15206         SendToICS("flag\n");
15207     } else {
15208         switch (gameMode) {
15209           default:
15210             return;
15211           case MachinePlaysWhite:
15212             if (whiteFlag) {
15213                 if (blackFlag)
15214                   GameEnds(GameIsDrawn, "Both players ran out of time",
15215                            GE_PLAYER);
15216                 else
15217                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15218             } else {
15219                 DisplayError(_("Your opponent is not out of time"), 0);
15220             }
15221             break;
15222           case MachinePlaysBlack:
15223             if (blackFlag) {
15224                 if (whiteFlag)
15225                   GameEnds(GameIsDrawn, "Both players ran out of time",
15226                            GE_PLAYER);
15227                 else
15228                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15229             } else {
15230                 DisplayError(_("Your opponent is not out of time"), 0);
15231             }
15232             break;
15233         }
15234     }
15235 }
15236
15237 void
15238 ClockClick (int which)
15239 {       // [HGM] code moved to back-end from winboard.c
15240         if(which) { // black clock
15241           if (gameMode == EditPosition || gameMode == IcsExamining) {
15242             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15243             SetBlackToPlayEvent();
15244           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15245           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15246           } else if (shiftKey) {
15247             AdjustClock(which, -1);
15248           } else if (gameMode == IcsPlayingWhite ||
15249                      gameMode == MachinePlaysBlack) {
15250             CallFlagEvent();
15251           }
15252         } else { // white clock
15253           if (gameMode == EditPosition || gameMode == IcsExamining) {
15254             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15255             SetWhiteToPlayEvent();
15256           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15257           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15258           } else if (shiftKey) {
15259             AdjustClock(which, -1);
15260           } else if (gameMode == IcsPlayingBlack ||
15261                    gameMode == MachinePlaysWhite) {
15262             CallFlagEvent();
15263           }
15264         }
15265 }
15266
15267 void
15268 DrawEvent ()
15269 {
15270     /* Offer draw or accept pending draw offer from opponent */
15271
15272     if (appData.icsActive) {
15273         /* Note: tournament rules require draw offers to be
15274            made after you make your move but before you punch
15275            your clock.  Currently ICS doesn't let you do that;
15276            instead, you immediately punch your clock after making
15277            a move, but you can offer a draw at any time. */
15278
15279         SendToICS(ics_prefix);
15280         SendToICS("draw\n");
15281         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15282     } else if (cmailMsgLoaded) {
15283         if (currentMove == cmailOldMove &&
15284             commentList[cmailOldMove] != NULL &&
15285             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15286                    "Black offers a draw" : "White offers a draw")) {
15287             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15288             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15289         } else if (currentMove == cmailOldMove + 1) {
15290             char *offer = WhiteOnMove(cmailOldMove) ?
15291               "White offers a draw" : "Black offers a draw";
15292             AppendComment(currentMove, offer, TRUE);
15293             DisplayComment(currentMove - 1, offer);
15294             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15295         } else {
15296             DisplayError(_("You must make your move before offering a draw"), 0);
15297             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15298         }
15299     } else if (first.offeredDraw) {
15300         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15301     } else {
15302         if (first.sendDrawOffers) {
15303             SendToProgram("draw\n", &first);
15304             userOfferedDraw = TRUE;
15305         }
15306     }
15307 }
15308
15309 void
15310 AdjournEvent ()
15311 {
15312     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15313
15314     if (appData.icsActive) {
15315         SendToICS(ics_prefix);
15316         SendToICS("adjourn\n");
15317     } else {
15318         /* Currently GNU Chess doesn't offer or accept Adjourns */
15319     }
15320 }
15321
15322
15323 void
15324 AbortEvent ()
15325 {
15326     /* Offer Abort or accept pending Abort offer from opponent */
15327
15328     if (appData.icsActive) {
15329         SendToICS(ics_prefix);
15330         SendToICS("abort\n");
15331     } else {
15332         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15333     }
15334 }
15335
15336 void
15337 ResignEvent ()
15338 {
15339     /* Resign.  You can do this even if it's not your turn. */
15340
15341     if (appData.icsActive) {
15342         SendToICS(ics_prefix);
15343         SendToICS("resign\n");
15344     } else {
15345         switch (gameMode) {
15346           case MachinePlaysWhite:
15347             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15348             break;
15349           case MachinePlaysBlack:
15350             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15351             break;
15352           case EditGame:
15353             if (cmailMsgLoaded) {
15354                 TruncateGame();
15355                 if (WhiteOnMove(cmailOldMove)) {
15356                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15357                 } else {
15358                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15359                 }
15360                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15361             }
15362             break;
15363           default:
15364             break;
15365         }
15366     }
15367 }
15368
15369
15370 void
15371 StopObservingEvent ()
15372 {
15373     /* Stop observing current games */
15374     SendToICS(ics_prefix);
15375     SendToICS("unobserve\n");
15376 }
15377
15378 void
15379 StopExaminingEvent ()
15380 {
15381     /* Stop observing current game */
15382     SendToICS(ics_prefix);
15383     SendToICS("unexamine\n");
15384 }
15385
15386 void
15387 ForwardInner (int target)
15388 {
15389     int limit; int oldSeekGraphUp = seekGraphUp;
15390
15391     if (appData.debugMode)
15392         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15393                 target, currentMove, forwardMostMove);
15394
15395     if (gameMode == EditPosition)
15396       return;
15397
15398     seekGraphUp = FALSE;
15399     MarkTargetSquares(1);
15400
15401     if (gameMode == PlayFromGameFile && !pausing)
15402       PauseEvent();
15403
15404     if (gameMode == IcsExamining && pausing)
15405       limit = pauseExamForwardMostMove;
15406     else
15407       limit = forwardMostMove;
15408
15409     if (target > limit) target = limit;
15410
15411     if (target > 0 && moveList[target - 1][0]) {
15412         int fromX, fromY, toX, toY;
15413         toX = moveList[target - 1][2] - AAA;
15414         toY = moveList[target - 1][3] - ONE;
15415         if (moveList[target - 1][1] == '@') {
15416             if (appData.highlightLastMove) {
15417                 SetHighlights(-1, -1, toX, toY);
15418             }
15419         } else {
15420             fromX = moveList[target - 1][0] - AAA;
15421             fromY = moveList[target - 1][1] - ONE;
15422             if (target == currentMove + 1) {
15423                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15424             }
15425             if (appData.highlightLastMove) {
15426                 SetHighlights(fromX, fromY, toX, toY);
15427             }
15428         }
15429     }
15430     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15431         gameMode == Training || gameMode == PlayFromGameFile ||
15432         gameMode == AnalyzeFile) {
15433         while (currentMove < target) {
15434             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15435             SendMoveToProgram(currentMove++, &first);
15436         }
15437     } else {
15438         currentMove = target;
15439     }
15440
15441     if (gameMode == EditGame || gameMode == EndOfGame) {
15442         whiteTimeRemaining = timeRemaining[0][currentMove];
15443         blackTimeRemaining = timeRemaining[1][currentMove];
15444     }
15445     DisplayBothClocks();
15446     DisplayMove(currentMove - 1);
15447     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15448     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15449     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15450         DisplayComment(currentMove - 1, commentList[currentMove]);
15451     }
15452     ClearMap(); // [HGM] exclude: invalidate map
15453 }
15454
15455
15456 void
15457 ForwardEvent ()
15458 {
15459     if (gameMode == IcsExamining && !pausing) {
15460         SendToICS(ics_prefix);
15461         SendToICS("forward\n");
15462     } else {
15463         ForwardInner(currentMove + 1);
15464     }
15465 }
15466
15467 void
15468 ToEndEvent ()
15469 {
15470     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15471         /* to optimze, we temporarily turn off analysis mode while we feed
15472          * the remaining moves to the engine. Otherwise we get analysis output
15473          * after each move.
15474          */
15475         if (first.analysisSupport) {
15476           SendToProgram("exit\nforce\n", &first);
15477           first.analyzing = FALSE;
15478         }
15479     }
15480
15481     if (gameMode == IcsExamining && !pausing) {
15482         SendToICS(ics_prefix);
15483         SendToICS("forward 999999\n");
15484     } else {
15485         ForwardInner(forwardMostMove);
15486     }
15487
15488     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15489         /* we have fed all the moves, so reactivate analysis mode */
15490         SendToProgram("analyze\n", &first);
15491         first.analyzing = TRUE;
15492         /*first.maybeThinking = TRUE;*/
15493         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15494     }
15495 }
15496
15497 void
15498 BackwardInner (int target)
15499 {
15500     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15501
15502     if (appData.debugMode)
15503         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15504                 target, currentMove, forwardMostMove);
15505
15506     if (gameMode == EditPosition) return;
15507     seekGraphUp = FALSE;
15508     MarkTargetSquares(1);
15509     if (currentMove <= backwardMostMove) {
15510         ClearHighlights();
15511         DrawPosition(full_redraw, boards[currentMove]);
15512         return;
15513     }
15514     if (gameMode == PlayFromGameFile && !pausing)
15515       PauseEvent();
15516
15517     if (moveList[target][0]) {
15518         int fromX, fromY, toX, toY;
15519         toX = moveList[target][2] - AAA;
15520         toY = moveList[target][3] - ONE;
15521         if (moveList[target][1] == '@') {
15522             if (appData.highlightLastMove) {
15523                 SetHighlights(-1, -1, toX, toY);
15524             }
15525         } else {
15526             fromX = moveList[target][0] - AAA;
15527             fromY = moveList[target][1] - ONE;
15528             if (target == currentMove - 1) {
15529                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15530             }
15531             if (appData.highlightLastMove) {
15532                 SetHighlights(fromX, fromY, toX, toY);
15533             }
15534         }
15535     }
15536     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15537         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15538         while (currentMove > target) {
15539             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15540                 // null move cannot be undone. Reload program with move history before it.
15541                 int i;
15542                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15543                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15544                 }
15545                 SendBoard(&first, i);
15546               if(second.analyzing) SendBoard(&second, i);
15547                 for(currentMove=i; currentMove<target; currentMove++) {
15548                     SendMoveToProgram(currentMove, &first);
15549                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15550                 }
15551                 break;
15552             }
15553             SendToBoth("undo\n");
15554             currentMove--;
15555         }
15556     } else {
15557         currentMove = target;
15558     }
15559
15560     if (gameMode == EditGame || gameMode == EndOfGame) {
15561         whiteTimeRemaining = timeRemaining[0][currentMove];
15562         blackTimeRemaining = timeRemaining[1][currentMove];
15563     }
15564     DisplayBothClocks();
15565     DisplayMove(currentMove - 1);
15566     DrawPosition(full_redraw, boards[currentMove]);
15567     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15568     // [HGM] PV info: routine tests if comment empty
15569     DisplayComment(currentMove - 1, commentList[currentMove]);
15570     ClearMap(); // [HGM] exclude: invalidate map
15571 }
15572
15573 void
15574 BackwardEvent ()
15575 {
15576     if (gameMode == IcsExamining && !pausing) {
15577         SendToICS(ics_prefix);
15578         SendToICS("backward\n");
15579     } else {
15580         BackwardInner(currentMove - 1);
15581     }
15582 }
15583
15584 void
15585 ToStartEvent ()
15586 {
15587     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15588         /* to optimize, we temporarily turn off analysis mode while we undo
15589          * all the moves. Otherwise we get analysis output after each undo.
15590          */
15591         if (first.analysisSupport) {
15592           SendToProgram("exit\nforce\n", &first);
15593           first.analyzing = FALSE;
15594         }
15595     }
15596
15597     if (gameMode == IcsExamining && !pausing) {
15598         SendToICS(ics_prefix);
15599         SendToICS("backward 999999\n");
15600     } else {
15601         BackwardInner(backwardMostMove);
15602     }
15603
15604     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15605         /* we have fed all the moves, so reactivate analysis mode */
15606         SendToProgram("analyze\n", &first);
15607         first.analyzing = TRUE;
15608         /*first.maybeThinking = TRUE;*/
15609         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15610     }
15611 }
15612
15613 void
15614 ToNrEvent (int to)
15615 {
15616   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15617   if (to >= forwardMostMove) to = forwardMostMove;
15618   if (to <= backwardMostMove) to = backwardMostMove;
15619   if (to < currentMove) {
15620     BackwardInner(to);
15621   } else {
15622     ForwardInner(to);
15623   }
15624 }
15625
15626 void
15627 RevertEvent (Boolean annotate)
15628 {
15629     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15630         return;
15631     }
15632     if (gameMode != IcsExamining) {
15633         DisplayError(_("You are not examining a game"), 0);
15634         return;
15635     }
15636     if (pausing) {
15637         DisplayError(_("You can't revert while pausing"), 0);
15638         return;
15639     }
15640     SendToICS(ics_prefix);
15641     SendToICS("revert\n");
15642 }
15643
15644 void
15645 RetractMoveEvent ()
15646 {
15647     switch (gameMode) {
15648       case MachinePlaysWhite:
15649       case MachinePlaysBlack:
15650         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15651             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15652             return;
15653         }
15654         if (forwardMostMove < 2) return;
15655         currentMove = forwardMostMove = forwardMostMove - 2;
15656         whiteTimeRemaining = timeRemaining[0][currentMove];
15657         blackTimeRemaining = timeRemaining[1][currentMove];
15658         DisplayBothClocks();
15659         DisplayMove(currentMove - 1);
15660         ClearHighlights();/*!! could figure this out*/
15661         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15662         SendToProgram("remove\n", &first);
15663         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15664         break;
15665
15666       case BeginningOfGame:
15667       default:
15668         break;
15669
15670       case IcsPlayingWhite:
15671       case IcsPlayingBlack:
15672         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15673             SendToICS(ics_prefix);
15674             SendToICS("takeback 2\n");
15675         } else {
15676             SendToICS(ics_prefix);
15677             SendToICS("takeback 1\n");
15678         }
15679         break;
15680     }
15681 }
15682
15683 void
15684 MoveNowEvent ()
15685 {
15686     ChessProgramState *cps;
15687
15688     switch (gameMode) {
15689       case MachinePlaysWhite:
15690         if (!WhiteOnMove(forwardMostMove)) {
15691             DisplayError(_("It is your turn"), 0);
15692             return;
15693         }
15694         cps = &first;
15695         break;
15696       case MachinePlaysBlack:
15697         if (WhiteOnMove(forwardMostMove)) {
15698             DisplayError(_("It is your turn"), 0);
15699             return;
15700         }
15701         cps = &first;
15702         break;
15703       case TwoMachinesPlay:
15704         if (WhiteOnMove(forwardMostMove) ==
15705             (first.twoMachinesColor[0] == 'w')) {
15706             cps = &first;
15707         } else {
15708             cps = &second;
15709         }
15710         break;
15711       case BeginningOfGame:
15712       default:
15713         return;
15714     }
15715     SendToProgram("?\n", cps);
15716 }
15717
15718 void
15719 TruncateGameEvent ()
15720 {
15721     EditGameEvent();
15722     if (gameMode != EditGame) return;
15723     TruncateGame();
15724 }
15725
15726 void
15727 TruncateGame ()
15728 {
15729     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15730     if (forwardMostMove > currentMove) {
15731         if (gameInfo.resultDetails != NULL) {
15732             free(gameInfo.resultDetails);
15733             gameInfo.resultDetails = NULL;
15734             gameInfo.result = GameUnfinished;
15735         }
15736         forwardMostMove = currentMove;
15737         HistorySet(parseList, backwardMostMove, forwardMostMove,
15738                    currentMove-1);
15739     }
15740 }
15741
15742 void
15743 HintEvent ()
15744 {
15745     if (appData.noChessProgram) return;
15746     switch (gameMode) {
15747       case MachinePlaysWhite:
15748         if (WhiteOnMove(forwardMostMove)) {
15749             DisplayError(_("Wait until your turn."), 0);
15750             return;
15751         }
15752         break;
15753       case BeginningOfGame:
15754       case MachinePlaysBlack:
15755         if (!WhiteOnMove(forwardMostMove)) {
15756             DisplayError(_("Wait until your turn."), 0);
15757             return;
15758         }
15759         break;
15760       default:
15761         DisplayError(_("No hint available"), 0);
15762         return;
15763     }
15764     SendToProgram("hint\n", &first);
15765     hintRequested = TRUE;
15766 }
15767
15768 void
15769 CreateBookEvent ()
15770 {
15771     ListGame * lg = (ListGame *) gameList.head;
15772     FILE *f, *g;
15773     int nItem;
15774     static int secondTime = FALSE;
15775
15776     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15777         DisplayError(_("Game list not loaded or empty"), 0);
15778         return;
15779     }
15780
15781     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15782         fclose(g);
15783         secondTime++;
15784         DisplayNote(_("Book file exists! Try again for overwrite."));
15785         return;
15786     }
15787
15788     creatingBook = TRUE;
15789     secondTime = FALSE;
15790
15791     /* Get list size */
15792     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15793         LoadGame(f, nItem, "", TRUE);
15794         AddGameToBook(TRUE);
15795         lg = (ListGame *) lg->node.succ;
15796     }
15797
15798     creatingBook = FALSE;
15799     FlushBook();
15800 }
15801
15802 void
15803 BookEvent ()
15804 {
15805     if (appData.noChessProgram) return;
15806     switch (gameMode) {
15807       case MachinePlaysWhite:
15808         if (WhiteOnMove(forwardMostMove)) {
15809             DisplayError(_("Wait until your turn."), 0);
15810             return;
15811         }
15812         break;
15813       case BeginningOfGame:
15814       case MachinePlaysBlack:
15815         if (!WhiteOnMove(forwardMostMove)) {
15816             DisplayError(_("Wait until your turn."), 0);
15817             return;
15818         }
15819         break;
15820       case EditPosition:
15821         EditPositionDone(TRUE);
15822         break;
15823       case TwoMachinesPlay:
15824         return;
15825       default:
15826         break;
15827     }
15828     SendToProgram("bk\n", &first);
15829     bookOutput[0] = NULLCHAR;
15830     bookRequested = TRUE;
15831 }
15832
15833 void
15834 AboutGameEvent ()
15835 {
15836     char *tags = PGNTags(&gameInfo);
15837     TagsPopUp(tags, CmailMsg());
15838     free(tags);
15839 }
15840
15841 /* end button procedures */
15842
15843 void
15844 PrintPosition (FILE *fp, int move)
15845 {
15846     int i, j;
15847
15848     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15849         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15850             char c = PieceToChar(boards[move][i][j]);
15851             fputc(c == 'x' ? '.' : c, fp);
15852             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15853         }
15854     }
15855     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15856       fprintf(fp, "white to play\n");
15857     else
15858       fprintf(fp, "black to play\n");
15859 }
15860
15861 void
15862 PrintOpponents (FILE *fp)
15863 {
15864     if (gameInfo.white != NULL) {
15865         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15866     } else {
15867         fprintf(fp, "\n");
15868     }
15869 }
15870
15871 /* Find last component of program's own name, using some heuristics */
15872 void
15873 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15874 {
15875     char *p, *q, c;
15876     int local = (strcmp(host, "localhost") == 0);
15877     while (!local && (p = strchr(prog, ';')) != NULL) {
15878         p++;
15879         while (*p == ' ') p++;
15880         prog = p;
15881     }
15882     if (*prog == '"' || *prog == '\'') {
15883         q = strchr(prog + 1, *prog);
15884     } else {
15885         q = strchr(prog, ' ');
15886     }
15887     if (q == NULL) q = prog + strlen(prog);
15888     p = q;
15889     while (p >= prog && *p != '/' && *p != '\\') p--;
15890     p++;
15891     if(p == prog && *p == '"') p++;
15892     c = *q; *q = 0;
15893     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15894     memcpy(buf, p, q - p);
15895     buf[q - p] = NULLCHAR;
15896     if (!local) {
15897         strcat(buf, "@");
15898         strcat(buf, host);
15899     }
15900 }
15901
15902 char *
15903 TimeControlTagValue ()
15904 {
15905     char buf[MSG_SIZ];
15906     if (!appData.clockMode) {
15907       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15908     } else if (movesPerSession > 0) {
15909       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15910     } else if (timeIncrement == 0) {
15911       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15912     } else {
15913       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15914     }
15915     return StrSave(buf);
15916 }
15917
15918 void
15919 SetGameInfo ()
15920 {
15921     /* This routine is used only for certain modes */
15922     VariantClass v = gameInfo.variant;
15923     ChessMove r = GameUnfinished;
15924     char *p = NULL;
15925
15926     if(keepInfo) return;
15927
15928     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15929         r = gameInfo.result;
15930         p = gameInfo.resultDetails;
15931         gameInfo.resultDetails = NULL;
15932     }
15933     ClearGameInfo(&gameInfo);
15934     gameInfo.variant = v;
15935
15936     switch (gameMode) {
15937       case MachinePlaysWhite:
15938         gameInfo.event = StrSave( appData.pgnEventHeader );
15939         gameInfo.site = StrSave(HostName());
15940         gameInfo.date = PGNDate();
15941         gameInfo.round = StrSave("-");
15942         gameInfo.white = StrSave(first.tidy);
15943         gameInfo.black = StrSave(UserName());
15944         gameInfo.timeControl = TimeControlTagValue();
15945         break;
15946
15947       case MachinePlaysBlack:
15948         gameInfo.event = StrSave( appData.pgnEventHeader );
15949         gameInfo.site = StrSave(HostName());
15950         gameInfo.date = PGNDate();
15951         gameInfo.round = StrSave("-");
15952         gameInfo.white = StrSave(UserName());
15953         gameInfo.black = StrSave(first.tidy);
15954         gameInfo.timeControl = TimeControlTagValue();
15955         break;
15956
15957       case TwoMachinesPlay:
15958         gameInfo.event = StrSave( appData.pgnEventHeader );
15959         gameInfo.site = StrSave(HostName());
15960         gameInfo.date = PGNDate();
15961         if (roundNr > 0) {
15962             char buf[MSG_SIZ];
15963             snprintf(buf, MSG_SIZ, "%d", roundNr);
15964             gameInfo.round = StrSave(buf);
15965         } else {
15966             gameInfo.round = StrSave("-");
15967         }
15968         if (first.twoMachinesColor[0] == 'w') {
15969             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15970             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15971         } else {
15972             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15973             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15974         }
15975         gameInfo.timeControl = TimeControlTagValue();
15976         break;
15977
15978       case EditGame:
15979         gameInfo.event = StrSave("Edited game");
15980         gameInfo.site = StrSave(HostName());
15981         gameInfo.date = PGNDate();
15982         gameInfo.round = StrSave("-");
15983         gameInfo.white = StrSave("-");
15984         gameInfo.black = StrSave("-");
15985         gameInfo.result = r;
15986         gameInfo.resultDetails = p;
15987         break;
15988
15989       case EditPosition:
15990         gameInfo.event = StrSave("Edited position");
15991         gameInfo.site = StrSave(HostName());
15992         gameInfo.date = PGNDate();
15993         gameInfo.round = StrSave("-");
15994         gameInfo.white = StrSave("-");
15995         gameInfo.black = StrSave("-");
15996         break;
15997
15998       case IcsPlayingWhite:
15999       case IcsPlayingBlack:
16000       case IcsObserving:
16001       case IcsExamining:
16002         break;
16003
16004       case PlayFromGameFile:
16005         gameInfo.event = StrSave("Game from non-PGN file");
16006         gameInfo.site = StrSave(HostName());
16007         gameInfo.date = PGNDate();
16008         gameInfo.round = StrSave("-");
16009         gameInfo.white = StrSave("?");
16010         gameInfo.black = StrSave("?");
16011         break;
16012
16013       default:
16014         break;
16015     }
16016 }
16017
16018 void
16019 ReplaceComment (int index, char *text)
16020 {
16021     int len;
16022     char *p;
16023     float score;
16024
16025     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16026        pvInfoList[index-1].depth == len &&
16027        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16028        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16029     while (*text == '\n') text++;
16030     len = strlen(text);
16031     while (len > 0 && text[len - 1] == '\n') len--;
16032
16033     if (commentList[index] != NULL)
16034       free(commentList[index]);
16035
16036     if (len == 0) {
16037         commentList[index] = NULL;
16038         return;
16039     }
16040   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16041       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16042       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16043     commentList[index] = (char *) malloc(len + 2);
16044     strncpy(commentList[index], text, len);
16045     commentList[index][len] = '\n';
16046     commentList[index][len + 1] = NULLCHAR;
16047   } else {
16048     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16049     char *p;
16050     commentList[index] = (char *) malloc(len + 7);
16051     safeStrCpy(commentList[index], "{\n", 3);
16052     safeStrCpy(commentList[index]+2, text, len+1);
16053     commentList[index][len+2] = NULLCHAR;
16054     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16055     strcat(commentList[index], "\n}\n");
16056   }
16057 }
16058
16059 void
16060 CrushCRs (char *text)
16061 {
16062   char *p = text;
16063   char *q = text;
16064   char ch;
16065
16066   do {
16067     ch = *p++;
16068     if (ch == '\r') continue;
16069     *q++ = ch;
16070   } while (ch != '\0');
16071 }
16072
16073 void
16074 AppendComment (int index, char *text, Boolean addBraces)
16075 /* addBraces  tells if we should add {} */
16076 {
16077     int oldlen, len;
16078     char *old;
16079
16080 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16081     if(addBraces == 3) addBraces = 0; else // force appending literally
16082     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16083
16084     CrushCRs(text);
16085     while (*text == '\n') text++;
16086     len = strlen(text);
16087     while (len > 0 && text[len - 1] == '\n') len--;
16088     text[len] = NULLCHAR;
16089
16090     if (len == 0) return;
16091
16092     if (commentList[index] != NULL) {
16093       Boolean addClosingBrace = addBraces;
16094         old = commentList[index];
16095         oldlen = strlen(old);
16096         while(commentList[index][oldlen-1] ==  '\n')
16097           commentList[index][--oldlen] = NULLCHAR;
16098         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16099         safeStrCpy(commentList[index], old, oldlen + len + 6);
16100         free(old);
16101         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16102         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16103           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16104           while (*text == '\n') { text++; len--; }
16105           commentList[index][--oldlen] = NULLCHAR;
16106       }
16107         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16108         else          strcat(commentList[index], "\n");
16109         strcat(commentList[index], text);
16110         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16111         else          strcat(commentList[index], "\n");
16112     } else {
16113         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16114         if(addBraces)
16115           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16116         else commentList[index][0] = NULLCHAR;
16117         strcat(commentList[index], text);
16118         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16119         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16120     }
16121 }
16122
16123 static char *
16124 FindStr (char * text, char * sub_text)
16125 {
16126     char * result = strstr( text, sub_text );
16127
16128     if( result != NULL ) {
16129         result += strlen( sub_text );
16130     }
16131
16132     return result;
16133 }
16134
16135 /* [AS] Try to extract PV info from PGN comment */
16136 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16137 char *
16138 GetInfoFromComment (int index, char * text)
16139 {
16140     char * sep = text, *p;
16141
16142     if( text != NULL && index > 0 ) {
16143         int score = 0;
16144         int depth = 0;
16145         int time = -1, sec = 0, deci;
16146         char * s_eval = FindStr( text, "[%eval " );
16147         char * s_emt = FindStr( text, "[%emt " );
16148 #if 0
16149         if( s_eval != NULL || s_emt != NULL ) {
16150 #else
16151         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16152 #endif
16153             /* New style */
16154             char delim;
16155
16156             if( s_eval != NULL ) {
16157                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16158                     return text;
16159                 }
16160
16161                 if( delim != ']' ) {
16162                     return text;
16163                 }
16164             }
16165
16166             if( s_emt != NULL ) {
16167             }
16168                 return text;
16169         }
16170         else {
16171             /* We expect something like: [+|-]nnn.nn/dd */
16172             int score_lo = 0;
16173
16174             if(*text != '{') return text; // [HGM] braces: must be normal comment
16175
16176             sep = strchr( text, '/' );
16177             if( sep == NULL || sep < (text+4) ) {
16178                 return text;
16179             }
16180
16181             p = text;
16182             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16183             if(p[1] == '(') { // comment starts with PV
16184                p = strchr(p, ')'); // locate end of PV
16185                if(p == NULL || sep < p+5) return text;
16186                // at this point we have something like "{(.*) +0.23/6 ..."
16187                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16188                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16189                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16190             }
16191             time = -1; sec = -1; deci = -1;
16192             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16193                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16194                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16195                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16196                 return text;
16197             }
16198
16199             if( score_lo < 0 || score_lo >= 100 ) {
16200                 return text;
16201             }
16202
16203             if(sec >= 0) time = 600*time + 10*sec; else
16204             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16205
16206             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16207
16208             /* [HGM] PV time: now locate end of PV info */
16209             while( *++sep >= '0' && *sep <= '9'); // strip depth
16210             if(time >= 0)
16211             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16212             if(sec >= 0)
16213             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16214             if(deci >= 0)
16215             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16216             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16217         }
16218
16219         if( depth <= 0 ) {
16220             return text;
16221         }
16222
16223         if( time < 0 ) {
16224             time = -1;
16225         }
16226
16227         pvInfoList[index-1].depth = depth;
16228         pvInfoList[index-1].score = score;
16229         pvInfoList[index-1].time  = 10*time; // centi-sec
16230         if(*sep == '}') *sep = 0; else *--sep = '{';
16231         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16232     }
16233     return sep;
16234 }
16235
16236 void
16237 SendToProgram (char *message, ChessProgramState *cps)
16238 {
16239     int count, outCount, error;
16240     char buf[MSG_SIZ];
16241
16242     if (cps->pr == NoProc) return;
16243     Attention(cps);
16244
16245     if (appData.debugMode) {
16246         TimeMark now;
16247         GetTimeMark(&now);
16248         fprintf(debugFP, "%ld >%-6s: %s",
16249                 SubtractTimeMarks(&now, &programStartTime),
16250                 cps->which, message);
16251         if(serverFP)
16252             fprintf(serverFP, "%ld >%-6s: %s",
16253                 SubtractTimeMarks(&now, &programStartTime),
16254                 cps->which, message), fflush(serverFP);
16255     }
16256
16257     count = strlen(message);
16258     outCount = OutputToProcess(cps->pr, message, count, &error);
16259     if (outCount < count && !exiting
16260                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16261       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16262       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16263         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16264             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16265                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16266                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16267                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16268             } else {
16269                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16270                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16271                 gameInfo.result = res;
16272             }
16273             gameInfo.resultDetails = StrSave(buf);
16274         }
16275         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16276         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16277     }
16278 }
16279
16280 void
16281 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16282 {
16283     char *end_str;
16284     char buf[MSG_SIZ];
16285     ChessProgramState *cps = (ChessProgramState *)closure;
16286
16287     if (isr != cps->isr) return; /* Killed intentionally */
16288     if (count <= 0) {
16289         if (count == 0) {
16290             RemoveInputSource(cps->isr);
16291             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16292                     _(cps->which), cps->program);
16293             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16294             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16295                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16296                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16297                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16298                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16299                 } else {
16300                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16301                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16302                     gameInfo.result = res;
16303                 }
16304                 gameInfo.resultDetails = StrSave(buf);
16305             }
16306             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16307             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16308         } else {
16309             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16310                     _(cps->which), cps->program);
16311             RemoveInputSource(cps->isr);
16312
16313             /* [AS] Program is misbehaving badly... kill it */
16314             if( count == -2 ) {
16315                 DestroyChildProcess( cps->pr, 9 );
16316                 cps->pr = NoProc;
16317             }
16318
16319             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16320         }
16321         return;
16322     }
16323
16324     if ((end_str = strchr(message, '\r')) != NULL)
16325       *end_str = NULLCHAR;
16326     if ((end_str = strchr(message, '\n')) != NULL)
16327       *end_str = NULLCHAR;
16328
16329     if (appData.debugMode) {
16330         TimeMark now; int print = 1;
16331         char *quote = ""; char c; int i;
16332
16333         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16334                 char start = message[0];
16335                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16336                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16337                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16338                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16339                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16340                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16341                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16342                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16343                    sscanf(message, "hint: %c", &c)!=1 &&
16344                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16345                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16346                     print = (appData.engineComments >= 2);
16347                 }
16348                 message[0] = start; // restore original message
16349         }
16350         if(print) {
16351                 GetTimeMark(&now);
16352                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16353                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16354                         quote,
16355                         message);
16356                 if(serverFP)
16357                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16358                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16359                         quote,
16360                         message), fflush(serverFP);
16361         }
16362     }
16363
16364     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16365     if (appData.icsEngineAnalyze) {
16366         if (strstr(message, "whisper") != NULL ||
16367              strstr(message, "kibitz") != NULL ||
16368             strstr(message, "tellics") != NULL) return;
16369     }
16370
16371     HandleMachineMove(message, cps);
16372 }
16373
16374
16375 void
16376 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16377 {
16378     char buf[MSG_SIZ];
16379     int seconds;
16380
16381     if( timeControl_2 > 0 ) {
16382         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16383             tc = timeControl_2;
16384         }
16385     }
16386     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16387     inc /= cps->timeOdds;
16388     st  /= cps->timeOdds;
16389
16390     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16391
16392     if (st > 0) {
16393       /* Set exact time per move, normally using st command */
16394       if (cps->stKludge) {
16395         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16396         seconds = st % 60;
16397         if (seconds == 0) {
16398           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16399         } else {
16400           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16401         }
16402       } else {
16403         snprintf(buf, MSG_SIZ, "st %d\n", st);
16404       }
16405     } else {
16406       /* Set conventional or incremental time control, using level command */
16407       if (seconds == 0) {
16408         /* Note old gnuchess bug -- minutes:seconds used to not work.
16409            Fixed in later versions, but still avoid :seconds
16410            when seconds is 0. */
16411         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16412       } else {
16413         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16414                  seconds, inc/1000.);
16415       }
16416     }
16417     SendToProgram(buf, cps);
16418
16419     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16420     /* Orthogonally, limit search to given depth */
16421     if (sd > 0) {
16422       if (cps->sdKludge) {
16423         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16424       } else {
16425         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16426       }
16427       SendToProgram(buf, cps);
16428     }
16429
16430     if(cps->nps >= 0) { /* [HGM] nps */
16431         if(cps->supportsNPS == FALSE)
16432           cps->nps = -1; // don't use if engine explicitly says not supported!
16433         else {
16434           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16435           SendToProgram(buf, cps);
16436         }
16437     }
16438 }
16439
16440 ChessProgramState *
16441 WhitePlayer ()
16442 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16443 {
16444     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16445        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16446         return &second;
16447     return &first;
16448 }
16449
16450 void
16451 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16452 {
16453     char message[MSG_SIZ];
16454     long time, otime;
16455
16456     /* Note: this routine must be called when the clocks are stopped
16457        or when they have *just* been set or switched; otherwise
16458        it will be off by the time since the current tick started.
16459     */
16460     if (machineWhite) {
16461         time = whiteTimeRemaining / 10;
16462         otime = blackTimeRemaining / 10;
16463     } else {
16464         time = blackTimeRemaining / 10;
16465         otime = whiteTimeRemaining / 10;
16466     }
16467     /* [HGM] translate opponent's time by time-odds factor */
16468     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16469
16470     if (time <= 0) time = 1;
16471     if (otime <= 0) otime = 1;
16472
16473     snprintf(message, MSG_SIZ, "time %ld\n", time);
16474     SendToProgram(message, cps);
16475
16476     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16477     SendToProgram(message, cps);
16478 }
16479
16480 char *
16481 EngineDefinedVariant (ChessProgramState *cps, int n)
16482 {   // return name of n-th unknown variant that engine supports
16483     static char buf[MSG_SIZ];
16484     char *p, *s = cps->variants;
16485     if(!s) return NULL;
16486     do { // parse string from variants feature
16487       VariantClass v;
16488         p = strchr(s, ',');
16489         if(p) *p = NULLCHAR;
16490       v = StringToVariant(s);
16491       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16492         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16493             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16494         }
16495         if(p) *p++ = ',';
16496         if(n < 0) return buf;
16497     } while(s = p);
16498     return NULL;
16499 }
16500
16501 int
16502 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16503 {
16504   char buf[MSG_SIZ];
16505   int len = strlen(name);
16506   int val;
16507
16508   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16509     (*p) += len + 1;
16510     sscanf(*p, "%d", &val);
16511     *loc = (val != 0);
16512     while (**p && **p != ' ')
16513       (*p)++;
16514     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16515     SendToProgram(buf, cps);
16516     return TRUE;
16517   }
16518   return FALSE;
16519 }
16520
16521 int
16522 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16523 {
16524   char buf[MSG_SIZ];
16525   int len = strlen(name);
16526   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16527     (*p) += len + 1;
16528     sscanf(*p, "%d", loc);
16529     while (**p && **p != ' ') (*p)++;
16530     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16531     SendToProgram(buf, cps);
16532     return TRUE;
16533   }
16534   return FALSE;
16535 }
16536
16537 int
16538 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16539 {
16540   char buf[MSG_SIZ];
16541   int len = strlen(name);
16542   if (strncmp((*p), name, len) == 0
16543       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16544     (*p) += len + 2;
16545     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16546     sscanf(*p, "%[^\"]", *loc);
16547     while (**p && **p != '\"') (*p)++;
16548     if (**p == '\"') (*p)++;
16549     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16550     SendToProgram(buf, cps);
16551     return TRUE;
16552   }
16553   return FALSE;
16554 }
16555
16556 int
16557 ParseOption (Option *opt, ChessProgramState *cps)
16558 // [HGM] options: process the string that defines an engine option, and determine
16559 // name, type, default value, and allowed value range
16560 {
16561         char *p, *q, buf[MSG_SIZ];
16562         int n, min = (-1)<<31, max = 1<<31, def;
16563
16564         if(p = strstr(opt->name, " -spin ")) {
16565             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16566             if(max < min) max = min; // enforce consistency
16567             if(def < min) def = min;
16568             if(def > max) def = max;
16569             opt->value = def;
16570             opt->min = min;
16571             opt->max = max;
16572             opt->type = Spin;
16573         } else if((p = strstr(opt->name, " -slider "))) {
16574             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16575             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16576             if(max < min) max = min; // enforce consistency
16577             if(def < min) def = min;
16578             if(def > max) def = max;
16579             opt->value = def;
16580             opt->min = min;
16581             opt->max = max;
16582             opt->type = Spin; // Slider;
16583         } else if((p = strstr(opt->name, " -string "))) {
16584             opt->textValue = p+9;
16585             opt->type = TextBox;
16586         } else if((p = strstr(opt->name, " -file "))) {
16587             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16588             opt->textValue = p+7;
16589             opt->type = FileName; // FileName;
16590         } else if((p = strstr(opt->name, " -path "))) {
16591             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16592             opt->textValue = p+7;
16593             opt->type = PathName; // PathName;
16594         } else if(p = strstr(opt->name, " -check ")) {
16595             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16596             opt->value = (def != 0);
16597             opt->type = CheckBox;
16598         } else if(p = strstr(opt->name, " -combo ")) {
16599             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16600             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16601             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16602             opt->value = n = 0;
16603             while(q = StrStr(q, " /// ")) {
16604                 n++; *q = 0;    // count choices, and null-terminate each of them
16605                 q += 5;
16606                 if(*q == '*') { // remember default, which is marked with * prefix
16607                     q++;
16608                     opt->value = n;
16609                 }
16610                 cps->comboList[cps->comboCnt++] = q;
16611             }
16612             cps->comboList[cps->comboCnt++] = NULL;
16613             opt->max = n + 1;
16614             opt->type = ComboBox;
16615         } else if(p = strstr(opt->name, " -button")) {
16616             opt->type = Button;
16617         } else if(p = strstr(opt->name, " -save")) {
16618             opt->type = SaveButton;
16619         } else return FALSE;
16620         *p = 0; // terminate option name
16621         // now look if the command-line options define a setting for this engine option.
16622         if(cps->optionSettings && cps->optionSettings[0])
16623             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16624         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16625           snprintf(buf, MSG_SIZ, "option %s", p);
16626                 if(p = strstr(buf, ",")) *p = 0;
16627                 if(q = strchr(buf, '=')) switch(opt->type) {
16628                     case ComboBox:
16629                         for(n=0; n<opt->max; n++)
16630                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16631                         break;
16632                     case TextBox:
16633                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16634                         break;
16635                     case Spin:
16636                     case CheckBox:
16637                         opt->value = atoi(q+1);
16638                     default:
16639                         break;
16640                 }
16641                 strcat(buf, "\n");
16642                 SendToProgram(buf, cps);
16643         }
16644         return TRUE;
16645 }
16646
16647 void
16648 FeatureDone (ChessProgramState *cps, int val)
16649 {
16650   DelayedEventCallback cb = GetDelayedEvent();
16651   if ((cb == InitBackEnd3 && cps == &first) ||
16652       (cb == SettingsMenuIfReady && cps == &second) ||
16653       (cb == LoadEngine) ||
16654       (cb == TwoMachinesEventIfReady)) {
16655     CancelDelayedEvent();
16656     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16657   }
16658   cps->initDone = val;
16659   if(val) cps->reload = FALSE;
16660 }
16661
16662 /* Parse feature command from engine */
16663 void
16664 ParseFeatures (char *args, ChessProgramState *cps)
16665 {
16666   char *p = args;
16667   char *q = NULL;
16668   int val;
16669   char buf[MSG_SIZ];
16670
16671   for (;;) {
16672     while (*p == ' ') p++;
16673     if (*p == NULLCHAR) return;
16674
16675     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16676     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16677     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16678     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16679     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16680     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16681     if (BoolFeature(&p, "reuse", &val, cps)) {
16682       /* Engine can disable reuse, but can't enable it if user said no */
16683       if (!val) cps->reuse = FALSE;
16684       continue;
16685     }
16686     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16687     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16688       if (gameMode == TwoMachinesPlay) {
16689         DisplayTwoMachinesTitle();
16690       } else {
16691         DisplayTitle("");
16692       }
16693       continue;
16694     }
16695     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16696     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16697     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16698     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16699     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16700     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16701     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16702     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16703     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16704     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16705     if (IntFeature(&p, "done", &val, cps)) {
16706       FeatureDone(cps, val);
16707       continue;
16708     }
16709     /* Added by Tord: */
16710     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16711     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16712     /* End of additions by Tord */
16713
16714     /* [HGM] added features: */
16715     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16716     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16717     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16718     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16719     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16720     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16721     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16722     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16723         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16724         FREE(cps->option[cps->nrOptions].name);
16725         cps->option[cps->nrOptions].name = q; q = NULL;
16726         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16727           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16728             SendToProgram(buf, cps);
16729             continue;
16730         }
16731         if(cps->nrOptions >= MAX_OPTIONS) {
16732             cps->nrOptions--;
16733             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16734             DisplayError(buf, 0);
16735         }
16736         continue;
16737     }
16738     /* End of additions by HGM */
16739
16740     /* unknown feature: complain and skip */
16741     q = p;
16742     while (*q && *q != '=') q++;
16743     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16744     SendToProgram(buf, cps);
16745     p = q;
16746     if (*p == '=') {
16747       p++;
16748       if (*p == '\"') {
16749         p++;
16750         while (*p && *p != '\"') p++;
16751         if (*p == '\"') p++;
16752       } else {
16753         while (*p && *p != ' ') p++;
16754       }
16755     }
16756   }
16757
16758 }
16759
16760 void
16761 PeriodicUpdatesEvent (int newState)
16762 {
16763     if (newState == appData.periodicUpdates)
16764       return;
16765
16766     appData.periodicUpdates=newState;
16767
16768     /* Display type changes, so update it now */
16769 //    DisplayAnalysis();
16770
16771     /* Get the ball rolling again... */
16772     if (newState) {
16773         AnalysisPeriodicEvent(1);
16774         StartAnalysisClock();
16775     }
16776 }
16777
16778 void
16779 PonderNextMoveEvent (int newState)
16780 {
16781     if (newState == appData.ponderNextMove) return;
16782     if (gameMode == EditPosition) EditPositionDone(TRUE);
16783     if (newState) {
16784         SendToProgram("hard\n", &first);
16785         if (gameMode == TwoMachinesPlay) {
16786             SendToProgram("hard\n", &second);
16787         }
16788     } else {
16789         SendToProgram("easy\n", &first);
16790         thinkOutput[0] = NULLCHAR;
16791         if (gameMode == TwoMachinesPlay) {
16792             SendToProgram("easy\n", &second);
16793         }
16794     }
16795     appData.ponderNextMove = newState;
16796 }
16797
16798 void
16799 NewSettingEvent (int option, int *feature, char *command, int value)
16800 {
16801     char buf[MSG_SIZ];
16802
16803     if (gameMode == EditPosition) EditPositionDone(TRUE);
16804     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16805     if(feature == NULL || *feature) SendToProgram(buf, &first);
16806     if (gameMode == TwoMachinesPlay) {
16807         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16808     }
16809 }
16810
16811 void
16812 ShowThinkingEvent ()
16813 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16814 {
16815     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16816     int newState = appData.showThinking
16817         // [HGM] thinking: other features now need thinking output as well
16818         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16819
16820     if (oldState == newState) return;
16821     oldState = newState;
16822     if (gameMode == EditPosition) EditPositionDone(TRUE);
16823     if (oldState) {
16824         SendToProgram("post\n", &first);
16825         if (gameMode == TwoMachinesPlay) {
16826             SendToProgram("post\n", &second);
16827         }
16828     } else {
16829         SendToProgram("nopost\n", &first);
16830         thinkOutput[0] = NULLCHAR;
16831         if (gameMode == TwoMachinesPlay) {
16832             SendToProgram("nopost\n", &second);
16833         }
16834     }
16835 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16836 }
16837
16838 void
16839 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16840 {
16841   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16842   if (pr == NoProc) return;
16843   AskQuestion(title, question, replyPrefix, pr);
16844 }
16845
16846 void
16847 TypeInEvent (char firstChar)
16848 {
16849     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16850         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16851         gameMode == AnalyzeMode || gameMode == EditGame ||
16852         gameMode == EditPosition || gameMode == IcsExamining ||
16853         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16854         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16855                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16856                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16857         gameMode == Training) PopUpMoveDialog(firstChar);
16858 }
16859
16860 void
16861 TypeInDoneEvent (char *move)
16862 {
16863         Board board;
16864         int n, fromX, fromY, toX, toY;
16865         char promoChar;
16866         ChessMove moveType;
16867
16868         // [HGM] FENedit
16869         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16870                 EditPositionPasteFEN(move);
16871                 return;
16872         }
16873         // [HGM] movenum: allow move number to be typed in any mode
16874         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16875           ToNrEvent(2*n-1);
16876           return;
16877         }
16878         // undocumented kludge: allow command-line option to be typed in!
16879         // (potentially fatal, and does not implement the effect of the option.)
16880         // should only be used for options that are values on which future decisions will be made,
16881         // and definitely not on options that would be used during initialization.
16882         if(strstr(move, "!!! -") == move) {
16883             ParseArgsFromString(move+4);
16884             return;
16885         }
16886
16887       if (gameMode != EditGame && currentMove != forwardMostMove &&
16888         gameMode != Training) {
16889         DisplayMoveError(_("Displayed move is not current"));
16890       } else {
16891         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16892           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16893         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16894         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16895           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16896           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16897         } else {
16898           DisplayMoveError(_("Could not parse move"));
16899         }
16900       }
16901 }
16902
16903 void
16904 DisplayMove (int moveNumber)
16905 {
16906     char message[MSG_SIZ];
16907     char res[MSG_SIZ];
16908     char cpThinkOutput[MSG_SIZ];
16909
16910     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16911
16912     if (moveNumber == forwardMostMove - 1 ||
16913         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16914
16915         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16916
16917         if (strchr(cpThinkOutput, '\n')) {
16918             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16919         }
16920     } else {
16921         *cpThinkOutput = NULLCHAR;
16922     }
16923
16924     /* [AS] Hide thinking from human user */
16925     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16926         *cpThinkOutput = NULLCHAR;
16927         if( thinkOutput[0] != NULLCHAR ) {
16928             int i;
16929
16930             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16931                 cpThinkOutput[i] = '.';
16932             }
16933             cpThinkOutput[i] = NULLCHAR;
16934             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16935         }
16936     }
16937
16938     if (moveNumber == forwardMostMove - 1 &&
16939         gameInfo.resultDetails != NULL) {
16940         if (gameInfo.resultDetails[0] == NULLCHAR) {
16941           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16942         } else {
16943           snprintf(res, MSG_SIZ, " {%s} %s",
16944                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16945         }
16946     } else {
16947         res[0] = NULLCHAR;
16948     }
16949
16950     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16951         DisplayMessage(res, cpThinkOutput);
16952     } else {
16953       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16954                 WhiteOnMove(moveNumber) ? " " : ".. ",
16955                 parseList[moveNumber], res);
16956         DisplayMessage(message, cpThinkOutput);
16957     }
16958 }
16959
16960 void
16961 DisplayComment (int moveNumber, char *text)
16962 {
16963     char title[MSG_SIZ];
16964
16965     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16966       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16967     } else {
16968       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16969               WhiteOnMove(moveNumber) ? " " : ".. ",
16970               parseList[moveNumber]);
16971     }
16972     if (text != NULL && (appData.autoDisplayComment || commentUp))
16973         CommentPopUp(title, text);
16974 }
16975
16976 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16977  * might be busy thinking or pondering.  It can be omitted if your
16978  * gnuchess is configured to stop thinking immediately on any user
16979  * input.  However, that gnuchess feature depends on the FIONREAD
16980  * ioctl, which does not work properly on some flavors of Unix.
16981  */
16982 void
16983 Attention (ChessProgramState *cps)
16984 {
16985 #if ATTENTION
16986     if (!cps->useSigint) return;
16987     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16988     switch (gameMode) {
16989       case MachinePlaysWhite:
16990       case MachinePlaysBlack:
16991       case TwoMachinesPlay:
16992       case IcsPlayingWhite:
16993       case IcsPlayingBlack:
16994       case AnalyzeMode:
16995       case AnalyzeFile:
16996         /* Skip if we know it isn't thinking */
16997         if (!cps->maybeThinking) return;
16998         if (appData.debugMode)
16999           fprintf(debugFP, "Interrupting %s\n", cps->which);
17000         InterruptChildProcess(cps->pr);
17001         cps->maybeThinking = FALSE;
17002         break;
17003       default:
17004         break;
17005     }
17006 #endif /*ATTENTION*/
17007 }
17008
17009 int
17010 CheckFlags ()
17011 {
17012     if (whiteTimeRemaining <= 0) {
17013         if (!whiteFlag) {
17014             whiteFlag = TRUE;
17015             if (appData.icsActive) {
17016                 if (appData.autoCallFlag &&
17017                     gameMode == IcsPlayingBlack && !blackFlag) {
17018                   SendToICS(ics_prefix);
17019                   SendToICS("flag\n");
17020                 }
17021             } else {
17022                 if (blackFlag) {
17023                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17024                 } else {
17025                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17026                     if (appData.autoCallFlag) {
17027                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17028                         return TRUE;
17029                     }
17030                 }
17031             }
17032         }
17033     }
17034     if (blackTimeRemaining <= 0) {
17035         if (!blackFlag) {
17036             blackFlag = TRUE;
17037             if (appData.icsActive) {
17038                 if (appData.autoCallFlag &&
17039                     gameMode == IcsPlayingWhite && !whiteFlag) {
17040                   SendToICS(ics_prefix);
17041                   SendToICS("flag\n");
17042                 }
17043             } else {
17044                 if (whiteFlag) {
17045                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17046                 } else {
17047                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17048                     if (appData.autoCallFlag) {
17049                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17050                         return TRUE;
17051                     }
17052                 }
17053             }
17054         }
17055     }
17056     return FALSE;
17057 }
17058
17059 void
17060 CheckTimeControl ()
17061 {
17062     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17063         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17064
17065     /*
17066      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17067      */
17068     if ( !WhiteOnMove(forwardMostMove) ) {
17069         /* White made time control */
17070         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17071         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17072         /* [HGM] time odds: correct new time quota for time odds! */
17073                                             / WhitePlayer()->timeOdds;
17074         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17075     } else {
17076         lastBlack -= blackTimeRemaining;
17077         /* Black made time control */
17078         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17079                                             / WhitePlayer()->other->timeOdds;
17080         lastWhite = whiteTimeRemaining;
17081     }
17082 }
17083
17084 void
17085 DisplayBothClocks ()
17086 {
17087     int wom = gameMode == EditPosition ?
17088       !blackPlaysFirst : WhiteOnMove(currentMove);
17089     DisplayWhiteClock(whiteTimeRemaining, wom);
17090     DisplayBlackClock(blackTimeRemaining, !wom);
17091 }
17092
17093
17094 /* Timekeeping seems to be a portability nightmare.  I think everyone
17095    has ftime(), but I'm really not sure, so I'm including some ifdefs
17096    to use other calls if you don't.  Clocks will be less accurate if
17097    you have neither ftime nor gettimeofday.
17098 */
17099
17100 /* VS 2008 requires the #include outside of the function */
17101 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17102 #include <sys/timeb.h>
17103 #endif
17104
17105 /* Get the current time as a TimeMark */
17106 void
17107 GetTimeMark (TimeMark *tm)
17108 {
17109 #if HAVE_GETTIMEOFDAY
17110
17111     struct timeval timeVal;
17112     struct timezone timeZone;
17113
17114     gettimeofday(&timeVal, &timeZone);
17115     tm->sec = (long) timeVal.tv_sec;
17116     tm->ms = (int) (timeVal.tv_usec / 1000L);
17117
17118 #else /*!HAVE_GETTIMEOFDAY*/
17119 #if HAVE_FTIME
17120
17121 // include <sys/timeb.h> / moved to just above start of function
17122     struct timeb timeB;
17123
17124     ftime(&timeB);
17125     tm->sec = (long) timeB.time;
17126     tm->ms = (int) timeB.millitm;
17127
17128 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17129     tm->sec = (long) time(NULL);
17130     tm->ms = 0;
17131 #endif
17132 #endif
17133 }
17134
17135 /* Return the difference in milliseconds between two
17136    time marks.  We assume the difference will fit in a long!
17137 */
17138 long
17139 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17140 {
17141     return 1000L*(tm2->sec - tm1->sec) +
17142            (long) (tm2->ms - tm1->ms);
17143 }
17144
17145
17146 /*
17147  * Code to manage the game clocks.
17148  *
17149  * In tournament play, black starts the clock and then white makes a move.
17150  * We give the human user a slight advantage if he is playing white---the
17151  * clocks don't run until he makes his first move, so it takes zero time.
17152  * Also, we don't account for network lag, so we could get out of sync
17153  * with GNU Chess's clock -- but then, referees are always right.
17154  */
17155
17156 static TimeMark tickStartTM;
17157 static long intendedTickLength;
17158
17159 long
17160 NextTickLength (long timeRemaining)
17161 {
17162     long nominalTickLength, nextTickLength;
17163
17164     if (timeRemaining > 0L && timeRemaining <= 10000L)
17165       nominalTickLength = 100L;
17166     else
17167       nominalTickLength = 1000L;
17168     nextTickLength = timeRemaining % nominalTickLength;
17169     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17170
17171     return nextTickLength;
17172 }
17173
17174 /* Adjust clock one minute up or down */
17175 void
17176 AdjustClock (Boolean which, int dir)
17177 {
17178     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17179     if(which) blackTimeRemaining += 60000*dir;
17180     else      whiteTimeRemaining += 60000*dir;
17181     DisplayBothClocks();
17182     adjustedClock = TRUE;
17183 }
17184
17185 /* Stop clocks and reset to a fresh time control */
17186 void
17187 ResetClocks ()
17188 {
17189     (void) StopClockTimer();
17190     if (appData.icsActive) {
17191         whiteTimeRemaining = blackTimeRemaining = 0;
17192     } else if (searchTime) {
17193         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17194         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17195     } else { /* [HGM] correct new time quote for time odds */
17196         whiteTC = blackTC = fullTimeControlString;
17197         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17198         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17199     }
17200     if (whiteFlag || blackFlag) {
17201         DisplayTitle("");
17202         whiteFlag = blackFlag = FALSE;
17203     }
17204     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17205     DisplayBothClocks();
17206     adjustedClock = FALSE;
17207 }
17208
17209 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17210
17211 /* Decrement running clock by amount of time that has passed */
17212 void
17213 DecrementClocks ()
17214 {
17215     long timeRemaining;
17216     long lastTickLength, fudge;
17217     TimeMark now;
17218
17219     if (!appData.clockMode) return;
17220     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17221
17222     GetTimeMark(&now);
17223
17224     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17225
17226     /* Fudge if we woke up a little too soon */
17227     fudge = intendedTickLength - lastTickLength;
17228     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17229
17230     if (WhiteOnMove(forwardMostMove)) {
17231         if(whiteNPS >= 0) lastTickLength = 0;
17232         timeRemaining = whiteTimeRemaining -= lastTickLength;
17233         if(timeRemaining < 0 && !appData.icsActive) {
17234             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17235             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17236                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17237                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17238             }
17239         }
17240         DisplayWhiteClock(whiteTimeRemaining - fudge,
17241                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17242     } else {
17243         if(blackNPS >= 0) lastTickLength = 0;
17244         timeRemaining = blackTimeRemaining -= lastTickLength;
17245         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17246             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17247             if(suddenDeath) {
17248                 blackStartMove = forwardMostMove;
17249                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17250             }
17251         }
17252         DisplayBlackClock(blackTimeRemaining - fudge,
17253                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17254     }
17255     if (CheckFlags()) return;
17256
17257     if(twoBoards) { // count down secondary board's clocks as well
17258         activePartnerTime -= lastTickLength;
17259         partnerUp = 1;
17260         if(activePartner == 'W')
17261             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17262         else
17263             DisplayBlackClock(activePartnerTime, TRUE);
17264         partnerUp = 0;
17265     }
17266
17267     tickStartTM = now;
17268     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17269     StartClockTimer(intendedTickLength);
17270
17271     /* if the time remaining has fallen below the alarm threshold, sound the
17272      * alarm. if the alarm has sounded and (due to a takeback or time control
17273      * with increment) the time remaining has increased to a level above the
17274      * threshold, reset the alarm so it can sound again.
17275      */
17276
17277     if (appData.icsActive && appData.icsAlarm) {
17278
17279         /* make sure we are dealing with the user's clock */
17280         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17281                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17282            )) return;
17283
17284         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17285             alarmSounded = FALSE;
17286         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17287             PlayAlarmSound();
17288             alarmSounded = TRUE;
17289         }
17290     }
17291 }
17292
17293
17294 /* A player has just moved, so stop the previously running
17295    clock and (if in clock mode) start the other one.
17296    We redisplay both clocks in case we're in ICS mode, because
17297    ICS gives us an update to both clocks after every move.
17298    Note that this routine is called *after* forwardMostMove
17299    is updated, so the last fractional tick must be subtracted
17300    from the color that is *not* on move now.
17301 */
17302 void
17303 SwitchClocks (int newMoveNr)
17304 {
17305     long lastTickLength;
17306     TimeMark now;
17307     int flagged = FALSE;
17308
17309     GetTimeMark(&now);
17310
17311     if (StopClockTimer() && appData.clockMode) {
17312         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17313         if (!WhiteOnMove(forwardMostMove)) {
17314             if(blackNPS >= 0) lastTickLength = 0;
17315             blackTimeRemaining -= lastTickLength;
17316            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17317 //         if(pvInfoList[forwardMostMove].time == -1)
17318                  pvInfoList[forwardMostMove].time =               // use GUI time
17319                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17320         } else {
17321            if(whiteNPS >= 0) lastTickLength = 0;
17322            whiteTimeRemaining -= lastTickLength;
17323            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17324 //         if(pvInfoList[forwardMostMove].time == -1)
17325                  pvInfoList[forwardMostMove].time =
17326                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17327         }
17328         flagged = CheckFlags();
17329     }
17330     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17331     CheckTimeControl();
17332
17333     if (flagged || !appData.clockMode) return;
17334
17335     switch (gameMode) {
17336       case MachinePlaysBlack:
17337       case MachinePlaysWhite:
17338       case BeginningOfGame:
17339         if (pausing) return;
17340         break;
17341
17342       case EditGame:
17343       case PlayFromGameFile:
17344       case IcsExamining:
17345         return;
17346
17347       default:
17348         break;
17349     }
17350
17351     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17352         if(WhiteOnMove(forwardMostMove))
17353              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17354         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17355     }
17356
17357     tickStartTM = now;
17358     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17359       whiteTimeRemaining : blackTimeRemaining);
17360     StartClockTimer(intendedTickLength);
17361 }
17362
17363
17364 /* Stop both clocks */
17365 void
17366 StopClocks ()
17367 {
17368     long lastTickLength;
17369     TimeMark now;
17370
17371     if (!StopClockTimer()) return;
17372     if (!appData.clockMode) return;
17373
17374     GetTimeMark(&now);
17375
17376     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17377     if (WhiteOnMove(forwardMostMove)) {
17378         if(whiteNPS >= 0) lastTickLength = 0;
17379         whiteTimeRemaining -= lastTickLength;
17380         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17381     } else {
17382         if(blackNPS >= 0) lastTickLength = 0;
17383         blackTimeRemaining -= lastTickLength;
17384         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17385     }
17386     CheckFlags();
17387 }
17388
17389 /* Start clock of player on move.  Time may have been reset, so
17390    if clock is already running, stop and restart it. */
17391 void
17392 StartClocks ()
17393 {
17394     (void) StopClockTimer(); /* in case it was running already */
17395     DisplayBothClocks();
17396     if (CheckFlags()) return;
17397
17398     if (!appData.clockMode) return;
17399     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17400
17401     GetTimeMark(&tickStartTM);
17402     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17403       whiteTimeRemaining : blackTimeRemaining);
17404
17405    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17406     whiteNPS = blackNPS = -1;
17407     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17408        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17409         whiteNPS = first.nps;
17410     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17411        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17412         blackNPS = first.nps;
17413     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17414         whiteNPS = second.nps;
17415     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17416         blackNPS = second.nps;
17417     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17418
17419     StartClockTimer(intendedTickLength);
17420 }
17421
17422 char *
17423 TimeString (long ms)
17424 {
17425     long second, minute, hour, day;
17426     char *sign = "";
17427     static char buf[32];
17428
17429     if (ms > 0 && ms <= 9900) {
17430       /* convert milliseconds to tenths, rounding up */
17431       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17432
17433       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17434       return buf;
17435     }
17436
17437     /* convert milliseconds to seconds, rounding up */
17438     /* use floating point to avoid strangeness of integer division
17439        with negative dividends on many machines */
17440     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17441
17442     if (second < 0) {
17443         sign = "-";
17444         second = -second;
17445     }
17446
17447     day = second / (60 * 60 * 24);
17448     second = second % (60 * 60 * 24);
17449     hour = second / (60 * 60);
17450     second = second % (60 * 60);
17451     minute = second / 60;
17452     second = second % 60;
17453
17454     if (day > 0)
17455       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17456               sign, day, hour, minute, second);
17457     else if (hour > 0)
17458       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17459     else
17460       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17461
17462     return buf;
17463 }
17464
17465
17466 /*
17467  * This is necessary because some C libraries aren't ANSI C compliant yet.
17468  */
17469 char *
17470 StrStr (char *string, char *match)
17471 {
17472     int i, length;
17473
17474     length = strlen(match);
17475
17476     for (i = strlen(string) - length; i >= 0; i--, string++)
17477       if (!strncmp(match, string, length))
17478         return string;
17479
17480     return NULL;
17481 }
17482
17483 char *
17484 StrCaseStr (char *string, char *match)
17485 {
17486     int i, j, length;
17487
17488     length = strlen(match);
17489
17490     for (i = strlen(string) - length; i >= 0; i--, string++) {
17491         for (j = 0; j < length; j++) {
17492             if (ToLower(match[j]) != ToLower(string[j]))
17493               break;
17494         }
17495         if (j == length) return string;
17496     }
17497
17498     return NULL;
17499 }
17500
17501 #ifndef _amigados
17502 int
17503 StrCaseCmp (char *s1, char *s2)
17504 {
17505     char c1, c2;
17506
17507     for (;;) {
17508         c1 = ToLower(*s1++);
17509         c2 = ToLower(*s2++);
17510         if (c1 > c2) return 1;
17511         if (c1 < c2) return -1;
17512         if (c1 == NULLCHAR) return 0;
17513     }
17514 }
17515
17516
17517 int
17518 ToLower (int c)
17519 {
17520     return isupper(c) ? tolower(c) : c;
17521 }
17522
17523
17524 int
17525 ToUpper (int c)
17526 {
17527     return islower(c) ? toupper(c) : c;
17528 }
17529 #endif /* !_amigados    */
17530
17531 char *
17532 StrSave (char *s)
17533 {
17534   char *ret;
17535
17536   if ((ret = (char *) malloc(strlen(s) + 1)))
17537     {
17538       safeStrCpy(ret, s, strlen(s)+1);
17539     }
17540   return ret;
17541 }
17542
17543 char *
17544 StrSavePtr (char *s, char **savePtr)
17545 {
17546     if (*savePtr) {
17547         free(*savePtr);
17548     }
17549     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17550       safeStrCpy(*savePtr, s, strlen(s)+1);
17551     }
17552     return(*savePtr);
17553 }
17554
17555 char *
17556 PGNDate ()
17557 {
17558     time_t clock;
17559     struct tm *tm;
17560     char buf[MSG_SIZ];
17561
17562     clock = time((time_t *)NULL);
17563     tm = localtime(&clock);
17564     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17565             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17566     return StrSave(buf);
17567 }
17568
17569
17570 char *
17571 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17572 {
17573     int i, j, fromX, fromY, toX, toY;
17574     int whiteToPlay;
17575     char buf[MSG_SIZ];
17576     char *p, *q;
17577     int emptycount;
17578     ChessSquare piece;
17579
17580     whiteToPlay = (gameMode == EditPosition) ?
17581       !blackPlaysFirst : (move % 2 == 0);
17582     p = buf;
17583
17584     /* Piece placement data */
17585     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17586         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17587         emptycount = 0;
17588         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17589             if (boards[move][i][j] == EmptySquare) {
17590                 emptycount++;
17591             } else { ChessSquare piece = boards[move][i][j];
17592                 if (emptycount > 0) {
17593                     if(emptycount<10) /* [HGM] can be >= 10 */
17594                         *p++ = '0' + emptycount;
17595                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17596                     emptycount = 0;
17597                 }
17598                 if(PieceToChar(piece) == '+') {
17599                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17600                     *p++ = '+';
17601                     piece = (ChessSquare)(DEMOTED piece);
17602                 }
17603                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17604                 if(p[-1] == '~') {
17605                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17606                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17607                     *p++ = '~';
17608                 }
17609             }
17610         }
17611         if (emptycount > 0) {
17612             if(emptycount<10) /* [HGM] can be >= 10 */
17613                 *p++ = '0' + emptycount;
17614             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17615             emptycount = 0;
17616         }
17617         *p++ = '/';
17618     }
17619     *(p - 1) = ' ';
17620
17621     /* [HGM] print Crazyhouse or Shogi holdings */
17622     if( gameInfo.holdingsWidth ) {
17623         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17624         q = p;
17625         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17626             piece = boards[move][i][BOARD_WIDTH-1];
17627             if( piece != EmptySquare )
17628               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17629                   *p++ = PieceToChar(piece);
17630         }
17631         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17632             piece = boards[move][BOARD_HEIGHT-i-1][0];
17633             if( piece != EmptySquare )
17634               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17635                   *p++ = PieceToChar(piece);
17636         }
17637
17638         if( q == p ) *p++ = '-';
17639         *p++ = ']';
17640         *p++ = ' ';
17641     }
17642
17643     /* Active color */
17644     *p++ = whiteToPlay ? 'w' : 'b';
17645     *p++ = ' ';
17646
17647   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17648     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17649   } else {
17650   if(nrCastlingRights) {
17651      q = p;
17652      if(appData.fischerCastling) {
17653        /* [HGM] write directly from rights */
17654            if(boards[move][CASTLING][2] != NoRights &&
17655               boards[move][CASTLING][0] != NoRights   )
17656                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17657            if(boards[move][CASTLING][2] != NoRights &&
17658               boards[move][CASTLING][1] != NoRights   )
17659                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17660            if(boards[move][CASTLING][5] != NoRights &&
17661               boards[move][CASTLING][3] != NoRights   )
17662                 *p++ = boards[move][CASTLING][3] + AAA;
17663            if(boards[move][CASTLING][5] != NoRights &&
17664               boards[move][CASTLING][4] != NoRights   )
17665                 *p++ = boards[move][CASTLING][4] + AAA;
17666      } else {
17667
17668         /* [HGM] write true castling rights */
17669         if( nrCastlingRights == 6 ) {
17670             int q, k=0;
17671             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17672                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17673             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17674                  boards[move][CASTLING][2] != NoRights  );
17675             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17676                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17677                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17678                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17679                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17680             }
17681             if(q) *p++ = 'Q';
17682             k = 0;
17683             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17684                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17685             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17686                  boards[move][CASTLING][5] != NoRights  );
17687             if(gameInfo.variant == VariantSChess) {
17688                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17689                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17690                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17691                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17692             }
17693             if(q) *p++ = 'q';
17694         }
17695      }
17696      if (q == p) *p++ = '-'; /* No castling rights */
17697      *p++ = ' ';
17698   }
17699
17700   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17701      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17702      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17703     /* En passant target square */
17704     if (move > backwardMostMove) {
17705         fromX = moveList[move - 1][0] - AAA;
17706         fromY = moveList[move - 1][1] - ONE;
17707         toX = moveList[move - 1][2] - AAA;
17708         toY = moveList[move - 1][3] - ONE;
17709         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17710             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17711             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17712             fromX == toX) {
17713             /* 2-square pawn move just happened */
17714             *p++ = toX + AAA;
17715             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17716         } else {
17717             *p++ = '-';
17718         }
17719     } else if(move == backwardMostMove) {
17720         // [HGM] perhaps we should always do it like this, and forget the above?
17721         if((signed char)boards[move][EP_STATUS] >= 0) {
17722             *p++ = boards[move][EP_STATUS] + AAA;
17723             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17724         } else {
17725             *p++ = '-';
17726         }
17727     } else {
17728         *p++ = '-';
17729     }
17730     *p++ = ' ';
17731   }
17732   }
17733
17734     if(moveCounts)
17735     {   int i = 0, j=move;
17736
17737         /* [HGM] find reversible plies */
17738         if (appData.debugMode) { int k;
17739             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17740             for(k=backwardMostMove; k<=forwardMostMove; k++)
17741                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17742
17743         }
17744
17745         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17746         if( j == backwardMostMove ) i += initialRulePlies;
17747         sprintf(p, "%d ", i);
17748         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17749
17750         /* Fullmove number */
17751         sprintf(p, "%d", (move / 2) + 1);
17752     } else *--p = NULLCHAR;
17753
17754     return StrSave(buf);
17755 }
17756
17757 Boolean
17758 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17759 {
17760     int i, j, k, w=0, subst=0, shuffle=0;
17761     char *p, c;
17762     int emptycount, virgin[BOARD_FILES];
17763     ChessSquare piece;
17764
17765     p = fen;
17766
17767     /* Piece placement data */
17768     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17769         j = 0;
17770         for (;;) {
17771             if (*p == '/' || *p == ' ' || *p == '[' ) {
17772                 if(j > w) w = j;
17773                 emptycount = gameInfo.boardWidth - j;
17774                 while (emptycount--)
17775                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17776                 if (*p == '/') p++;
17777                 else if(autoSize) { // we stumbled unexpectedly into end of board
17778                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17779                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17780                     }
17781                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17782                 }
17783                 break;
17784 #if(BOARD_FILES >= 10)
17785             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17786                 p++; emptycount=10;
17787                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17788                 while (emptycount--)
17789                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17790 #endif
17791             } else if (*p == '*') {
17792                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17793             } else if (isdigit(*p)) {
17794                 emptycount = *p++ - '0';
17795                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17796                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17797                 while (emptycount--)
17798                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17799             } else if (*p == '<') {
17800                 if(i == BOARD_HEIGHT-1) shuffle = 1;
17801                 else if (i != 0 || !shuffle) return FALSE;
17802                 p++;
17803             } else if (shuffle && *p == '>') {
17804                 p++; // for now ignore closing shuffle range, and assume rank-end
17805             } else if (*p == '?') {
17806                 if (j >= gameInfo.boardWidth) return FALSE;
17807                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
17808                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
17809             } else if (*p == '+' || isalpha(*p)) {
17810                 if (j >= gameInfo.boardWidth) return FALSE;
17811                 if(*p=='+') {
17812                     piece = CharToPiece(*++p);
17813                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17814                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17815                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17816                 } else piece = CharToPiece(*p++);
17817
17818                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17819                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17820                     piece = (ChessSquare) (PROMOTED piece);
17821                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17822                     p++;
17823                 }
17824                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17825             } else {
17826                 return FALSE;
17827             }
17828         }
17829     }
17830     while (*p == '/' || *p == ' ') p++;
17831
17832     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17833
17834     /* [HGM] by default clear Crazyhouse holdings, if present */
17835     if(gameInfo.holdingsWidth) {
17836        for(i=0; i<BOARD_HEIGHT; i++) {
17837            board[i][0]             = EmptySquare; /* black holdings */
17838            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17839            board[i][1]             = (ChessSquare) 0; /* black counts */
17840            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17841        }
17842     }
17843
17844     /* [HGM] look for Crazyhouse holdings here */
17845     while(*p==' ') p++;
17846     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17847         int swap=0, wcnt=0, bcnt=0;
17848         if(*p == '[') p++;
17849         if(*p == '<') swap++, p++;
17850         if(*p == '-' ) p++; /* empty holdings */ else {
17851             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17852             /* if we would allow FEN reading to set board size, we would   */
17853             /* have to add holdings and shift the board read so far here   */
17854             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17855                 p++;
17856                 if((int) piece >= (int) BlackPawn ) {
17857                     i = (int)piece - (int)BlackPawn;
17858                     i = PieceToNumber((ChessSquare)i);
17859                     if( i >= gameInfo.holdingsSize ) return FALSE;
17860                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17861                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17862                     bcnt++;
17863                 } else {
17864                     i = (int)piece - (int)WhitePawn;
17865                     i = PieceToNumber((ChessSquare)i);
17866                     if( i >= gameInfo.holdingsSize ) return FALSE;
17867                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17868                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17869                     wcnt++;
17870                 }
17871             }
17872             if(subst) { // substitute back-rank question marks by holdings pieces
17873                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
17874                     int k, m, n = bcnt + 1;
17875                     if(board[0][j] == ClearBoard) {
17876                         if(!wcnt) return FALSE;
17877                         n = rand() % wcnt;
17878                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
17879                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
17880                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
17881                             break;
17882                         }
17883                     }
17884                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
17885                         if(!bcnt) return FALSE;
17886                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
17887                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
17888                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
17889                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
17890                             break;
17891                         }
17892                     }
17893                 }
17894                 subst = 0;
17895             }
17896         }
17897         if(*p == ']') p++;
17898     }
17899
17900     if(subst) return FALSE; // substitution requested, but no holdings
17901
17902     while(*p == ' ') p++;
17903
17904     /* Active color */
17905     c = *p++;
17906     if(appData.colorNickNames) {
17907       if( c == appData.colorNickNames[0] ) c = 'w'; else
17908       if( c == appData.colorNickNames[1] ) c = 'b';
17909     }
17910     switch (c) {
17911       case 'w':
17912         *blackPlaysFirst = FALSE;
17913         break;
17914       case 'b':
17915         *blackPlaysFirst = TRUE;
17916         break;
17917       default:
17918         return FALSE;
17919     }
17920
17921     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17922     /* return the extra info in global variiables             */
17923
17924     /* set defaults in case FEN is incomplete */
17925     board[EP_STATUS] = EP_UNKNOWN;
17926     for(i=0; i<nrCastlingRights; i++ ) {
17927         board[CASTLING][i] =
17928             appData.fischerCastling ? NoRights : initialRights[i];
17929     }   /* assume possible unless obviously impossible */
17930     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17931     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17932     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17933                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17934     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17935     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17936     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17937                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17938     FENrulePlies = 0;
17939
17940     while(*p==' ') p++;
17941     if(nrCastlingRights) {
17942       int fischer = 0;
17943       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17944       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17945           /* castling indicator present, so default becomes no castlings */
17946           for(i=0; i<nrCastlingRights; i++ ) {
17947                  board[CASTLING][i] = NoRights;
17948           }
17949       }
17950       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17951              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
17952              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17953              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17954         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17955
17956         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17957             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17958             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17959         }
17960         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17961             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17962         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17963                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17964         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17965                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17966         switch(c) {
17967           case'K':
17968               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17969               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17970               board[CASTLING][2] = whiteKingFile;
17971               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17972               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17973               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
17974               break;
17975           case'Q':
17976               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17977               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17978               board[CASTLING][2] = whiteKingFile;
17979               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17980               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17981               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
17982               break;
17983           case'k':
17984               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17985               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17986               board[CASTLING][5] = blackKingFile;
17987               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17988               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17989               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
17990               break;
17991           case'q':
17992               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17993               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17994               board[CASTLING][5] = blackKingFile;
17995               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17996               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17997               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
17998           case '-':
17999               break;
18000           default: /* FRC castlings */
18001               if(c >= 'a') { /* black rights */
18002                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18003                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18004                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18005                   if(i == BOARD_RGHT) break;
18006                   board[CASTLING][5] = i;
18007                   c -= AAA;
18008                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18009                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18010                   if(c > i)
18011                       board[CASTLING][3] = c;
18012                   else
18013                       board[CASTLING][4] = c;
18014               } else { /* white rights */
18015                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18016                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18017                     if(board[0][i] == WhiteKing) break;
18018                   if(i == BOARD_RGHT) break;
18019                   board[CASTLING][2] = i;
18020                   c -= AAA - 'a' + 'A';
18021                   if(board[0][c] >= WhiteKing) break;
18022                   if(c > i)
18023                       board[CASTLING][0] = c;
18024                   else
18025                       board[CASTLING][1] = c;
18026               }
18027         }
18028       }
18029       for(i=0; i<nrCastlingRights; i++)
18030         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18031       if(gameInfo.variant == VariantSChess)
18032         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18033       if(fischer && shuffle) appData.fischerCastling = TRUE;
18034     if (appData.debugMode) {
18035         fprintf(debugFP, "FEN castling rights:");
18036         for(i=0; i<nrCastlingRights; i++)
18037         fprintf(debugFP, " %d", board[CASTLING][i]);
18038         fprintf(debugFP, "\n");
18039     }
18040
18041       while(*p==' ') p++;
18042     }
18043
18044     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18045
18046     /* read e.p. field in games that know e.p. capture */
18047     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18048        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18049        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18050       if(*p=='-') {
18051         p++; board[EP_STATUS] = EP_NONE;
18052       } else {
18053          char c = *p++ - AAA;
18054
18055          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18056          if(*p >= '0' && *p <='9') p++;
18057          board[EP_STATUS] = c;
18058       }
18059     }
18060
18061
18062     if(sscanf(p, "%d", &i) == 1) {
18063         FENrulePlies = i; /* 50-move ply counter */
18064         /* (The move number is still ignored)    */
18065     }
18066
18067     return TRUE;
18068 }
18069
18070 void
18071 EditPositionPasteFEN (char *fen)
18072 {
18073   if (fen != NULL) {
18074     Board initial_position;
18075
18076     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18077       DisplayError(_("Bad FEN position in clipboard"), 0);
18078       return ;
18079     } else {
18080       int savedBlackPlaysFirst = blackPlaysFirst;
18081       EditPositionEvent();
18082       blackPlaysFirst = savedBlackPlaysFirst;
18083       CopyBoard(boards[0], initial_position);
18084       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18085       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18086       DisplayBothClocks();
18087       DrawPosition(FALSE, boards[currentMove]);
18088     }
18089   }
18090 }
18091
18092 static char cseq[12] = "\\   ";
18093
18094 Boolean
18095 set_cont_sequence (char *new_seq)
18096 {
18097     int len;
18098     Boolean ret;
18099
18100     // handle bad attempts to set the sequence
18101         if (!new_seq)
18102                 return 0; // acceptable error - no debug
18103
18104     len = strlen(new_seq);
18105     ret = (len > 0) && (len < sizeof(cseq));
18106     if (ret)
18107       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18108     else if (appData.debugMode)
18109       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18110     return ret;
18111 }
18112
18113 /*
18114     reformat a source message so words don't cross the width boundary.  internal
18115     newlines are not removed.  returns the wrapped size (no null character unless
18116     included in source message).  If dest is NULL, only calculate the size required
18117     for the dest buffer.  lp argument indicats line position upon entry, and it's
18118     passed back upon exit.
18119 */
18120 int
18121 wrap (char *dest, char *src, int count, int width, int *lp)
18122 {
18123     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18124
18125     cseq_len = strlen(cseq);
18126     old_line = line = *lp;
18127     ansi = len = clen = 0;
18128
18129     for (i=0; i < count; i++)
18130     {
18131         if (src[i] == '\033')
18132             ansi = 1;
18133
18134         // if we hit the width, back up
18135         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18136         {
18137             // store i & len in case the word is too long
18138             old_i = i, old_len = len;
18139
18140             // find the end of the last word
18141             while (i && src[i] != ' ' && src[i] != '\n')
18142             {
18143                 i--;
18144                 len--;
18145             }
18146
18147             // word too long?  restore i & len before splitting it
18148             if ((old_i-i+clen) >= width)
18149             {
18150                 i = old_i;
18151                 len = old_len;
18152             }
18153
18154             // extra space?
18155             if (i && src[i-1] == ' ')
18156                 len--;
18157
18158             if (src[i] != ' ' && src[i] != '\n')
18159             {
18160                 i--;
18161                 if (len)
18162                     len--;
18163             }
18164
18165             // now append the newline and continuation sequence
18166             if (dest)
18167                 dest[len] = '\n';
18168             len++;
18169             if (dest)
18170                 strncpy(dest+len, cseq, cseq_len);
18171             len += cseq_len;
18172             line = cseq_len;
18173             clen = cseq_len;
18174             continue;
18175         }
18176
18177         if (dest)
18178             dest[len] = src[i];
18179         len++;
18180         if (!ansi)
18181             line++;
18182         if (src[i] == '\n')
18183             line = 0;
18184         if (src[i] == 'm')
18185             ansi = 0;
18186     }
18187     if (dest && appData.debugMode)
18188     {
18189         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18190             count, width, line, len, *lp);
18191         show_bytes(debugFP, src, count);
18192         fprintf(debugFP, "\ndest: ");
18193         show_bytes(debugFP, dest, len);
18194         fprintf(debugFP, "\n");
18195     }
18196     *lp = dest ? line : old_line;
18197
18198     return len;
18199 }
18200
18201 // [HGM] vari: routines for shelving variations
18202 Boolean modeRestore = FALSE;
18203
18204 void
18205 PushInner (int firstMove, int lastMove)
18206 {
18207         int i, j, nrMoves = lastMove - firstMove;
18208
18209         // push current tail of game on stack
18210         savedResult[storedGames] = gameInfo.result;
18211         savedDetails[storedGames] = gameInfo.resultDetails;
18212         gameInfo.resultDetails = NULL;
18213         savedFirst[storedGames] = firstMove;
18214         savedLast [storedGames] = lastMove;
18215         savedFramePtr[storedGames] = framePtr;
18216         framePtr -= nrMoves; // reserve space for the boards
18217         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18218             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18219             for(j=0; j<MOVE_LEN; j++)
18220                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18221             for(j=0; j<2*MOVE_LEN; j++)
18222                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18223             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18224             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18225             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18226             pvInfoList[firstMove+i-1].depth = 0;
18227             commentList[framePtr+i] = commentList[firstMove+i];
18228             commentList[firstMove+i] = NULL;
18229         }
18230
18231         storedGames++;
18232         forwardMostMove = firstMove; // truncate game so we can start variation
18233 }
18234
18235 void
18236 PushTail (int firstMove, int lastMove)
18237 {
18238         if(appData.icsActive) { // only in local mode
18239                 forwardMostMove = currentMove; // mimic old ICS behavior
18240                 return;
18241         }
18242         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18243
18244         PushInner(firstMove, lastMove);
18245         if(storedGames == 1) GreyRevert(FALSE);
18246         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18247 }
18248
18249 void
18250 PopInner (Boolean annotate)
18251 {
18252         int i, j, nrMoves;
18253         char buf[8000], moveBuf[20];
18254
18255         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18256         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18257         nrMoves = savedLast[storedGames] - currentMove;
18258         if(annotate) {
18259                 int cnt = 10;
18260                 if(!WhiteOnMove(currentMove))
18261                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18262                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18263                 for(i=currentMove; i<forwardMostMove; i++) {
18264                         if(WhiteOnMove(i))
18265                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18266                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18267                         strcat(buf, moveBuf);
18268                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18269                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18270                 }
18271                 strcat(buf, ")");
18272         }
18273         for(i=1; i<=nrMoves; i++) { // copy last variation back
18274             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18275             for(j=0; j<MOVE_LEN; j++)
18276                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18277             for(j=0; j<2*MOVE_LEN; j++)
18278                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18279             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18280             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18281             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18282             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18283             commentList[currentMove+i] = commentList[framePtr+i];
18284             commentList[framePtr+i] = NULL;
18285         }
18286         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18287         framePtr = savedFramePtr[storedGames];
18288         gameInfo.result = savedResult[storedGames];
18289         if(gameInfo.resultDetails != NULL) {
18290             free(gameInfo.resultDetails);
18291       }
18292         gameInfo.resultDetails = savedDetails[storedGames];
18293         forwardMostMove = currentMove + nrMoves;
18294 }
18295
18296 Boolean
18297 PopTail (Boolean annotate)
18298 {
18299         if(appData.icsActive) return FALSE; // only in local mode
18300         if(!storedGames) return FALSE; // sanity
18301         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18302
18303         PopInner(annotate);
18304         if(currentMove < forwardMostMove) ForwardEvent(); else
18305         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18306
18307         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18308         return TRUE;
18309 }
18310
18311 void
18312 CleanupTail ()
18313 {       // remove all shelved variations
18314         int i;
18315         for(i=0; i<storedGames; i++) {
18316             if(savedDetails[i])
18317                 free(savedDetails[i]);
18318             savedDetails[i] = NULL;
18319         }
18320         for(i=framePtr; i<MAX_MOVES; i++) {
18321                 if(commentList[i]) free(commentList[i]);
18322                 commentList[i] = NULL;
18323         }
18324         framePtr = MAX_MOVES-1;
18325         storedGames = 0;
18326 }
18327
18328 void
18329 LoadVariation (int index, char *text)
18330 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18331         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18332         int level = 0, move;
18333
18334         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18335         // first find outermost bracketing variation
18336         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18337             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18338                 if(*p == '{') wait = '}'; else
18339                 if(*p == '[') wait = ']'; else
18340                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18341                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18342             }
18343             if(*p == wait) wait = NULLCHAR; // closing ]} found
18344             p++;
18345         }
18346         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18347         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18348         end[1] = NULLCHAR; // clip off comment beyond variation
18349         ToNrEvent(currentMove-1);
18350         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18351         // kludge: use ParsePV() to append variation to game
18352         move = currentMove;
18353         ParsePV(start, TRUE, TRUE);
18354         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18355         ClearPremoveHighlights();
18356         CommentPopDown();
18357         ToNrEvent(currentMove+1);
18358 }
18359
18360 void
18361 LoadTheme ()
18362 {
18363     char *p, *q, buf[MSG_SIZ];
18364     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18365         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18366         ParseArgsFromString(buf);
18367         ActivateTheme(TRUE); // also redo colors
18368         return;
18369     }
18370     p = nickName;
18371     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18372     {
18373         int len;
18374         q = appData.themeNames;
18375         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18376       if(appData.useBitmaps) {
18377         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18378                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18379                 appData.liteBackTextureMode,
18380                 appData.darkBackTextureMode );
18381       } else {
18382         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18383                 Col2Text(2),   // lightSquareColor
18384                 Col2Text(3) ); // darkSquareColor
18385       }
18386       if(appData.useBorder) {
18387         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18388                 appData.border);
18389       } else {
18390         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18391       }
18392       if(appData.useFont) {
18393         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18394                 appData.renderPiecesWithFont,
18395                 appData.fontToPieceTable,
18396                 Col2Text(9),    // appData.fontBackColorWhite
18397                 Col2Text(10) ); // appData.fontForeColorBlack
18398       } else {
18399         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18400                 appData.pieceDirectory);
18401         if(!appData.pieceDirectory[0])
18402           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18403                 Col2Text(0),   // whitePieceColor
18404                 Col2Text(1) ); // blackPieceColor
18405       }
18406       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18407                 Col2Text(4),   // highlightSquareColor
18408                 Col2Text(5) ); // premoveHighlightColor
18409         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18410         if(insert != q) insert[-1] = NULLCHAR;
18411         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18412         if(q)   free(q);
18413     }
18414     ActivateTheme(FALSE);
18415 }