Implement check-count field in FENs
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * Enhancements Copyright 2005 Alessandro Scotti
12  *
13  * The following terms apply to Digital Equipment Corporation's copyright
14  * interest in XBoard:
15  * ------------------------------------------------------------------------
16  * All Rights Reserved
17  *
18  * Permission to use, copy, modify, and distribute this software and its
19  * documentation for any purpose and without fee is hereby granted,
20  * provided that the above copyright notice appear in all copies and that
21  * both that copyright notice and this permission notice appear in
22  * supporting documentation, and that the name of Digital not be
23  * used in advertising or publicity pertaining to distribution of the
24  * software without specific, written prior permission.
25  *
26  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
27  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
28  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
29  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
30  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
31  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32  * SOFTWARE.
33  * ------------------------------------------------------------------------
34  *
35  * The following terms apply to the enhanced version of XBoard
36  * distributed by the Free Software Foundation:
37  * ------------------------------------------------------------------------
38  *
39  * GNU XBoard is free software: you can redistribute it and/or modify
40  * it under the terms of the GNU General Public License as published by
41  * the Free Software Foundation, either version 3 of the License, or (at
42  * your option) any later version.
43  *
44  * GNU XBoard is distributed in the hope that it will be useful, but
45  * WITHOUT ANY WARRANTY; without even the implied warranty of
46  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
47  * General Public License for more details.
48  *
49  * You should have received a copy of the GNU General Public License
50  * along with this program. If not, see http://www.gnu.org/licenses/.  *
51  *
52  *------------------------------------------------------------------------
53  ** See the file ChangeLog for a revision history.  */
54
55 /* [AS] Also useful here for debugging */
56 #ifdef WIN32
57 #include <windows.h>
58
59     int flock(int f, int code);
60 #   define LOCK_EX 2
61 #   define SLASH '\\'
62
63 #   ifdef ARC_64BIT
64 #       define EGBB_NAME "egbbdll64.dll"
65 #   else
66 #       define EGBB_NAME "egbbdll.dll"
67 #   endif
68
69 #else
70
71 #   include <sys/file.h>
72 #   define SLASH '/'
73
74 #   include <dlfcn.h>
75 #   ifdef ARC_64BIT
76 #       define EGBB_NAME "egbbso64.so"
77 #   else
78 #       define EGBB_NAME "egbbso.so"
79 #   endif
80     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
81 #   define CDECL
82 #   define HMODULE void *
83 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
84 #   define GetProcAddress dlsym
85
86 #endif
87
88 #include "config.h"
89
90 #include <assert.h>
91 #include <stdio.h>
92 #include <ctype.h>
93 #include <errno.h>
94 #include <sys/types.h>
95 #include <sys/stat.h>
96 #include <math.h>
97 #include <ctype.h>
98
99 #if STDC_HEADERS
100 # include <stdlib.h>
101 # include <string.h>
102 # include <stdarg.h>
103 #else /* not STDC_HEADERS */
104 # if HAVE_STRING_H
105 #  include <string.h>
106 # else /* not HAVE_STRING_H */
107 #  include <strings.h>
108 # endif /* not HAVE_STRING_H */
109 #endif /* not STDC_HEADERS */
110
111 #if HAVE_SYS_FCNTL_H
112 # include <sys/fcntl.h>
113 #else /* not HAVE_SYS_FCNTL_H */
114 # if HAVE_FCNTL_H
115 #  include <fcntl.h>
116 # endif /* HAVE_FCNTL_H */
117 #endif /* not HAVE_SYS_FCNTL_H */
118
119 #if TIME_WITH_SYS_TIME
120 # include <sys/time.h>
121 # include <time.h>
122 #else
123 # if HAVE_SYS_TIME_H
124 #  include <sys/time.h>
125 # else
126 #  include <time.h>
127 # endif
128 #endif
129
130 #if defined(_amigados) && !defined(__GNUC__)
131 struct timezone {
132     int tz_minuteswest;
133     int tz_dsttime;
134 };
135 extern int gettimeofday(struct timeval *, struct timezone *);
136 #endif
137
138 #if HAVE_UNISTD_H
139 # include <unistd.h>
140 #endif
141
142 #include "common.h"
143 #include "frontend.h"
144 #include "backend.h"
145 #include "parser.h"
146 #include "moves.h"
147 #if ZIPPY
148 # include "zippy.h"
149 #endif
150 #include "backendz.h"
151 #include "evalgraph.h"
152 #include "engineoutput.h"
153 #include "gettext.h"
154
155 #ifdef ENABLE_NLS
156 # define _(s) gettext (s)
157 # define N_(s) gettext_noop (s)
158 # define T_(s) gettext(s)
159 #else
160 # ifdef WIN32
161 #   define _(s) T_(s)
162 #   define N_(s) s
163 # else
164 #   define _(s) (s)
165 #   define N_(s) s
166 #   define T_(s) s
167 # endif
168 #endif
169
170
171 int establish P((void));
172 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
173                          char *buf, int count, int error));
174 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
175                       char *buf, int count, int error));
176 void SendToICS P((char *s));
177 void SendToICSDelayed P((char *s, long msdelay));
178 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
179 void HandleMachineMove P((char *message, ChessProgramState *cps));
180 int AutoPlayOneMove P((void));
181 int LoadGameOneMove P((ChessMove readAhead));
182 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
183 int LoadPositionFromFile P((char *filename, int n, char *title));
184 int SavePositionToFile P((char *filename));
185 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
186 void ShowMove P((int fromX, int fromY, int toX, int toY));
187 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
188                    /*char*/int promoChar));
189 void BackwardInner P((int target));
190 void ForwardInner P((int target));
191 int Adjudicate P((ChessProgramState *cps));
192 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
193 void EditPositionDone P((Boolean fakeRights));
194 void PrintOpponents P((FILE *fp));
195 void PrintPosition P((FILE *fp, int move));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264 int deadRanks;
265
266 extern int tinyLayout, smallLayout;
267 ChessProgramStats programStats;
268 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
269 int endPV = -1;
270 static int exiting = 0; /* [HGM] moved to top */
271 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
272 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
273 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
274 int partnerHighlight[2];
275 Boolean partnerBoardValid = 0;
276 char partnerStatus[MSG_SIZ];
277 Boolean partnerUp;
278 Boolean originalFlip;
279 Boolean twoBoards = 0;
280 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
281 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
282 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
283 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
284 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
285 int opponentKibitzes;
286 int lastSavedGame; /* [HGM] save: ID of game */
287 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
288 extern int chatCount;
289 int chattingPartner;
290 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
291 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
292 char lastMsg[MSG_SIZ];
293 char lastTalker[MSG_SIZ];
294 ChessSquare pieceSweep = EmptySquare;
295 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
296 int promoDefaultAltered;
297 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
298 static int initPing = -1;
299 int border;       /* [HGM] width of board rim, needed to size seek graph  */
300 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
301 int solvingTime, totalTime;
302
303 /* States for ics_getting_history */
304 #define H_FALSE 0
305 #define H_REQUESTED 1
306 #define H_GOT_REQ_HEADER 2
307 #define H_GOT_UNREQ_HEADER 3
308 #define H_GETTING_MOVES 4
309 #define H_GOT_UNWANTED_HEADER 5
310
311 /* whosays values for GameEnds */
312 #define GE_ICS 0
313 #define GE_ENGINE 1
314 #define GE_PLAYER 2
315 #define GE_FILE 3
316 #define GE_XBOARD 4
317 #define GE_ENGINE1 5
318 #define GE_ENGINE2 6
319
320 /* Maximum number of games in a cmail message */
321 #define CMAIL_MAX_GAMES 20
322
323 /* Different types of move when calling RegisterMove */
324 #define CMAIL_MOVE   0
325 #define CMAIL_RESIGN 1
326 #define CMAIL_DRAW   2
327 #define CMAIL_ACCEPT 3
328
329 /* Different types of result to remember for each game */
330 #define CMAIL_NOT_RESULT 0
331 #define CMAIL_OLD_RESULT 1
332 #define CMAIL_NEW_RESULT 2
333
334 /* Telnet protocol constants */
335 #define TN_WILL 0373
336 #define TN_WONT 0374
337 #define TN_DO   0375
338 #define TN_DONT 0376
339 #define TN_IAC  0377
340 #define TN_ECHO 0001
341 #define TN_SGA  0003
342 #define TN_PORT 23
343
344 char*
345 safeStrCpy (char *dst, const char *src, size_t count)
346 { // [HGM] made safe
347   int i;
348   assert( dst != NULL );
349   assert( src != NULL );
350   assert( count > 0 );
351
352   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
353   if(  i == count && dst[count-1] != NULLCHAR)
354     {
355       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
356       if(appData.debugMode)
357         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
358     }
359
360   return dst;
361 }
362
363 /* Some compiler can't cast u64 to double
364  * This function do the job for us:
365
366  * We use the highest bit for cast, this only
367  * works if the highest bit is not
368  * in use (This should not happen)
369  *
370  * We used this for all compiler
371  */
372 double
373 u64ToDouble (u64 value)
374 {
375   double r;
376   u64 tmp = value & u64Const(0x7fffffffffffffff);
377   r = (double)(s64)tmp;
378   if (value & u64Const(0x8000000000000000))
379        r +=  9.2233720368547758080e18; /* 2^63 */
380  return r;
381 }
382
383 /* Fake up flags for now, as we aren't keeping track of castling
384    availability yet. [HGM] Change of logic: the flag now only
385    indicates the type of castlings allowed by the rule of the game.
386    The actual rights themselves are maintained in the array
387    castlingRights, as part of the game history, and are not probed
388    by this function.
389  */
390 int
391 PosFlags (int index)
392 {
393   int flags = F_ALL_CASTLE_OK;
394   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
395   switch (gameInfo.variant) {
396   case VariantSuicide:
397     flags &= ~F_ALL_CASTLE_OK;
398   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
399     flags |= F_IGNORE_CHECK;
400   case VariantLosers:
401     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
402     break;
403   case VariantAtomic:
404     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
405     break;
406   case VariantKriegspiel:
407     flags |= F_KRIEGSPIEL_CAPTURE;
408     break;
409   case VariantCapaRandom:
410   case VariantFischeRandom:
411     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
412   case VariantNoCastle:
413   case VariantShatranj:
414   case VariantCourier:
415   case VariantMakruk:
416   case VariantASEAN:
417   case VariantGrand:
418     flags &= ~F_ALL_CASTLE_OK;
419     break;
420   case VariantChu:
421   case VariantChuChess:
422   case VariantLion:
423     flags |= F_NULL_MOVE;
424     break;
425   default:
426     break;
427   }
428   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
429   return flags;
430 }
431
432 FILE *gameFileFP, *debugFP, *serverFP;
433 char *currentDebugFile; // [HGM] debug split: to remember name
434
435 /*
436     [AS] Note: sometimes, the sscanf() function is used to parse the input
437     into a fixed-size buffer. Because of this, we must be prepared to
438     receive strings as long as the size of the input buffer, which is currently
439     set to 4K for Windows and 8K for the rest.
440     So, we must either allocate sufficiently large buffers here, or
441     reduce the size of the input buffer in the input reading part.
442 */
443
444 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
445 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
446 char thinkOutput1[MSG_SIZ*10];
447 char promoRestrict[MSG_SIZ];
448
449 ChessProgramState first, second, pairing;
450
451 /* premove variables */
452 int premoveToX = 0;
453 int premoveToY = 0;
454 int premoveFromX = 0;
455 int premoveFromY = 0;
456 int premovePromoChar = 0;
457 int gotPremove = 0;
458 Boolean alarmSounded;
459 /* end premove variables */
460
461 char *ics_prefix = "$";
462 enum ICS_TYPE ics_type = ICS_GENERIC;
463
464 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
465 int pauseExamForwardMostMove = 0;
466 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
467 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
468 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
469 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
470 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
471 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
472 int whiteFlag = FALSE, blackFlag = FALSE;
473 int userOfferedDraw = FALSE;
474 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
475 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
476 int cmailMoveType[CMAIL_MAX_GAMES];
477 long ics_clock_paused = 0;
478 ProcRef icsPR = NoProc, cmailPR = NoProc;
479 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
480 GameMode gameMode = BeginningOfGame;
481 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
482 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
483 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
484 int hiddenThinkOutputState = 0; /* [AS] */
485 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
486 int adjudicateLossPlies = 6;
487 char white_holding[64], black_holding[64];
488 TimeMark lastNodeCountTime;
489 long lastNodeCount=0;
490 int shiftKey, controlKey; // [HGM] set by mouse handler
491
492 int have_sent_ICS_logon = 0;
493 int movesPerSession;
494 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
495 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
496 Boolean adjustedClock;
497 long timeControl_2; /* [AS] Allow separate time controls */
498 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
499 long timeRemaining[2][MAX_MOVES];
500 int matchGame = 0, nextGame = 0, roundNr = 0;
501 Boolean waitingForGame = FALSE, startingEngine = FALSE;
502 TimeMark programStartTime, pauseStart;
503 char ics_handle[MSG_SIZ];
504 int have_set_title = 0;
505
506 /* animateTraining preserves the state of appData.animate
507  * when Training mode is activated. This allows the
508  * response to be animated when appData.animate == TRUE and
509  * appData.animateDragging == TRUE.
510  */
511 Boolean animateTraining;
512
513 GameInfo gameInfo;
514
515 AppData appData;
516
517 Board boards[MAX_MOVES];
518 /* [HGM] Following 7 needed for accurate legality tests: */
519 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
520 unsigned char initialRights[BOARD_FILES];
521 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
522 int   initialRulePlies, FENrulePlies;
523 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
524 int loadFlag = 0;
525 Boolean shuffleOpenings;
526 int mute; // mute all sounds
527
528 // [HGM] vari: next 12 to save and restore variations
529 #define MAX_VARIATIONS 10
530 int framePtr = MAX_MOVES-1; // points to free stack entry
531 int storedGames = 0;
532 int savedFirst[MAX_VARIATIONS];
533 int savedLast[MAX_VARIATIONS];
534 int savedFramePtr[MAX_VARIATIONS];
535 char *savedDetails[MAX_VARIATIONS];
536 ChessMove savedResult[MAX_VARIATIONS];
537
538 void PushTail P((int firstMove, int lastMove));
539 Boolean PopTail P((Boolean annotate));
540 void PushInner P((int firstMove, int lastMove));
541 void PopInner P((Boolean annotate));
542 void CleanupTail P((void));
543
544 ChessSquare  FIDEArray[2][BOARD_FILES] = {
545     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
548         BlackKing, BlackBishop, BlackKnight, BlackRook }
549 };
550
551 ChessSquare twoKingsArray[2][BOARD_FILES] = {
552     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
553         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
555         BlackKing, BlackKing, BlackKnight, BlackRook }
556 };
557
558 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
559     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
560         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
561     { BlackRook, BlackMan, BlackBishop, BlackQueen,
562         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
563 };
564
565 ChessSquare SpartanArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
567         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
568     { BlackAlfil, BlackDragon, BlackKing, BlackTower,
569         BlackTower, BlackKing, BlackAngel, BlackAlfil }
570 };
571
572 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
573     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
575     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
576         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
577 };
578
579 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
580     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
581         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
582     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
583         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
584 };
585
586 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
587     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
588         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
589     { BlackRook, BlackKnight, BlackMan, BlackFerz,
590         BlackKing, BlackMan, BlackKnight, BlackRook }
591 };
592
593 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
594     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
595         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
596     { BlackRook, BlackKnight, BlackMan, BlackFerz,
597         BlackKing, BlackMan, BlackKnight, BlackRook }
598 };
599
600 ChessSquare  lionArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
602         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackLion, BlackBishop, BlackQueen,
604         BlackKing, BlackBishop, BlackKnight, BlackRook }
605 };
606
607
608 #if (BOARD_FILES>=10)
609 ChessSquare ShogiArray[2][BOARD_FILES] = {
610     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
611         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
612     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
613         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
614 };
615
616 ChessSquare XiangqiArray[2][BOARD_FILES] = {
617     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
618         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
619     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
620         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
621 };
622
623 ChessSquare CapablancaArray[2][BOARD_FILES] = {
624     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
625         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
626     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
627         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
628 };
629
630 ChessSquare GreatArray[2][BOARD_FILES] = {
631     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
632         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
633     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
634         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
635 };
636
637 ChessSquare JanusArray[2][BOARD_FILES] = {
638     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
639         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
640     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
641         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
642 };
643
644 ChessSquare GrandArray[2][BOARD_FILES] = {
645     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
646         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
647     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
648         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
649 };
650
651 ChessSquare ChuChessArray[2][BOARD_FILES] = {
652     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
653         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
654     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
655         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
656 };
657
658 #ifdef GOTHIC
659 ChessSquare GothicArray[2][BOARD_FILES] = {
660     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
661         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
662     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
663         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
664 };
665 #else // !GOTHIC
666 #define GothicArray CapablancaArray
667 #endif // !GOTHIC
668
669 #ifdef FALCON
670 ChessSquare FalconArray[2][BOARD_FILES] = {
671     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
672         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
673     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
674         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
675 };
676 #else // !FALCON
677 #define FalconArray CapablancaArray
678 #endif // !FALCON
679
680 #else // !(BOARD_FILES>=10)
681 #define XiangqiPosition FIDEArray
682 #define CapablancaArray FIDEArray
683 #define GothicArray FIDEArray
684 #define GreatArray FIDEArray
685 #endif // !(BOARD_FILES>=10)
686
687 #if (BOARD_FILES>=12)
688 ChessSquare CourierArray[2][BOARD_FILES] = {
689     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
690         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
691     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
692         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
693 };
694 ChessSquare ChuArray[6][BOARD_FILES] = {
695     { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
696       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
697     { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
698       BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
699     { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
700       WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
701     { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
702       BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
703     { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
704       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
705     { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
706       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
707 };
708 #else // !(BOARD_FILES>=12)
709 #define CourierArray CapablancaArray
710 #define ChuArray CapablancaArray
711 #endif // !(BOARD_FILES>=12)
712
713
714 Board initialPosition;
715
716
717 /* Convert str to a rating. Checks for special cases of "----",
718
719    "++++", etc. Also strips ()'s */
720 int
721 string_to_rating (char *str)
722 {
723   while(*str && !isdigit(*str)) ++str;
724   if (!*str)
725     return 0;   /* One of the special "no rating" cases */
726   else
727     return atoi(str);
728 }
729
730 void
731 ClearProgramStats ()
732 {
733     /* Init programStats */
734     programStats.movelist[0] = 0;
735     programStats.depth = 0;
736     programStats.nr_moves = 0;
737     programStats.moves_left = 0;
738     programStats.nodes = 0;
739     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
740     programStats.score = 0;
741     programStats.got_only_move = 0;
742     programStats.got_fail = 0;
743     programStats.line_is_book = 0;
744 }
745
746 void
747 CommonEngineInit ()
748 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
749     if (appData.firstPlaysBlack) {
750         first.twoMachinesColor = "black\n";
751         second.twoMachinesColor = "white\n";
752     } else {
753         first.twoMachinesColor = "white\n";
754         second.twoMachinesColor = "black\n";
755     }
756
757     first.other = &second;
758     second.other = &first;
759
760     { float norm = 1;
761         if(appData.timeOddsMode) {
762             norm = appData.timeOdds[0];
763             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
764         }
765         first.timeOdds  = appData.timeOdds[0]/norm;
766         second.timeOdds = appData.timeOdds[1]/norm;
767     }
768
769     if(programVersion) free(programVersion);
770     if (appData.noChessProgram) {
771         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
772         sprintf(programVersion, "%s", PACKAGE_STRING);
773     } else {
774       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
775       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
776       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
777     }
778 }
779
780 void
781 UnloadEngine (ChessProgramState *cps)
782 {
783         /* Kill off first chess program */
784         if (cps->isr != NULL)
785           RemoveInputSource(cps->isr);
786         cps->isr = NULL;
787
788         if (cps->pr != NoProc) {
789             ExitAnalyzeMode();
790             DoSleep( appData.delayBeforeQuit );
791             SendToProgram("quit\n", cps);
792             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
793         }
794         cps->pr = NoProc;
795         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
796 }
797
798 void
799 ClearOptions (ChessProgramState *cps)
800 {
801     int i;
802     cps->nrOptions = cps->comboCnt = 0;
803     for(i=0; i<MAX_OPTIONS; i++) {
804         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
805         cps->option[i].textValue = 0;
806     }
807 }
808
809 char *engineNames[] = {
810   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
811      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
812 N_("first"),
813   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
814      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
815 N_("second")
816 };
817
818 void
819 InitEngine (ChessProgramState *cps, int n)
820 {   // [HGM] all engine initialiation put in a function that does one engine
821
822     ClearOptions(cps);
823
824     cps->which = engineNames[n];
825     cps->maybeThinking = FALSE;
826     cps->pr = NoProc;
827     cps->isr = NULL;
828     cps->sendTime = 2;
829     cps->sendDrawOffers = 1;
830
831     cps->program = appData.chessProgram[n];
832     cps->host = appData.host[n];
833     cps->dir = appData.directory[n];
834     cps->initString = appData.engInitString[n];
835     cps->computerString = appData.computerString[n];
836     cps->useSigint  = TRUE;
837     cps->useSigterm = TRUE;
838     cps->reuse = appData.reuse[n];
839     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
840     cps->useSetboard = FALSE;
841     cps->useSAN = FALSE;
842     cps->usePing = FALSE;
843     cps->lastPing = 0;
844     cps->lastPong = 0;
845     cps->usePlayother = FALSE;
846     cps->useColors = TRUE;
847     cps->useUsermove = FALSE;
848     cps->sendICS = FALSE;
849     cps->sendName = appData.icsActive;
850     cps->sdKludge = FALSE;
851     cps->stKludge = FALSE;
852     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
853     TidyProgramName(cps->program, cps->host, cps->tidy);
854     cps->matchWins = 0;
855     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
856     cps->analysisSupport = 2; /* detect */
857     cps->analyzing = FALSE;
858     cps->initDone = FALSE;
859     cps->reload = FALSE;
860     cps->pseudo = appData.pseudo[n];
861
862     /* New features added by Tord: */
863     cps->useFEN960 = FALSE;
864     cps->useOOCastle = TRUE;
865     /* End of new features added by Tord. */
866     cps->fenOverride  = appData.fenOverride[n];
867
868     /* [HGM] time odds: set factor for each machine */
869     cps->timeOdds  = appData.timeOdds[n];
870
871     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
872     cps->accumulateTC = appData.accumulateTC[n];
873     cps->maxNrOfSessions = 1;
874
875     /* [HGM] debug */
876     cps->debug = FALSE;
877
878     cps->drawDepth = appData.drawDepth[n];
879     cps->supportsNPS = UNKNOWN;
880     cps->memSize = FALSE;
881     cps->maxCores = FALSE;
882     ASSIGN(cps->egtFormats, "");
883
884     /* [HGM] options */
885     cps->optionSettings  = appData.engOptions[n];
886
887     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
888     cps->isUCI = appData.isUCI[n]; /* [AS] */
889     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
890     cps->highlight = 0;
891
892     if (appData.protocolVersion[n] > PROTOVER
893         || appData.protocolVersion[n] < 1)
894       {
895         char buf[MSG_SIZ];
896         int len;
897
898         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
899                        appData.protocolVersion[n]);
900         if( (len >= MSG_SIZ) && appData.debugMode )
901           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
902
903         DisplayFatalError(buf, 0, 2);
904       }
905     else
906       {
907         cps->protocolVersion = appData.protocolVersion[n];
908       }
909
910     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
911     ParseFeatures(appData.featureDefaults, cps);
912 }
913
914 ChessProgramState *savCps;
915
916 GameMode oldMode;
917
918 void
919 LoadEngine ()
920 {
921     int i;
922     if(WaitForEngine(savCps, LoadEngine)) return;
923     CommonEngineInit(); // recalculate time odds
924     if(gameInfo.variant != StringToVariant(appData.variant)) {
925         // we changed variant when loading the engine; this forces us to reset
926         Reset(TRUE, savCps != &first);
927         oldMode = BeginningOfGame; // to prevent restoring old mode
928     }
929     InitChessProgram(savCps, FALSE);
930     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
931     DisplayMessage("", "");
932     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
933     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
934     ThawUI();
935     SetGNUMode();
936     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
937 }
938
939 void
940 ReplaceEngine (ChessProgramState *cps, int n)
941 {
942     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
943     keepInfo = 1;
944     if(oldMode != BeginningOfGame) EditGameEvent();
945     keepInfo = 0;
946     UnloadEngine(cps);
947     appData.noChessProgram = FALSE;
948     appData.clockMode = TRUE;
949     InitEngine(cps, n);
950     UpdateLogos(TRUE);
951     if(n) return; // only startup first engine immediately; second can wait
952     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
953     LoadEngine();
954 }
955
956 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
957 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
958
959 static char resetOptions[] =
960         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
961         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
962         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
963         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
964
965 void
966 FloatToFront(char **list, char *engineLine)
967 {
968     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
969     int i=0;
970     if(appData.recentEngines <= 0) return;
971     TidyProgramName(engineLine, "localhost", tidy+1);
972     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
973     strncpy(buf+1, *list, MSG_SIZ-50);
974     if(p = strstr(buf, tidy)) { // tidy name appears in list
975         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
976         while(*p++ = *++q); // squeeze out
977     }
978     strcat(tidy, buf+1); // put list behind tidy name
979     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
980     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
981     ASSIGN(*list, tidy+1);
982 }
983
984 char *insert, *wbOptions, *currentEngine[2]; // point in ChessProgramNames were we should insert new engine
985
986 void
987 Load (ChessProgramState *cps, int i)
988 {
989     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
990     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
991         ASSIGN(currentEngine[i], engineLine);
992         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
993         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
994         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
995         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
996         appData.firstProtocolVersion = PROTOVER;
997         ParseArgsFromString(buf);
998         SwapEngines(i);
999         ReplaceEngine(cps, i);
1000         FloatToFront(&appData.recentEngineList, engineLine);
1001         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1002         return;
1003     }
1004     p = engineName;
1005     while(q = strchr(p, SLASH)) p = q+1;
1006     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1007     if(engineDir[0] != NULLCHAR) {
1008         ASSIGN(appData.directory[i], engineDir); p = engineName;
1009     } else if(p != engineName) { // derive directory from engine path, when not given
1010         p[-1] = 0;
1011         ASSIGN(appData.directory[i], engineName);
1012         p[-1] = SLASH;
1013         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1014     } else { ASSIGN(appData.directory[i], "."); }
1015     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1016     if(params[0]) {
1017         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1018         snprintf(command, MSG_SIZ, "%s %s", p, params);
1019         p = command;
1020     }
1021     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1022     ASSIGN(appData.chessProgram[i], p);
1023     appData.isUCI[i] = isUCI;
1024     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1025     appData.hasOwnBookUCI[i] = hasBook;
1026     if(!nickName[0]) useNick = FALSE;
1027     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1028     if(addToList) {
1029         int len;
1030         char quote;
1031         q = firstChessProgramNames;
1032         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1033         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1034         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s",
1035                         quote, p, quote, appData.directory[i],
1036                         useNick ? " -fn \"" : "",
1037                         useNick ? nickName : "",
1038                         useNick ? "\"" : "",
1039                         v1 ? " -firstProtocolVersion 1" : "",
1040                         hasBook ? "" : " -fNoOwnBookUCI",
1041                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1042                         storeVariant ? " -variant " : "",
1043                         storeVariant ? VariantName(gameInfo.variant) : "");
1044         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " %s", wbOptions);
1045         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 2);
1046         if(insert != q) insert[-1] = NULLCHAR;
1047         snprintf(firstChessProgramNames, len, "%s\n%s\n%s", q, buf, insert);
1048         if(q)   free(q);
1049         FloatToFront(&appData.recentEngineList, buf);
1050         ASSIGN(currentEngine[i], buf);
1051     }
1052     ReplaceEngine(cps, i);
1053 }
1054
1055 void
1056 InitTimeControls ()
1057 {
1058     int matched, min, sec;
1059     /*
1060      * Parse timeControl resource
1061      */
1062     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1063                           appData.movesPerSession)) {
1064         char buf[MSG_SIZ];
1065         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1066         DisplayFatalError(buf, 0, 2);
1067     }
1068
1069     /*
1070      * Parse searchTime resource
1071      */
1072     if (*appData.searchTime != NULLCHAR) {
1073         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1074         if (matched == 1) {
1075             searchTime = min * 60;
1076         } else if (matched == 2) {
1077             searchTime = min * 60 + sec;
1078         } else {
1079             char buf[MSG_SIZ];
1080             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1081             DisplayFatalError(buf, 0, 2);
1082         }
1083     }
1084 }
1085
1086 void
1087 InitBackEnd1 ()
1088 {
1089
1090     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1091     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1092
1093     GetTimeMark(&programStartTime);
1094     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1095     appData.seedBase = random() + (random()<<15);
1096     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1097
1098     ClearProgramStats();
1099     programStats.ok_to_send = 1;
1100     programStats.seen_stat = 0;
1101
1102     /*
1103      * Initialize game list
1104      */
1105     ListNew(&gameList);
1106
1107
1108     /*
1109      * Internet chess server status
1110      */
1111     if (appData.icsActive) {
1112         appData.matchMode = FALSE;
1113         appData.matchGames = 0;
1114 #if ZIPPY
1115         appData.noChessProgram = !appData.zippyPlay;
1116 #else
1117         appData.zippyPlay = FALSE;
1118         appData.zippyTalk = FALSE;
1119         appData.noChessProgram = TRUE;
1120 #endif
1121         if (*appData.icsHelper != NULLCHAR) {
1122             appData.useTelnet = TRUE;
1123             appData.telnetProgram = appData.icsHelper;
1124         }
1125     } else {
1126         appData.zippyTalk = appData.zippyPlay = FALSE;
1127     }
1128
1129     /* [AS] Initialize pv info list [HGM] and game state */
1130     {
1131         int i, j;
1132
1133         for( i=0; i<=framePtr; i++ ) {
1134             pvInfoList[i].depth = -1;
1135             boards[i][EP_STATUS] = EP_NONE;
1136             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1137         }
1138     }
1139
1140     InitTimeControls();
1141
1142     /* [AS] Adjudication threshold */
1143     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1144
1145     InitEngine(&first, 0);
1146     InitEngine(&second, 1);
1147     CommonEngineInit();
1148
1149     pairing.which = "pairing"; // pairing engine
1150     pairing.pr = NoProc;
1151     pairing.isr = NULL;
1152     pairing.program = appData.pairingEngine;
1153     pairing.host = "localhost";
1154     pairing.dir = ".";
1155
1156     if (appData.icsActive) {
1157         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1158     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1159         appData.clockMode = FALSE;
1160         first.sendTime = second.sendTime = 0;
1161     }
1162
1163 #if ZIPPY
1164     /* Override some settings from environment variables, for backward
1165        compatibility.  Unfortunately it's not feasible to have the env
1166        vars just set defaults, at least in xboard.  Ugh.
1167     */
1168     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1169       ZippyInit();
1170     }
1171 #endif
1172
1173     if (!appData.icsActive) {
1174       char buf[MSG_SIZ];
1175       int len;
1176
1177       /* Check for variants that are supported only in ICS mode,
1178          or not at all.  Some that are accepted here nevertheless
1179          have bugs; see comments below.
1180       */
1181       VariantClass variant = StringToVariant(appData.variant);
1182       switch (variant) {
1183       case VariantBughouse:     /* need four players and two boards */
1184       case VariantKriegspiel:   /* need to hide pieces and move details */
1185         /* case VariantFischeRandom: (Fabien: moved below) */
1186         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1187         if( (len >= MSG_SIZ) && appData.debugMode )
1188           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1189
1190         DisplayFatalError(buf, 0, 2);
1191         return;
1192
1193       case VariantUnknown:
1194       case VariantLoadable:
1195       case Variant29:
1196       case Variant30:
1197       case Variant31:
1198       case Variant32:
1199       case Variant33:
1200       case Variant34:
1201       case Variant35:
1202       case Variant36:
1203       default:
1204         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1205         if( (len >= MSG_SIZ) && appData.debugMode )
1206           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1207
1208         DisplayFatalError(buf, 0, 2);
1209         return;
1210
1211       case VariantNormal:     /* definitely works! */
1212         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1213           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1214           return;
1215         }
1216       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1217       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1218       case VariantGothic:     /* [HGM] should work */
1219       case VariantCapablanca: /* [HGM] should work */
1220       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1221       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1222       case VariantChu:        /* [HGM] experimental */
1223       case VariantKnightmate: /* [HGM] should work */
1224       case VariantCylinder:   /* [HGM] untested */
1225       case VariantFalcon:     /* [HGM] untested */
1226       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1227                                  offboard interposition not understood */
1228       case VariantWildCastle: /* pieces not automatically shuffled */
1229       case VariantNoCastle:   /* pieces not automatically shuffled */
1230       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1231       case VariantLosers:     /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantSuicide:    /* should work except for win condition,
1234                                  and doesn't know captures are mandatory */
1235       case VariantGiveaway:   /* should work except for win condition,
1236                                  and doesn't know captures are mandatory */
1237       case VariantTwoKings:   /* should work */
1238       case VariantAtomic:     /* should work except for win condition */
1239       case Variant3Check:     /* should work except for win condition */
1240       case VariantShatranj:   /* should work except for all win conditions */
1241       case VariantMakruk:     /* should work except for draw countdown */
1242       case VariantASEAN :     /* should work except for draw countdown */
1243       case VariantBerolina:   /* might work if TestLegality is off */
1244       case VariantCapaRandom: /* should work */
1245       case VariantJanus:      /* should work */
1246       case VariantSuper:      /* experimental */
1247       case VariantGreat:      /* experimental, requires legality testing to be off */
1248       case VariantSChess:     /* S-Chess, should work */
1249       case VariantGrand:      /* should work */
1250       case VariantSpartan:    /* should work */
1251       case VariantLion:       /* should work */
1252       case VariantChuChess:   /* should work */
1253         break;
1254       }
1255     }
1256
1257 }
1258
1259 int
1260 NextIntegerFromString (char ** str, long * value)
1261 {
1262     int result = -1;
1263     char * s = *str;
1264
1265     while( *s == ' ' || *s == '\t' ) {
1266         s++;
1267     }
1268
1269     *value = 0;
1270
1271     if( *s >= '0' && *s <= '9' ) {
1272         while( *s >= '0' && *s <= '9' ) {
1273             *value = *value * 10 + (*s - '0');
1274             s++;
1275         }
1276
1277         result = 0;
1278     }
1279
1280     *str = s;
1281
1282     return result;
1283 }
1284
1285 int
1286 NextTimeControlFromString (char ** str, long * value)
1287 {
1288     long temp;
1289     int result = NextIntegerFromString( str, &temp );
1290
1291     if( result == 0 ) {
1292         *value = temp * 60; /* Minutes */
1293         if( **str == ':' ) {
1294             (*str)++;
1295             result = NextIntegerFromString( str, &temp );
1296             *value += temp; /* Seconds */
1297         }
1298     }
1299
1300     return result;
1301 }
1302
1303 int
1304 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1305 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1306     int result = -1, type = 0; long temp, temp2;
1307
1308     if(**str != ':') return -1; // old params remain in force!
1309     (*str)++;
1310     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1311     if( NextIntegerFromString( str, &temp ) ) return -1;
1312     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1313
1314     if(**str != '/') {
1315         /* time only: incremental or sudden-death time control */
1316         if(**str == '+') { /* increment follows; read it */
1317             (*str)++;
1318             if(**str == '!') type = *(*str)++; // Bronstein TC
1319             if(result = NextIntegerFromString( str, &temp2)) return -1;
1320             *inc = temp2 * 1000;
1321             if(**str == '.') { // read fraction of increment
1322                 char *start = ++(*str);
1323                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1324                 temp2 *= 1000;
1325                 while(start++ < *str) temp2 /= 10;
1326                 *inc += temp2;
1327             }
1328         } else *inc = 0;
1329         *moves = 0; *tc = temp * 1000; *incType = type;
1330         return 0;
1331     }
1332
1333     (*str)++; /* classical time control */
1334     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1335
1336     if(result == 0) {
1337         *moves = temp;
1338         *tc    = temp2 * 1000;
1339         *inc   = 0;
1340         *incType = type;
1341     }
1342     return result;
1343 }
1344
1345 int
1346 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1347 {   /* [HGM] get time to add from the multi-session time-control string */
1348     int incType, moves=1; /* kludge to force reading of first session */
1349     long time, increment;
1350     char *s = tcString;
1351
1352     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1353     do {
1354         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1355         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1356         if(movenr == -1) return time;    /* last move before new session     */
1357         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1358         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1359         if(!moves) return increment;     /* current session is incremental   */
1360         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1361     } while(movenr >= -1);               /* try again for next session       */
1362
1363     return 0; // no new time quota on this move
1364 }
1365
1366 int
1367 ParseTimeControl (char *tc, float ti, int mps)
1368 {
1369   long tc1;
1370   long tc2;
1371   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1372   int min, sec=0;
1373
1374   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1375   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1376       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1377   if(ti > 0) {
1378
1379     if(mps)
1380       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1381     else
1382       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1383   } else {
1384     if(mps)
1385       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1386     else
1387       snprintf(buf, MSG_SIZ, ":%s", mytc);
1388   }
1389   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1390
1391   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1392     return FALSE;
1393   }
1394
1395   if( *tc == '/' ) {
1396     /* Parse second time control */
1397     tc++;
1398
1399     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1400       return FALSE;
1401     }
1402
1403     if( tc2 == 0 ) {
1404       return FALSE;
1405     }
1406
1407     timeControl_2 = tc2 * 1000;
1408   }
1409   else {
1410     timeControl_2 = 0;
1411   }
1412
1413   if( tc1 == 0 ) {
1414     return FALSE;
1415   }
1416
1417   timeControl = tc1 * 1000;
1418
1419   if (ti >= 0) {
1420     timeIncrement = ti * 1000;  /* convert to ms */
1421     movesPerSession = 0;
1422   } else {
1423     timeIncrement = 0;
1424     movesPerSession = mps;
1425   }
1426   return TRUE;
1427 }
1428
1429 void
1430 InitBackEnd2 ()
1431 {
1432     if (appData.debugMode) {
1433 #    ifdef __GIT_VERSION
1434       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1435 #    else
1436       fprintf(debugFP, "Version: %s\n", programVersion);
1437 #    endif
1438     }
1439     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1440
1441     set_cont_sequence(appData.wrapContSeq);
1442     if (appData.matchGames > 0) {
1443         appData.matchMode = TRUE;
1444     } else if (appData.matchMode) {
1445         appData.matchGames = 1;
1446     }
1447     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1448         appData.matchGames = appData.sameColorGames;
1449     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1450         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1451         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1452     }
1453     Reset(TRUE, FALSE);
1454     if (appData.noChessProgram || first.protocolVersion == 1) {
1455       InitBackEnd3();
1456     } else {
1457       /* kludge: allow timeout for initial "feature" commands */
1458       FreezeUI();
1459       DisplayMessage("", _("Starting chess program"));
1460       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1461     }
1462 }
1463
1464 int
1465 CalculateIndex (int index, int gameNr)
1466 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1467     int res;
1468     if(index > 0) return index; // fixed nmber
1469     if(index == 0) return 1;
1470     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1471     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1472     return res;
1473 }
1474
1475 int
1476 LoadGameOrPosition (int gameNr)
1477 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1478     if (*appData.loadGameFile != NULLCHAR) {
1479         if (!LoadGameFromFile(appData.loadGameFile,
1480                 CalculateIndex(appData.loadGameIndex, gameNr),
1481                               appData.loadGameFile, FALSE)) {
1482             DisplayFatalError(_("Bad game file"), 0, 1);
1483             return 0;
1484         }
1485     } else if (*appData.loadPositionFile != NULLCHAR) {
1486         if (!LoadPositionFromFile(appData.loadPositionFile,
1487                 CalculateIndex(appData.loadPositionIndex, gameNr),
1488                                   appData.loadPositionFile)) {
1489             DisplayFatalError(_("Bad position file"), 0, 1);
1490             return 0;
1491         }
1492     }
1493     return 1;
1494 }
1495
1496 void
1497 ReserveGame (int gameNr, char resChar)
1498 {
1499     FILE *tf = fopen(appData.tourneyFile, "r+");
1500     char *p, *q, c, buf[MSG_SIZ];
1501     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1502     safeStrCpy(buf, lastMsg, MSG_SIZ);
1503     DisplayMessage(_("Pick new game"), "");
1504     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1505     ParseArgsFromFile(tf);
1506     p = q = appData.results;
1507     if(appData.debugMode) {
1508       char *r = appData.participants;
1509       fprintf(debugFP, "results = '%s'\n", p);
1510       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1511       fprintf(debugFP, "\n");
1512     }
1513     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1514     nextGame = q - p;
1515     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1516     safeStrCpy(q, p, strlen(p) + 2);
1517     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1518     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1519     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1520         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1521         q[nextGame] = '*';
1522     }
1523     fseek(tf, -(strlen(p)+4), SEEK_END);
1524     c = fgetc(tf);
1525     if(c != '"') // depending on DOS or Unix line endings we can be one off
1526          fseek(tf, -(strlen(p)+2), SEEK_END);
1527     else fseek(tf, -(strlen(p)+3), SEEK_END);
1528     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1529     DisplayMessage(buf, "");
1530     free(p); appData.results = q;
1531     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1532        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1533       int round = appData.defaultMatchGames * appData.tourneyType;
1534       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1535          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1536         UnloadEngine(&first);  // next game belongs to other pairing;
1537         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1538     }
1539     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1540 }
1541
1542 void
1543 MatchEvent (int mode)
1544 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1545         int dummy;
1546         if(matchMode) { // already in match mode: switch it off
1547             abortMatch = TRUE;
1548             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1549             return;
1550         }
1551 //      if(gameMode != BeginningOfGame) {
1552 //          DisplayError(_("You can only start a match from the initial position."), 0);
1553 //          return;
1554 //      }
1555         abortMatch = FALSE;
1556         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1557         /* Set up machine vs. machine match */
1558         nextGame = 0;
1559         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1560         if(appData.tourneyFile[0]) {
1561             ReserveGame(-1, 0);
1562             if(nextGame > appData.matchGames) {
1563                 char buf[MSG_SIZ];
1564                 if(strchr(appData.results, '*') == NULL) {
1565                     FILE *f;
1566                     appData.tourneyCycles++;
1567                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1568                         fclose(f);
1569                         NextTourneyGame(-1, &dummy);
1570                         ReserveGame(-1, 0);
1571                         if(nextGame <= appData.matchGames) {
1572                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1573                             matchMode = mode;
1574                             ScheduleDelayedEvent(NextMatchGame, 10000);
1575                             return;
1576                         }
1577                     }
1578                 }
1579                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1580                 DisplayError(buf, 0);
1581                 appData.tourneyFile[0] = 0;
1582                 return;
1583             }
1584         } else
1585         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1586             DisplayFatalError(_("Can't have a match with no chess programs"),
1587                               0, 2);
1588             return;
1589         }
1590         matchMode = mode;
1591         matchGame = roundNr = 1;
1592         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1593         NextMatchGame();
1594 }
1595
1596 void
1597 InitBackEnd3 P((void))
1598 {
1599     GameMode initialMode;
1600     char buf[MSG_SIZ];
1601     int err, len;
1602
1603     ParseFeatures(appData.features[0], &first);
1604     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1605        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1606         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1607        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1608        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1609         char c, *q = first.variants, *p = strchr(q, ',');
1610         if(p) *p = NULLCHAR;
1611         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1612             int w, h, s;
1613             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1614                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1615             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1616             Reset(TRUE, FALSE);         // and re-initialize
1617         }
1618         if(p) *p = ',';
1619     }
1620
1621     InitChessProgram(&first, startedFromSetupPosition);
1622
1623     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1624         free(programVersion);
1625         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1626         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1627         FloatToFront(&appData.recentEngineList, currentEngine[0] ? currentEngine[0] : appData.firstChessProgram);
1628     }
1629
1630     if (appData.icsActive) {
1631 #ifdef WIN32
1632         /* [DM] Make a console window if needed [HGM] merged ifs */
1633         ConsoleCreate();
1634 #endif
1635         err = establish();
1636         if (err != 0)
1637           {
1638             if (*appData.icsCommPort != NULLCHAR)
1639               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1640                              appData.icsCommPort);
1641             else
1642               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1643                         appData.icsHost, appData.icsPort);
1644
1645             if( (len >= MSG_SIZ) && appData.debugMode )
1646               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1647
1648             DisplayFatalError(buf, err, 1);
1649             return;
1650         }
1651         SetICSMode();
1652         telnetISR =
1653           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1654         fromUserISR =
1655           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1656         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1657             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1658     } else if (appData.noChessProgram) {
1659         SetNCPMode();
1660     } else {
1661         SetGNUMode();
1662     }
1663
1664     if (*appData.cmailGameName != NULLCHAR) {
1665         SetCmailMode();
1666         OpenLoopback(&cmailPR);
1667         cmailISR =
1668           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1669     }
1670
1671     ThawUI();
1672     DisplayMessage("", "");
1673     if (StrCaseCmp(appData.initialMode, "") == 0) {
1674       initialMode = BeginningOfGame;
1675       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1676         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1677         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1678         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1679         ModeHighlight();
1680       }
1681     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1682       initialMode = TwoMachinesPlay;
1683     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1684       initialMode = AnalyzeFile;
1685     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1686       initialMode = AnalyzeMode;
1687     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1688       initialMode = MachinePlaysWhite;
1689     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1690       initialMode = MachinePlaysBlack;
1691     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1692       initialMode = EditGame;
1693     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1694       initialMode = EditPosition;
1695     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1696       initialMode = Training;
1697     } else {
1698       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1699       if( (len >= MSG_SIZ) && appData.debugMode )
1700         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1701
1702       DisplayFatalError(buf, 0, 2);
1703       return;
1704     }
1705
1706     if (appData.matchMode) {
1707         if(appData.tourneyFile[0]) { // start tourney from command line
1708             FILE *f;
1709             if(f = fopen(appData.tourneyFile, "r")) {
1710                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1711                 fclose(f);
1712                 appData.clockMode = TRUE;
1713                 SetGNUMode();
1714             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1715         }
1716         MatchEvent(TRUE);
1717     } else if (*appData.cmailGameName != NULLCHAR) {
1718         /* Set up cmail mode */
1719         ReloadCmailMsgEvent(TRUE);
1720     } else {
1721         /* Set up other modes */
1722         if (initialMode == AnalyzeFile) {
1723           if (*appData.loadGameFile == NULLCHAR) {
1724             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1725             return;
1726           }
1727         }
1728         if (*appData.loadGameFile != NULLCHAR) {
1729             (void) LoadGameFromFile(appData.loadGameFile,
1730                                     appData.loadGameIndex,
1731                                     appData.loadGameFile, TRUE);
1732         } else if (*appData.loadPositionFile != NULLCHAR) {
1733             (void) LoadPositionFromFile(appData.loadPositionFile,
1734                                         appData.loadPositionIndex,
1735                                         appData.loadPositionFile);
1736             /* [HGM] try to make self-starting even after FEN load */
1737             /* to allow automatic setup of fairy variants with wtm */
1738             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1739                 gameMode = BeginningOfGame;
1740                 setboardSpoiledMachineBlack = 1;
1741             }
1742             /* [HGM] loadPos: make that every new game uses the setup */
1743             /* from file as long as we do not switch variant          */
1744             if(!blackPlaysFirst) {
1745                 startedFromPositionFile = TRUE;
1746                 CopyBoard(filePosition, boards[0]);
1747                 CopyBoard(initialPosition, boards[0]);
1748             }
1749         } else if(*appData.fen != NULLCHAR) {
1750             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1751                 startedFromPositionFile = TRUE;
1752                 Reset(TRUE, TRUE);
1753             }
1754         }
1755         if (initialMode == AnalyzeMode) {
1756           if (appData.noChessProgram) {
1757             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1758             return;
1759           }
1760           if (appData.icsActive) {
1761             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1762             return;
1763           }
1764           AnalyzeModeEvent();
1765         } else if (initialMode == AnalyzeFile) {
1766           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1767           ShowThinkingEvent();
1768           AnalyzeFileEvent();
1769           AnalysisPeriodicEvent(1);
1770         } else if (initialMode == MachinePlaysWhite) {
1771           if (appData.noChessProgram) {
1772             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1773                               0, 2);
1774             return;
1775           }
1776           if (appData.icsActive) {
1777             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1778                               0, 2);
1779             return;
1780           }
1781           MachineWhiteEvent();
1782         } else if (initialMode == MachinePlaysBlack) {
1783           if (appData.noChessProgram) {
1784             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1785                               0, 2);
1786             return;
1787           }
1788           if (appData.icsActive) {
1789             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1790                               0, 2);
1791             return;
1792           }
1793           MachineBlackEvent();
1794         } else if (initialMode == TwoMachinesPlay) {
1795           if (appData.noChessProgram) {
1796             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1797                               0, 2);
1798             return;
1799           }
1800           if (appData.icsActive) {
1801             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1802                               0, 2);
1803             return;
1804           }
1805           TwoMachinesEvent();
1806         } else if (initialMode == EditGame) {
1807           EditGameEvent();
1808         } else if (initialMode == EditPosition) {
1809           EditPositionEvent();
1810         } else if (initialMode == Training) {
1811           if (*appData.loadGameFile == NULLCHAR) {
1812             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1813             return;
1814           }
1815           TrainingEvent();
1816         }
1817     }
1818 }
1819
1820 void
1821 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1822 {
1823     DisplayBook(current+1);
1824
1825     MoveHistorySet( movelist, first, last, current, pvInfoList );
1826
1827     EvalGraphSet( first, last, current, pvInfoList );
1828
1829     MakeEngineOutputTitle();
1830 }
1831
1832 /*
1833  * Establish will establish a contact to a remote host.port.
1834  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1835  *  used to talk to the host.
1836  * Returns 0 if okay, error code if not.
1837  */
1838 int
1839 establish ()
1840 {
1841     char buf[MSG_SIZ];
1842
1843     if (*appData.icsCommPort != NULLCHAR) {
1844         /* Talk to the host through a serial comm port */
1845         return OpenCommPort(appData.icsCommPort, &icsPR);
1846
1847     } else if (*appData.gateway != NULLCHAR) {
1848         if (*appData.remoteShell == NULLCHAR) {
1849             /* Use the rcmd protocol to run telnet program on a gateway host */
1850             snprintf(buf, sizeof(buf), "%s %s %s",
1851                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1852             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1853
1854         } else {
1855             /* Use the rsh program to run telnet program on a gateway host */
1856             if (*appData.remoteUser == NULLCHAR) {
1857                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1858                         appData.gateway, appData.telnetProgram,
1859                         appData.icsHost, appData.icsPort);
1860             } else {
1861                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1862                         appData.remoteShell, appData.gateway,
1863                         appData.remoteUser, appData.telnetProgram,
1864                         appData.icsHost, appData.icsPort);
1865             }
1866             return StartChildProcess(buf, "", &icsPR);
1867
1868         }
1869     } else if (appData.useTelnet) {
1870         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1871
1872     } else {
1873         /* TCP socket interface differs somewhat between
1874            Unix and NT; handle details in the front end.
1875            */
1876         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1877     }
1878 }
1879
1880 void
1881 EscapeExpand (char *p, char *q)
1882 {       // [HGM] initstring: routine to shape up string arguments
1883         while(*p++ = *q++) if(p[-1] == '\\')
1884             switch(*q++) {
1885                 case 'n': p[-1] = '\n'; break;
1886                 case 'r': p[-1] = '\r'; break;
1887                 case 't': p[-1] = '\t'; break;
1888                 case '\\': p[-1] = '\\'; break;
1889                 case 0: *p = 0; return;
1890                 default: p[-1] = q[-1]; break;
1891             }
1892 }
1893
1894 void
1895 show_bytes (FILE *fp, char *buf, int count)
1896 {
1897     while (count--) {
1898         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1899             fprintf(fp, "\\%03o", *buf & 0xff);
1900         } else {
1901             putc(*buf, fp);
1902         }
1903         buf++;
1904     }
1905     fflush(fp);
1906 }
1907
1908 /* Returns an errno value */
1909 int
1910 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1911 {
1912     char buf[8192], *p, *q, *buflim;
1913     int left, newcount, outcount;
1914
1915     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1916         *appData.gateway != NULLCHAR) {
1917         if (appData.debugMode) {
1918             fprintf(debugFP, ">ICS: ");
1919             show_bytes(debugFP, message, count);
1920             fprintf(debugFP, "\n");
1921         }
1922         return OutputToProcess(pr, message, count, outError);
1923     }
1924
1925     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1926     p = message;
1927     q = buf;
1928     left = count;
1929     newcount = 0;
1930     while (left) {
1931         if (q >= buflim) {
1932             if (appData.debugMode) {
1933                 fprintf(debugFP, ">ICS: ");
1934                 show_bytes(debugFP, buf, newcount);
1935                 fprintf(debugFP, "\n");
1936             }
1937             outcount = OutputToProcess(pr, buf, newcount, outError);
1938             if (outcount < newcount) return -1; /* to be sure */
1939             q = buf;
1940             newcount = 0;
1941         }
1942         if (*p == '\n') {
1943             *q++ = '\r';
1944             newcount++;
1945         } else if (((unsigned char) *p) == TN_IAC) {
1946             *q++ = (char) TN_IAC;
1947             newcount ++;
1948         }
1949         *q++ = *p++;
1950         newcount++;
1951         left--;
1952     }
1953     if (appData.debugMode) {
1954         fprintf(debugFP, ">ICS: ");
1955         show_bytes(debugFP, buf, newcount);
1956         fprintf(debugFP, "\n");
1957     }
1958     outcount = OutputToProcess(pr, buf, newcount, outError);
1959     if (outcount < newcount) return -1; /* to be sure */
1960     return count;
1961 }
1962
1963 void
1964 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1965 {
1966     int outError, outCount;
1967     static int gotEof = 0;
1968     static FILE *ini;
1969
1970     /* Pass data read from player on to ICS */
1971     if (count > 0) {
1972         gotEof = 0;
1973         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1974         if (outCount < count) {
1975             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1976         }
1977         if(have_sent_ICS_logon == 2) {
1978           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1979             fprintf(ini, "%s", message);
1980             have_sent_ICS_logon = 3;
1981           } else
1982             have_sent_ICS_logon = 1;
1983         } else if(have_sent_ICS_logon == 3) {
1984             fprintf(ini, "%s", message);
1985             fclose(ini);
1986           have_sent_ICS_logon = 1;
1987         }
1988     } else if (count < 0) {
1989         RemoveInputSource(isr);
1990         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1991     } else if (gotEof++ > 0) {
1992         RemoveInputSource(isr);
1993         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1994     }
1995 }
1996
1997 void
1998 KeepAlive ()
1999 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
2000     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
2001     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2002     SendToICS("date\n");
2003     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2004 }
2005
2006 /* added routine for printf style output to ics */
2007 void
2008 ics_printf (char *format, ...)
2009 {
2010     char buffer[MSG_SIZ];
2011     va_list args;
2012
2013     va_start(args, format);
2014     vsnprintf(buffer, sizeof(buffer), format, args);
2015     buffer[sizeof(buffer)-1] = '\0';
2016     SendToICS(buffer);
2017     va_end(args);
2018 }
2019
2020 void
2021 SendToICS (char *s)
2022 {
2023     int count, outCount, outError;
2024
2025     if (icsPR == NoProc) return;
2026
2027     count = strlen(s);
2028     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2029     if (outCount < count) {
2030         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2031     }
2032 }
2033
2034 /* This is used for sending logon scripts to the ICS. Sending
2035    without a delay causes problems when using timestamp on ICC
2036    (at least on my machine). */
2037 void
2038 SendToICSDelayed (char *s, long msdelay)
2039 {
2040     int count, outCount, outError;
2041
2042     if (icsPR == NoProc) return;
2043
2044     count = strlen(s);
2045     if (appData.debugMode) {
2046         fprintf(debugFP, ">ICS: ");
2047         show_bytes(debugFP, s, count);
2048         fprintf(debugFP, "\n");
2049     }
2050     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2051                                       msdelay);
2052     if (outCount < count) {
2053         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2054     }
2055 }
2056
2057
2058 /* Remove all highlighting escape sequences in s
2059    Also deletes any suffix starting with '('
2060    */
2061 char *
2062 StripHighlightAndTitle (char *s)
2063 {
2064     static char retbuf[MSG_SIZ];
2065     char *p = retbuf;
2066
2067     while (*s != NULLCHAR) {
2068         while (*s == '\033') {
2069             while (*s != NULLCHAR && !isalpha(*s)) s++;
2070             if (*s != NULLCHAR) s++;
2071         }
2072         while (*s != NULLCHAR && *s != '\033') {
2073             if (*s == '(' || *s == '[') {
2074                 *p = NULLCHAR;
2075                 return retbuf;
2076             }
2077             *p++ = *s++;
2078         }
2079     }
2080     *p = NULLCHAR;
2081     return retbuf;
2082 }
2083
2084 /* Remove all highlighting escape sequences in s */
2085 char *
2086 StripHighlight (char *s)
2087 {
2088     static char retbuf[MSG_SIZ];
2089     char *p = retbuf;
2090
2091     while (*s != NULLCHAR) {
2092         while (*s == '\033') {
2093             while (*s != NULLCHAR && !isalpha(*s)) s++;
2094             if (*s != NULLCHAR) s++;
2095         }
2096         while (*s != NULLCHAR && *s != '\033') {
2097             *p++ = *s++;
2098         }
2099     }
2100     *p = NULLCHAR;
2101     return retbuf;
2102 }
2103
2104 char engineVariant[MSG_SIZ];
2105 char *variantNames[] = VARIANT_NAMES;
2106 char *
2107 VariantName (VariantClass v)
2108 {
2109     if(v == VariantUnknown || *engineVariant) return engineVariant;
2110     return variantNames[v];
2111 }
2112
2113
2114 /* Identify a variant from the strings the chess servers use or the
2115    PGN Variant tag names we use. */
2116 VariantClass
2117 StringToVariant (char *e)
2118 {
2119     char *p;
2120     int wnum = -1;
2121     VariantClass v = VariantNormal;
2122     int i, found = FALSE;
2123     char buf[MSG_SIZ], c;
2124     int len;
2125
2126     if (!e) return v;
2127
2128     /* [HGM] skip over optional board-size prefixes */
2129     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2130         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2131         while( *e++ != '_');
2132     }
2133
2134     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2135         v = VariantNormal;
2136         found = TRUE;
2137     } else
2138     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2139       if (p = StrCaseStr(e, variantNames[i])) {
2140         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2141         v = (VariantClass) i;
2142         found = TRUE;
2143         break;
2144       }
2145     }
2146
2147     if (!found) {
2148       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2149           || StrCaseStr(e, "wild/fr")
2150           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2151         v = VariantFischeRandom;
2152       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2153                  (i = 1, p = StrCaseStr(e, "w"))) {
2154         p += i;
2155         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2156         if (isdigit(*p)) {
2157           wnum = atoi(p);
2158         } else {
2159           wnum = -1;
2160         }
2161         switch (wnum) {
2162         case 0: /* FICS only, actually */
2163         case 1:
2164           /* Castling legal even if K starts on d-file */
2165           v = VariantWildCastle;
2166           break;
2167         case 2:
2168         case 3:
2169         case 4:
2170           /* Castling illegal even if K & R happen to start in
2171              normal positions. */
2172           v = VariantNoCastle;
2173           break;
2174         case 5:
2175         case 7:
2176         case 8:
2177         case 10:
2178         case 11:
2179         case 12:
2180         case 13:
2181         case 14:
2182         case 15:
2183         case 18:
2184         case 19:
2185           /* Castling legal iff K & R start in normal positions */
2186           v = VariantNormal;
2187           break;
2188         case 6:
2189         case 20:
2190         case 21:
2191           /* Special wilds for position setup; unclear what to do here */
2192           v = VariantLoadable;
2193           break;
2194         case 9:
2195           /* Bizarre ICC game */
2196           v = VariantTwoKings;
2197           break;
2198         case 16:
2199           v = VariantKriegspiel;
2200           break;
2201         case 17:
2202           v = VariantLosers;
2203           break;
2204         case 22:
2205           v = VariantFischeRandom;
2206           break;
2207         case 23:
2208           v = VariantCrazyhouse;
2209           break;
2210         case 24:
2211           v = VariantBughouse;
2212           break;
2213         case 25:
2214           v = Variant3Check;
2215           break;
2216         case 26:
2217           /* Not quite the same as FICS suicide! */
2218           v = VariantGiveaway;
2219           break;
2220         case 27:
2221           v = VariantAtomic;
2222           break;
2223         case 28:
2224           v = VariantShatranj;
2225           break;
2226
2227         /* Temporary names for future ICC types.  The name *will* change in
2228            the next xboard/WinBoard release after ICC defines it. */
2229         case 29:
2230           v = Variant29;
2231           break;
2232         case 30:
2233           v = Variant30;
2234           break;
2235         case 31:
2236           v = Variant31;
2237           break;
2238         case 32:
2239           v = Variant32;
2240           break;
2241         case 33:
2242           v = Variant33;
2243           break;
2244         case 34:
2245           v = Variant34;
2246           break;
2247         case 35:
2248           v = Variant35;
2249           break;
2250         case 36:
2251           v = Variant36;
2252           break;
2253         case 37:
2254           v = VariantShogi;
2255           break;
2256         case 38:
2257           v = VariantXiangqi;
2258           break;
2259         case 39:
2260           v = VariantCourier;
2261           break;
2262         case 40:
2263           v = VariantGothic;
2264           break;
2265         case 41:
2266           v = VariantCapablanca;
2267           break;
2268         case 42:
2269           v = VariantKnightmate;
2270           break;
2271         case 43:
2272           v = VariantFairy;
2273           break;
2274         case 44:
2275           v = VariantCylinder;
2276           break;
2277         case 45:
2278           v = VariantFalcon;
2279           break;
2280         case 46:
2281           v = VariantCapaRandom;
2282           break;
2283         case 47:
2284           v = VariantBerolina;
2285           break;
2286         case 48:
2287           v = VariantJanus;
2288           break;
2289         case 49:
2290           v = VariantSuper;
2291           break;
2292         case 50:
2293           v = VariantGreat;
2294           break;
2295         case -1:
2296           /* Found "wild" or "w" in the string but no number;
2297              must assume it's normal chess. */
2298           v = VariantNormal;
2299           break;
2300         default:
2301           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2302           if( (len >= MSG_SIZ) && appData.debugMode )
2303             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2304
2305           DisplayError(buf, 0);
2306           v = VariantUnknown;
2307           break;
2308         }
2309       }
2310     }
2311     if (appData.debugMode) {
2312       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2313               e, wnum, VariantName(v));
2314     }
2315     return v;
2316 }
2317
2318 static int leftover_start = 0, leftover_len = 0;
2319 char star_match[STAR_MATCH_N][MSG_SIZ];
2320
2321 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2322    advance *index beyond it, and set leftover_start to the new value of
2323    *index; else return FALSE.  If pattern contains the character '*', it
2324    matches any sequence of characters not containing '\r', '\n', or the
2325    character following the '*' (if any), and the matched sequence(s) are
2326    copied into star_match.
2327    */
2328 int
2329 looking_at ( char *buf, int *index, char *pattern)
2330 {
2331     char *bufp = &buf[*index], *patternp = pattern;
2332     int star_count = 0;
2333     char *matchp = star_match[0];
2334
2335     for (;;) {
2336         if (*patternp == NULLCHAR) {
2337             *index = leftover_start = bufp - buf;
2338             *matchp = NULLCHAR;
2339             return TRUE;
2340         }
2341         if (*bufp == NULLCHAR) return FALSE;
2342         if (*patternp == '*') {
2343             if (*bufp == *(patternp + 1)) {
2344                 *matchp = NULLCHAR;
2345                 matchp = star_match[++star_count];
2346                 patternp += 2;
2347                 bufp++;
2348                 continue;
2349             } else if (*bufp == '\n' || *bufp == '\r') {
2350                 patternp++;
2351                 if (*patternp == NULLCHAR)
2352                   continue;
2353                 else
2354                   return FALSE;
2355             } else {
2356                 *matchp++ = *bufp++;
2357                 continue;
2358             }
2359         }
2360         if (*patternp != *bufp) return FALSE;
2361         patternp++;
2362         bufp++;
2363     }
2364 }
2365
2366 void
2367 SendToPlayer (char *data, int length)
2368 {
2369     int error, outCount;
2370     outCount = OutputToProcess(NoProc, data, length, &error);
2371     if (outCount < length) {
2372         DisplayFatalError(_("Error writing to display"), error, 1);
2373     }
2374 }
2375
2376 void
2377 PackHolding (char packed[], char *holding)
2378 {
2379     char *p = holding;
2380     char *q = packed;
2381     int runlength = 0;
2382     int curr = 9999;
2383     do {
2384         if (*p == curr) {
2385             runlength++;
2386         } else {
2387             switch (runlength) {
2388               case 0:
2389                 break;
2390               case 1:
2391                 *q++ = curr;
2392                 break;
2393               case 2:
2394                 *q++ = curr;
2395                 *q++ = curr;
2396                 break;
2397               default:
2398                 sprintf(q, "%d", runlength);
2399                 while (*q) q++;
2400                 *q++ = curr;
2401                 break;
2402             }
2403             runlength = 1;
2404             curr = *p;
2405         }
2406     } while (*p++);
2407     *q = NULLCHAR;
2408 }
2409
2410 /* Telnet protocol requests from the front end */
2411 void
2412 TelnetRequest (unsigned char ddww, unsigned char option)
2413 {
2414     unsigned char msg[3];
2415     int outCount, outError;
2416
2417     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2418
2419     if (appData.debugMode) {
2420         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2421         switch (ddww) {
2422           case TN_DO:
2423             ddwwStr = "DO";
2424             break;
2425           case TN_DONT:
2426             ddwwStr = "DONT";
2427             break;
2428           case TN_WILL:
2429             ddwwStr = "WILL";
2430             break;
2431           case TN_WONT:
2432             ddwwStr = "WONT";
2433             break;
2434           default:
2435             ddwwStr = buf1;
2436             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2437             break;
2438         }
2439         switch (option) {
2440           case TN_ECHO:
2441             optionStr = "ECHO";
2442             break;
2443           default:
2444             optionStr = buf2;
2445             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2446             break;
2447         }
2448         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2449     }
2450     msg[0] = TN_IAC;
2451     msg[1] = ddww;
2452     msg[2] = option;
2453     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2454     if (outCount < 3) {
2455         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2456     }
2457 }
2458
2459 void
2460 DoEcho ()
2461 {
2462     if (!appData.icsActive) return;
2463     TelnetRequest(TN_DO, TN_ECHO);
2464 }
2465
2466 void
2467 DontEcho ()
2468 {
2469     if (!appData.icsActive) return;
2470     TelnetRequest(TN_DONT, TN_ECHO);
2471 }
2472
2473 void
2474 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2475 {
2476     /* put the holdings sent to us by the server on the board holdings area */
2477     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2478     char p;
2479     ChessSquare piece;
2480
2481     if(gameInfo.holdingsWidth < 2)  return;
2482     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2483         return; // prevent overwriting by pre-board holdings
2484
2485     if( (int)lowestPiece >= BlackPawn ) {
2486         holdingsColumn = 0;
2487         countsColumn = 1;
2488         holdingsStartRow = BOARD_HEIGHT-1;
2489         direction = -1;
2490     } else {
2491         holdingsColumn = BOARD_WIDTH-1;
2492         countsColumn = BOARD_WIDTH-2;
2493         holdingsStartRow = 0;
2494         direction = 1;
2495     }
2496
2497     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2498         board[i][holdingsColumn] = EmptySquare;
2499         board[i][countsColumn]   = (ChessSquare) 0;
2500     }
2501     while( (p=*holdings++) != NULLCHAR ) {
2502         piece = CharToPiece( ToUpper(p) );
2503         if(piece == EmptySquare) continue;
2504         /*j = (int) piece - (int) WhitePawn;*/
2505         j = PieceToNumber(piece);
2506         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2507         if(j < 0) continue;               /* should not happen */
2508         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2509         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2510         board[holdingsStartRow+j*direction][countsColumn]++;
2511     }
2512 }
2513
2514
2515 void
2516 VariantSwitch (Board board, VariantClass newVariant)
2517 {
2518    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2519    static Board oldBoard;
2520
2521    startedFromPositionFile = FALSE;
2522    if(gameInfo.variant == newVariant) return;
2523
2524    /* [HGM] This routine is called each time an assignment is made to
2525     * gameInfo.variant during a game, to make sure the board sizes
2526     * are set to match the new variant. If that means adding or deleting
2527     * holdings, we shift the playing board accordingly
2528     * This kludge is needed because in ICS observe mode, we get boards
2529     * of an ongoing game without knowing the variant, and learn about the
2530     * latter only later. This can be because of the move list we requested,
2531     * in which case the game history is refilled from the beginning anyway,
2532     * but also when receiving holdings of a crazyhouse game. In the latter
2533     * case we want to add those holdings to the already received position.
2534     */
2535
2536
2537    if (appData.debugMode) {
2538      fprintf(debugFP, "Switch board from %s to %s\n",
2539              VariantName(gameInfo.variant), VariantName(newVariant));
2540      setbuf(debugFP, NULL);
2541    }
2542    shuffleOpenings = 0;       /* [HGM] shuffle */
2543    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2544    switch(newVariant)
2545      {
2546      case VariantShogi:
2547        newWidth = 9;  newHeight = 9;
2548        gameInfo.holdingsSize = 7;
2549      case VariantBughouse:
2550      case VariantCrazyhouse:
2551        newHoldingsWidth = 2; break;
2552      case VariantGreat:
2553        newWidth = 10;
2554      case VariantSuper:
2555        newHoldingsWidth = 2;
2556        gameInfo.holdingsSize = 8;
2557        break;
2558      case VariantGothic:
2559      case VariantCapablanca:
2560      case VariantCapaRandom:
2561        newWidth = 10;
2562      default:
2563        newHoldingsWidth = gameInfo.holdingsSize = 0;
2564      };
2565
2566    if(newWidth  != gameInfo.boardWidth  ||
2567       newHeight != gameInfo.boardHeight ||
2568       newHoldingsWidth != gameInfo.holdingsWidth ) {
2569
2570      /* shift position to new playing area, if needed */
2571      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2572        for(i=0; i<BOARD_HEIGHT; i++)
2573          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2574            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2575              board[i][j];
2576        for(i=0; i<newHeight; i++) {
2577          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2578          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2579        }
2580      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2581        for(i=0; i<BOARD_HEIGHT; i++)
2582          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2583            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2584              board[i][j];
2585      }
2586      board[HOLDINGS_SET] = 0;
2587      gameInfo.boardWidth  = newWidth;
2588      gameInfo.boardHeight = newHeight;
2589      gameInfo.holdingsWidth = newHoldingsWidth;
2590      gameInfo.variant = newVariant;
2591      InitDrawingSizes(-2, 0);
2592    } else gameInfo.variant = newVariant;
2593    CopyBoard(oldBoard, board);   // remember correctly formatted board
2594      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2595    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2596 }
2597
2598 static int loggedOn = FALSE;
2599
2600 /*-- Game start info cache: --*/
2601 int gs_gamenum;
2602 char gs_kind[MSG_SIZ];
2603 static char player1Name[128] = "";
2604 static char player2Name[128] = "";
2605 static char cont_seq[] = "\n\\   ";
2606 static int player1Rating = -1;
2607 static int player2Rating = -1;
2608 /*----------------------------*/
2609
2610 ColorClass curColor = ColorNormal;
2611 int suppressKibitz = 0;
2612
2613 // [HGM] seekgraph
2614 Boolean soughtPending = FALSE;
2615 Boolean seekGraphUp;
2616 #define MAX_SEEK_ADS 200
2617 #define SQUARE 0x80
2618 char *seekAdList[MAX_SEEK_ADS];
2619 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2620 float tcList[MAX_SEEK_ADS];
2621 char colorList[MAX_SEEK_ADS];
2622 int nrOfSeekAds = 0;
2623 int minRating = 1010, maxRating = 2800;
2624 int hMargin = 10, vMargin = 20, h, w;
2625 extern int squareSize, lineGap;
2626
2627 void
2628 PlotSeekAd (int i)
2629 {
2630         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2631         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2632         if(r < minRating+100 && r >=0 ) r = minRating+100;
2633         if(r > maxRating) r = maxRating;
2634         if(tc < 1.f) tc = 1.f;
2635         if(tc > 95.f) tc = 95.f;
2636         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2637         y = ((double)r - minRating)/(maxRating - minRating)
2638             * (h-vMargin-squareSize/8-1) + vMargin;
2639         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2640         if(strstr(seekAdList[i], " u ")) color = 1;
2641         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2642            !strstr(seekAdList[i], "bullet") &&
2643            !strstr(seekAdList[i], "blitz") &&
2644            !strstr(seekAdList[i], "standard") ) color = 2;
2645         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2646         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2647 }
2648
2649 void
2650 PlotSingleSeekAd (int i)
2651 {
2652         PlotSeekAd(i);
2653 }
2654
2655 void
2656 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2657 {
2658         char buf[MSG_SIZ], *ext = "";
2659         VariantClass v = StringToVariant(type);
2660         if(strstr(type, "wild")) {
2661             ext = type + 4; // append wild number
2662             if(v == VariantFischeRandom) type = "chess960"; else
2663             if(v == VariantLoadable) type = "setup"; else
2664             type = VariantName(v);
2665         }
2666         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2667         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2668             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2669             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2670             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2671             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2672             seekNrList[nrOfSeekAds] = nr;
2673             zList[nrOfSeekAds] = 0;
2674             seekAdList[nrOfSeekAds++] = StrSave(buf);
2675             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2676         }
2677 }
2678
2679 void
2680 EraseSeekDot (int i)
2681 {
2682     int x = xList[i], y = yList[i], d=squareSize/4, k;
2683     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2684     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2685     // now replot every dot that overlapped
2686     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2687         int xx = xList[k], yy = yList[k];
2688         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2689             DrawSeekDot(xx, yy, colorList[k]);
2690     }
2691 }
2692
2693 void
2694 RemoveSeekAd (int nr)
2695 {
2696         int i;
2697         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2698             EraseSeekDot(i);
2699             if(seekAdList[i]) free(seekAdList[i]);
2700             seekAdList[i] = seekAdList[--nrOfSeekAds];
2701             seekNrList[i] = seekNrList[nrOfSeekAds];
2702             ratingList[i] = ratingList[nrOfSeekAds];
2703             colorList[i]  = colorList[nrOfSeekAds];
2704             tcList[i] = tcList[nrOfSeekAds];
2705             xList[i]  = xList[nrOfSeekAds];
2706             yList[i]  = yList[nrOfSeekAds];
2707             zList[i]  = zList[nrOfSeekAds];
2708             seekAdList[nrOfSeekAds] = NULL;
2709             break;
2710         }
2711 }
2712
2713 Boolean
2714 MatchSoughtLine (char *line)
2715 {
2716     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2717     int nr, base, inc, u=0; char dummy;
2718
2719     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2720        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2721        (u=1) &&
2722        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2723         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2724         // match: compact and save the line
2725         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2726         return TRUE;
2727     }
2728     return FALSE;
2729 }
2730
2731 int
2732 DrawSeekGraph ()
2733 {
2734     int i;
2735     if(!seekGraphUp) return FALSE;
2736     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2737     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2738
2739     DrawSeekBackground(0, 0, w, h);
2740     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2741     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2742     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2743         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2744         yy = h-1-yy;
2745         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2746         if(i%500 == 0) {
2747             char buf[MSG_SIZ];
2748             snprintf(buf, MSG_SIZ, "%d", i);
2749             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2750         }
2751     }
2752     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2753     for(i=1; i<100; i+=(i<10?1:5)) {
2754         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2755         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2756         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2757             char buf[MSG_SIZ];
2758             snprintf(buf, MSG_SIZ, "%d", i);
2759             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2760         }
2761     }
2762     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2763     return TRUE;
2764 }
2765
2766 int
2767 SeekGraphClick (ClickType click, int x, int y, int moving)
2768 {
2769     static int lastDown = 0, displayed = 0, lastSecond;
2770     if(y < 0) return FALSE;
2771     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2772         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2773         if(!seekGraphUp) return FALSE;
2774         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2775         DrawPosition(TRUE, NULL);
2776         return TRUE;
2777     }
2778     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2779         if(click == Release || moving) return FALSE;
2780         nrOfSeekAds = 0;
2781         soughtPending = TRUE;
2782         SendToICS(ics_prefix);
2783         SendToICS("sought\n"); // should this be "sought all"?
2784     } else { // issue challenge based on clicked ad
2785         int dist = 10000; int i, closest = 0, second = 0;
2786         for(i=0; i<nrOfSeekAds; i++) {
2787             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2788             if(d < dist) { dist = d; closest = i; }
2789             second += (d - zList[i] < 120); // count in-range ads
2790             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2791         }
2792         if(dist < 120) {
2793             char buf[MSG_SIZ];
2794             second = (second > 1);
2795             if(displayed != closest || second != lastSecond) {
2796                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2797                 lastSecond = second; displayed = closest;
2798             }
2799             if(click == Press) {
2800                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2801                 lastDown = closest;
2802                 return TRUE;
2803             } // on press 'hit', only show info
2804             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2805             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2806             SendToICS(ics_prefix);
2807             SendToICS(buf);
2808             return TRUE; // let incoming board of started game pop down the graph
2809         } else if(click == Release) { // release 'miss' is ignored
2810             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2811             if(moving == 2) { // right up-click
2812                 nrOfSeekAds = 0; // refresh graph
2813                 soughtPending = TRUE;
2814                 SendToICS(ics_prefix);
2815                 SendToICS("sought\n"); // should this be "sought all"?
2816             }
2817             return TRUE;
2818         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2819         // press miss or release hit 'pop down' seek graph
2820         seekGraphUp = FALSE;
2821         DrawPosition(TRUE, NULL);
2822     }
2823     return TRUE;
2824 }
2825
2826 void
2827 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2828 {
2829 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2830 #define STARTED_NONE 0
2831 #define STARTED_MOVES 1
2832 #define STARTED_BOARD 2
2833 #define STARTED_OBSERVE 3
2834 #define STARTED_HOLDINGS 4
2835 #define STARTED_CHATTER 5
2836 #define STARTED_COMMENT 6
2837 #define STARTED_MOVES_NOHIDE 7
2838
2839     static int started = STARTED_NONE;
2840     static char parse[20000];
2841     static int parse_pos = 0;
2842     static char buf[BUF_SIZE + 1];
2843     static int firstTime = TRUE, intfSet = FALSE;
2844     static ColorClass prevColor = ColorNormal;
2845     static int savingComment = FALSE;
2846     static int cmatch = 0; // continuation sequence match
2847     char *bp;
2848     char str[MSG_SIZ];
2849     int i, oldi;
2850     int buf_len;
2851     int next_out;
2852     int tkind;
2853     int backup;    /* [DM] For zippy color lines */
2854     char *p;
2855     char talker[MSG_SIZ]; // [HGM] chat
2856     int channel, collective=0;
2857
2858     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2859
2860     if (appData.debugMode) {
2861       if (!error) {
2862         fprintf(debugFP, "<ICS: ");
2863         show_bytes(debugFP, data, count);
2864         fprintf(debugFP, "\n");
2865       }
2866     }
2867
2868     if (appData.debugMode) { int f = forwardMostMove;
2869         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2870                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2871                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2872     }
2873     if (count > 0) {
2874         /* If last read ended with a partial line that we couldn't parse,
2875            prepend it to the new read and try again. */
2876         if (leftover_len > 0) {
2877             for (i=0; i<leftover_len; i++)
2878               buf[i] = buf[leftover_start + i];
2879         }
2880
2881     /* copy new characters into the buffer */
2882     bp = buf + leftover_len;
2883     buf_len=leftover_len;
2884     for (i=0; i<count; i++)
2885     {
2886         // ignore these
2887         if (data[i] == '\r')
2888             continue;
2889
2890         // join lines split by ICS?
2891         if (!appData.noJoin)
2892         {
2893             /*
2894                 Joining just consists of finding matches against the
2895                 continuation sequence, and discarding that sequence
2896                 if found instead of copying it.  So, until a match
2897                 fails, there's nothing to do since it might be the
2898                 complete sequence, and thus, something we don't want
2899                 copied.
2900             */
2901             if (data[i] == cont_seq[cmatch])
2902             {
2903                 cmatch++;
2904                 if (cmatch == strlen(cont_seq))
2905                 {
2906                     cmatch = 0; // complete match.  just reset the counter
2907
2908                     /*
2909                         it's possible for the ICS to not include the space
2910                         at the end of the last word, making our [correct]
2911                         join operation fuse two separate words.  the server
2912                         does this when the space occurs at the width setting.
2913                     */
2914                     if (!buf_len || buf[buf_len-1] != ' ')
2915                     {
2916                         *bp++ = ' ';
2917                         buf_len++;
2918                     }
2919                 }
2920                 continue;
2921             }
2922             else if (cmatch)
2923             {
2924                 /*
2925                     match failed, so we have to copy what matched before
2926                     falling through and copying this character.  In reality,
2927                     this will only ever be just the newline character, but
2928                     it doesn't hurt to be precise.
2929                 */
2930                 strncpy(bp, cont_seq, cmatch);
2931                 bp += cmatch;
2932                 buf_len += cmatch;
2933                 cmatch = 0;
2934             }
2935         }
2936
2937         // copy this char
2938         *bp++ = data[i];
2939         buf_len++;
2940     }
2941
2942         buf[buf_len] = NULLCHAR;
2943 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2944         next_out = 0;
2945         leftover_start = 0;
2946
2947         i = 0;
2948         while (i < buf_len) {
2949             /* Deal with part of the TELNET option negotiation
2950                protocol.  We refuse to do anything beyond the
2951                defaults, except that we allow the WILL ECHO option,
2952                which ICS uses to turn off password echoing when we are
2953                directly connected to it.  We reject this option
2954                if localLineEditing mode is on (always on in xboard)
2955                and we are talking to port 23, which might be a real
2956                telnet server that will try to keep WILL ECHO on permanently.
2957              */
2958             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2959                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2960                 unsigned char option;
2961                 oldi = i;
2962                 switch ((unsigned char) buf[++i]) {
2963                   case TN_WILL:
2964                     if (appData.debugMode)
2965                       fprintf(debugFP, "\n<WILL ");
2966                     switch (option = (unsigned char) buf[++i]) {
2967                       case TN_ECHO:
2968                         if (appData.debugMode)
2969                           fprintf(debugFP, "ECHO ");
2970                         /* Reply only if this is a change, according
2971                            to the protocol rules. */
2972                         if (remoteEchoOption) break;
2973                         if (appData.localLineEditing &&
2974                             atoi(appData.icsPort) == TN_PORT) {
2975                             TelnetRequest(TN_DONT, TN_ECHO);
2976                         } else {
2977                             EchoOff();
2978                             TelnetRequest(TN_DO, TN_ECHO);
2979                             remoteEchoOption = TRUE;
2980                         }
2981                         break;
2982                       default:
2983                         if (appData.debugMode)
2984                           fprintf(debugFP, "%d ", option);
2985                         /* Whatever this is, we don't want it. */
2986                         TelnetRequest(TN_DONT, option);
2987                         break;
2988                     }
2989                     break;
2990                   case TN_WONT:
2991                     if (appData.debugMode)
2992                       fprintf(debugFP, "\n<WONT ");
2993                     switch (option = (unsigned char) buf[++i]) {
2994                       case TN_ECHO:
2995                         if (appData.debugMode)
2996                           fprintf(debugFP, "ECHO ");
2997                         /* Reply only if this is a change, according
2998                            to the protocol rules. */
2999                         if (!remoteEchoOption) break;
3000                         EchoOn();
3001                         TelnetRequest(TN_DONT, TN_ECHO);
3002                         remoteEchoOption = FALSE;
3003                         break;
3004                       default:
3005                         if (appData.debugMode)
3006                           fprintf(debugFP, "%d ", (unsigned char) option);
3007                         /* Whatever this is, it must already be turned
3008                            off, because we never agree to turn on
3009                            anything non-default, so according to the
3010                            protocol rules, we don't reply. */
3011                         break;
3012                     }
3013                     break;
3014                   case TN_DO:
3015                     if (appData.debugMode)
3016                       fprintf(debugFP, "\n<DO ");
3017                     switch (option = (unsigned char) buf[++i]) {
3018                       default:
3019                         /* Whatever this is, we refuse to do it. */
3020                         if (appData.debugMode)
3021                           fprintf(debugFP, "%d ", option);
3022                         TelnetRequest(TN_WONT, option);
3023                         break;
3024                     }
3025                     break;
3026                   case TN_DONT:
3027                     if (appData.debugMode)
3028                       fprintf(debugFP, "\n<DONT ");
3029                     switch (option = (unsigned char) buf[++i]) {
3030                       default:
3031                         if (appData.debugMode)
3032                           fprintf(debugFP, "%d ", option);
3033                         /* Whatever this is, we are already not doing
3034                            it, because we never agree to do anything
3035                            non-default, so according to the protocol
3036                            rules, we don't reply. */
3037                         break;
3038                     }
3039                     break;
3040                   case TN_IAC:
3041                     if (appData.debugMode)
3042                       fprintf(debugFP, "\n<IAC ");
3043                     /* Doubled IAC; pass it through */
3044                     i--;
3045                     break;
3046                   default:
3047                     if (appData.debugMode)
3048                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3049                     /* Drop all other telnet commands on the floor */
3050                     break;
3051                 }
3052                 if (oldi > next_out)
3053                   SendToPlayer(&buf[next_out], oldi - next_out);
3054                 if (++i > next_out)
3055                   next_out = i;
3056                 continue;
3057             }
3058
3059             /* OK, this at least will *usually* work */
3060             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3061                 loggedOn = TRUE;
3062             }
3063
3064             if (loggedOn && !intfSet) {
3065                 if (ics_type == ICS_ICC) {
3066                   snprintf(str, MSG_SIZ,
3067                           "/set-quietly interface %s\n/set-quietly style 12\n",
3068                           programVersion);
3069                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3070                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3071                 } else if (ics_type == ICS_CHESSNET) {
3072                   snprintf(str, MSG_SIZ, "/style 12\n");
3073                 } else {
3074                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3075                   strcat(str, programVersion);
3076                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3077                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3078                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3079 #ifdef WIN32
3080                   strcat(str, "$iset nohighlight 1\n");
3081 #endif
3082                   strcat(str, "$iset lock 1\n$style 12\n");
3083                 }
3084                 SendToICS(str);
3085                 NotifyFrontendLogin();
3086                 intfSet = TRUE;
3087             }
3088
3089             if (started == STARTED_COMMENT) {
3090                 /* Accumulate characters in comment */
3091                 parse[parse_pos++] = buf[i];
3092                 if (buf[i] == '\n') {
3093                     parse[parse_pos] = NULLCHAR;
3094                     if(chattingPartner>=0) {
3095                         char mess[MSG_SIZ];
3096                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3097                         OutputChatMessage(chattingPartner, mess);
3098                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3099                             int p;
3100                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3101                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3102                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3103                                 OutputChatMessage(p, mess);
3104                                 break;
3105                             }
3106                         }
3107                         chattingPartner = -1;
3108                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3109                         collective = 0;
3110                     } else
3111                     if(!suppressKibitz) // [HGM] kibitz
3112                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3113                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3114                         int nrDigit = 0, nrAlph = 0, j;
3115                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3116                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3117                         parse[parse_pos] = NULLCHAR;
3118                         // try to be smart: if it does not look like search info, it should go to
3119                         // ICS interaction window after all, not to engine-output window.
3120                         for(j=0; j<parse_pos; j++) { // count letters and digits
3121                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3122                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3123                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3124                         }
3125                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3126                             int depth=0; float score;
3127                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3128                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3129                                 pvInfoList[forwardMostMove-1].depth = depth;
3130                                 pvInfoList[forwardMostMove-1].score = 100*score;
3131                             }
3132                             OutputKibitz(suppressKibitz, parse);
3133                         } else {
3134                             char tmp[MSG_SIZ];
3135                             if(gameMode == IcsObserving) // restore original ICS messages
3136                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3137                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3138                             else
3139                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3140                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3141                             SendToPlayer(tmp, strlen(tmp));
3142                         }
3143                         next_out = i+1; // [HGM] suppress printing in ICS window
3144                     }
3145                     started = STARTED_NONE;
3146                 } else {
3147                     /* Don't match patterns against characters in comment */
3148                     i++;
3149                     continue;
3150                 }
3151             }
3152             if (started == STARTED_CHATTER) {
3153                 if (buf[i] != '\n') {
3154                     /* Don't match patterns against characters in chatter */
3155                     i++;
3156                     continue;
3157                 }
3158                 started = STARTED_NONE;
3159                 if(suppressKibitz) next_out = i+1;
3160             }
3161
3162             /* Kludge to deal with rcmd protocol */
3163             if (firstTime && looking_at(buf, &i, "\001*")) {
3164                 DisplayFatalError(&buf[1], 0, 1);
3165                 continue;
3166             } else {
3167                 firstTime = FALSE;
3168             }
3169
3170             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3171                 ics_type = ICS_ICC;
3172                 ics_prefix = "/";
3173                 if (appData.debugMode)
3174                   fprintf(debugFP, "ics_type %d\n", ics_type);
3175                 continue;
3176             }
3177             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3178                 ics_type = ICS_FICS;
3179                 ics_prefix = "$";
3180                 if (appData.debugMode)
3181                   fprintf(debugFP, "ics_type %d\n", ics_type);
3182                 continue;
3183             }
3184             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3185                 ics_type = ICS_CHESSNET;
3186                 ics_prefix = "/";
3187                 if (appData.debugMode)
3188                   fprintf(debugFP, "ics_type %d\n", ics_type);
3189                 continue;
3190             }
3191
3192             if (!loggedOn &&
3193                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3194                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3195                  looking_at(buf, &i, "will be \"*\""))) {
3196               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3197               continue;
3198             }
3199
3200             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3201               char buf[MSG_SIZ];
3202               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3203               DisplayIcsInteractionTitle(buf);
3204               have_set_title = TRUE;
3205             }
3206
3207             /* skip finger notes */
3208             if (started == STARTED_NONE &&
3209                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3210                  (buf[i] == '1' && buf[i+1] == '0')) &&
3211                 buf[i+2] == ':' && buf[i+3] == ' ') {
3212               started = STARTED_CHATTER;
3213               i += 3;
3214               continue;
3215             }
3216
3217             oldi = i;
3218             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3219             if(appData.seekGraph) {
3220                 if(soughtPending && MatchSoughtLine(buf+i)) {
3221                     i = strstr(buf+i, "rated") - buf;
3222                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3223                     next_out = leftover_start = i;
3224                     started = STARTED_CHATTER;
3225                     suppressKibitz = TRUE;
3226                     continue;
3227                 }
3228                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3229                         && looking_at(buf, &i, "* ads displayed")) {
3230                     soughtPending = FALSE;
3231                     seekGraphUp = TRUE;
3232                     DrawSeekGraph();
3233                     continue;
3234                 }
3235                 if(appData.autoRefresh) {
3236                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3237                         int s = (ics_type == ICS_ICC); // ICC format differs
3238                         if(seekGraphUp)
3239                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3240                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3241                         looking_at(buf, &i, "*% "); // eat prompt
3242                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3243                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3244                         next_out = i; // suppress
3245                         continue;
3246                     }
3247                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3248                         char *p = star_match[0];
3249                         while(*p) {
3250                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3251                             while(*p && *p++ != ' '); // next
3252                         }
3253                         looking_at(buf, &i, "*% "); // eat prompt
3254                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3255                         next_out = i;
3256                         continue;
3257                     }
3258                 }
3259             }
3260
3261             /* skip formula vars */
3262             if (started == STARTED_NONE &&
3263                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3264               started = STARTED_CHATTER;
3265               i += 3;
3266               continue;
3267             }
3268
3269             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3270             if (appData.autoKibitz && started == STARTED_NONE &&
3271                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3272                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3273                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3274                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3275                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3276                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3277                         suppressKibitz = TRUE;
3278                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3279                         next_out = i;
3280                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3281                                 && (gameMode == IcsPlayingWhite)) ||
3282                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3283                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3284                             started = STARTED_CHATTER; // own kibitz we simply discard
3285                         else {
3286                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3287                             parse_pos = 0; parse[0] = NULLCHAR;
3288                             savingComment = TRUE;
3289                             suppressKibitz = gameMode != IcsObserving ? 2 :
3290                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3291                         }
3292                         continue;
3293                 } else
3294                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3295                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3296                          && atoi(star_match[0])) {
3297                     // suppress the acknowledgements of our own autoKibitz
3298                     char *p;
3299                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3300                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3301                     SendToPlayer(star_match[0], strlen(star_match[0]));
3302                     if(looking_at(buf, &i, "*% ")) // eat prompt
3303                         suppressKibitz = FALSE;
3304                     next_out = i;
3305                     continue;
3306                 }
3307             } // [HGM] kibitz: end of patch
3308
3309             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3310
3311             // [HGM] chat: intercept tells by users for which we have an open chat window
3312             channel = -1;
3313             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3314                                            looking_at(buf, &i, "* whispers:") ||
3315                                            looking_at(buf, &i, "* kibitzes:") ||
3316                                            looking_at(buf, &i, "* shouts:") ||
3317                                            looking_at(buf, &i, "* c-shouts:") ||
3318                                            looking_at(buf, &i, "--> * ") ||
3319                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3320                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3321                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3322                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3323                 int p;
3324                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3325                 chattingPartner = -1; collective = 0;
3326
3327                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3328                 for(p=0; p<MAX_CHAT; p++) {
3329                     collective = 1;
3330                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3331                     talker[0] = '['; strcat(talker, "] ");
3332                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3333                     chattingPartner = p; break;
3334                     }
3335                 } else
3336                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3337                 for(p=0; p<MAX_CHAT; p++) {
3338                     collective = 1;
3339                     if(!strcmp("kibitzes", chatPartner[p])) {
3340                         talker[0] = '['; strcat(talker, "] ");
3341                         chattingPartner = p; break;
3342                     }
3343                 } else
3344                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3345                 for(p=0; p<MAX_CHAT; p++) {
3346                     collective = 1;
3347                     if(!strcmp("whispers", chatPartner[p])) {
3348                         talker[0] = '['; strcat(talker, "] ");
3349                         chattingPartner = p; break;
3350                     }
3351                 } else
3352                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3353                   if(buf[i-8] == '-' && buf[i-3] == 't')
3354                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3355                     collective = 1;
3356                     if(!strcmp("c-shouts", chatPartner[p])) {
3357                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3358                         chattingPartner = p; break;
3359                     }
3360                   }
3361                   if(chattingPartner < 0)
3362                   for(p=0; p<MAX_CHAT; p++) {
3363                     collective = 1;
3364                     if(!strcmp("shouts", chatPartner[p])) {
3365                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3366                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3367                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3368                         chattingPartner = p; break;
3369                     }
3370                   }
3371                 }
3372                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3373                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3374                     talker[0] = 0;
3375                     Colorize(ColorTell, FALSE);
3376                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3377                     collective |= 2;
3378                     chattingPartner = p; break;
3379                 }
3380                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3381                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3382                     started = STARTED_COMMENT;
3383                     parse_pos = 0; parse[0] = NULLCHAR;
3384                     savingComment = 3 + chattingPartner; // counts as TRUE
3385                     if(collective == 3) i = oldi; else {
3386                         suppressKibitz = TRUE;
3387                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3388                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3389                         continue;
3390                     }
3391                 }
3392             } // [HGM] chat: end of patch
3393
3394           backup = i;
3395             if (appData.zippyTalk || appData.zippyPlay) {
3396                 /* [DM] Backup address for color zippy lines */
3397 #if ZIPPY
3398                if (loggedOn == TRUE)
3399                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3400                           (appData.zippyPlay && ZippyMatch(buf, &backup)))
3401                        ;
3402 #endif
3403             } // [DM] 'else { ' deleted
3404                 if (
3405                     /* Regular tells and says */
3406                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3407                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3408                     looking_at(buf, &i, "* says: ") ||
3409                     /* Don't color "message" or "messages" output */
3410                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3411                     looking_at(buf, &i, "*. * at *:*: ") ||
3412                     looking_at(buf, &i, "--* (*:*): ") ||
3413                     /* Message notifications (same color as tells) */
3414                     looking_at(buf, &i, "* has left a message ") ||
3415                     looking_at(buf, &i, "* just sent you a message:\n") ||
3416                     /* Whispers and kibitzes */
3417                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3418                     looking_at(buf, &i, "* kibitzes: ") ||
3419                     /* Channel tells */
3420                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3421
3422                   if (tkind == 1 && strchr(star_match[0], ':')) {
3423                       /* Avoid "tells you:" spoofs in channels */
3424                      tkind = 3;
3425                   }
3426                   if (star_match[0][0] == NULLCHAR ||
3427                       strchr(star_match[0], ' ') ||
3428                       (tkind == 3 && strchr(star_match[1], ' '))) {
3429                     /* Reject bogus matches */
3430                     i = oldi;
3431                   } else {
3432                     if (appData.colorize) {
3433                       if (oldi > next_out) {
3434                         SendToPlayer(&buf[next_out], oldi - next_out);
3435                         next_out = oldi;
3436                       }
3437                       switch (tkind) {
3438                       case 1:
3439                         Colorize(ColorTell, FALSE);
3440                         curColor = ColorTell;
3441                         break;
3442                       case 2:
3443                         Colorize(ColorKibitz, FALSE);
3444                         curColor = ColorKibitz;
3445                         break;
3446                       case 3:
3447                         p = strrchr(star_match[1], '(');
3448                         if (p == NULL) {
3449                           p = star_match[1];
3450                         } else {
3451                           p++;
3452                         }
3453                         if (atoi(p) == 1) {
3454                           Colorize(ColorChannel1, FALSE);
3455                           curColor = ColorChannel1;
3456                         } else {
3457                           Colorize(ColorChannel, FALSE);
3458                           curColor = ColorChannel;
3459                         }
3460                         break;
3461                       case 5:
3462                         curColor = ColorNormal;
3463                         break;
3464                       }
3465                     }
3466                     if (started == STARTED_NONE && appData.autoComment &&
3467                         (gameMode == IcsObserving ||
3468                          gameMode == IcsPlayingWhite ||
3469                          gameMode == IcsPlayingBlack)) {
3470                       parse_pos = i - oldi;
3471                       memcpy(parse, &buf[oldi], parse_pos);
3472                       parse[parse_pos] = NULLCHAR;
3473                       started = STARTED_COMMENT;
3474                       savingComment = TRUE;
3475                     } else if(collective != 3) {
3476                       started = STARTED_CHATTER;
3477                       savingComment = FALSE;
3478                     }
3479                     loggedOn = TRUE;
3480                     continue;
3481                   }
3482                 }
3483
3484                 if (looking_at(buf, &i, "* s-shouts: ") ||
3485                     looking_at(buf, &i, "* c-shouts: ")) {
3486                     if (appData.colorize) {
3487                         if (oldi > next_out) {
3488                             SendToPlayer(&buf[next_out], oldi - next_out);
3489                             next_out = oldi;
3490                         }
3491                         Colorize(ColorSShout, FALSE);
3492                         curColor = ColorSShout;
3493                     }
3494                     loggedOn = TRUE;
3495                     started = STARTED_CHATTER;
3496                     continue;
3497                 }
3498
3499                 if (looking_at(buf, &i, "--->")) {
3500                     loggedOn = TRUE;
3501                     continue;
3502                 }
3503
3504                 if (looking_at(buf, &i, "* shouts: ") ||
3505                     looking_at(buf, &i, "--> ")) {
3506                     if (appData.colorize) {
3507                         if (oldi > next_out) {
3508                             SendToPlayer(&buf[next_out], oldi - next_out);
3509                             next_out = oldi;
3510                         }
3511                         Colorize(ColorShout, FALSE);
3512                         curColor = ColorShout;
3513                     }
3514                     loggedOn = TRUE;
3515                     started = STARTED_CHATTER;
3516                     continue;
3517                 }
3518
3519                 if (looking_at( buf, &i, "Challenge:")) {
3520                     if (appData.colorize) {
3521                         if (oldi > next_out) {
3522                             SendToPlayer(&buf[next_out], oldi - next_out);
3523                             next_out = oldi;
3524                         }
3525                         Colorize(ColorChallenge, FALSE);
3526                         curColor = ColorChallenge;
3527                     }
3528                     loggedOn = TRUE;
3529                     continue;
3530                 }
3531
3532                 if (looking_at(buf, &i, "* offers you") ||
3533                     looking_at(buf, &i, "* offers to be") ||
3534                     looking_at(buf, &i, "* would like to") ||
3535                     looking_at(buf, &i, "* requests to") ||
3536                     looking_at(buf, &i, "Your opponent offers") ||
3537                     looking_at(buf, &i, "Your opponent requests")) {
3538
3539                     if (appData.colorize) {
3540                         if (oldi > next_out) {
3541                             SendToPlayer(&buf[next_out], oldi - next_out);
3542                             next_out = oldi;
3543                         }
3544                         Colorize(ColorRequest, FALSE);
3545                         curColor = ColorRequest;
3546                     }
3547                     continue;
3548                 }
3549
3550                 if (looking_at(buf, &i, "* (*) seeking")) {
3551                     if (appData.colorize) {
3552                         if (oldi > next_out) {
3553                             SendToPlayer(&buf[next_out], oldi - next_out);
3554                             next_out = oldi;
3555                         }
3556                         Colorize(ColorSeek, FALSE);
3557                         curColor = ColorSeek;
3558                     }
3559                     continue;
3560             }
3561
3562           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3563
3564             if (looking_at(buf, &i, "\\   ")) {
3565                 if (prevColor != ColorNormal) {
3566                     if (oldi > next_out) {
3567                         SendToPlayer(&buf[next_out], oldi - next_out);
3568                         next_out = oldi;
3569                     }
3570                     Colorize(prevColor, TRUE);
3571                     curColor = prevColor;
3572                 }
3573                 if (savingComment) {
3574                     parse_pos = i - oldi;
3575                     memcpy(parse, &buf[oldi], parse_pos);
3576                     parse[parse_pos] = NULLCHAR;
3577                     started = STARTED_COMMENT;
3578                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3579                         chattingPartner = savingComment - 3; // kludge to remember the box
3580                 } else {
3581                     started = STARTED_CHATTER;
3582                 }
3583                 continue;
3584             }
3585
3586             if (looking_at(buf, &i, "Black Strength :") ||
3587                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3588                 looking_at(buf, &i, "<10>") ||
3589                 looking_at(buf, &i, "#@#")) {
3590                 /* Wrong board style */
3591                 loggedOn = TRUE;
3592                 SendToICS(ics_prefix);
3593                 SendToICS("set style 12\n");
3594                 SendToICS(ics_prefix);
3595                 SendToICS("refresh\n");
3596                 continue;
3597             }
3598
3599             if (looking_at(buf, &i, "login:")) {
3600               if (!have_sent_ICS_logon) {
3601                 if(ICSInitScript())
3602                   have_sent_ICS_logon = 1;
3603                 else // no init script was found
3604                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3605               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3606                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3607               }
3608                 continue;
3609             }
3610
3611             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3612                 (looking_at(buf, &i, "\n<12> ") ||
3613                  looking_at(buf, &i, "<12> "))) {
3614                 loggedOn = TRUE;
3615                 if (oldi > next_out) {
3616                     SendToPlayer(&buf[next_out], oldi - next_out);
3617                 }
3618                 next_out = i;
3619                 started = STARTED_BOARD;
3620                 parse_pos = 0;
3621                 continue;
3622             }
3623
3624             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3625                 looking_at(buf, &i, "<b1> ")) {
3626                 if (oldi > next_out) {
3627                     SendToPlayer(&buf[next_out], oldi - next_out);
3628                 }
3629                 next_out = i;
3630                 started = STARTED_HOLDINGS;
3631                 parse_pos = 0;
3632                 continue;
3633             }
3634
3635             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3636                 loggedOn = TRUE;
3637                 /* Header for a move list -- first line */
3638
3639                 switch (ics_getting_history) {
3640                   case H_FALSE:
3641                     switch (gameMode) {
3642                       case IcsIdle:
3643                       case BeginningOfGame:
3644                         /* User typed "moves" or "oldmoves" while we
3645                            were idle.  Pretend we asked for these
3646                            moves and soak them up so user can step
3647                            through them and/or save them.
3648                            */
3649                         Reset(FALSE, TRUE);
3650                         gameMode = IcsObserving;
3651                         ModeHighlight();
3652                         ics_gamenum = -1;
3653                         ics_getting_history = H_GOT_UNREQ_HEADER;
3654                         break;
3655                       case EditGame: /*?*/
3656                       case EditPosition: /*?*/
3657                         /* Should above feature work in these modes too? */
3658                         /* For now it doesn't */
3659                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3660                         break;
3661                       default:
3662                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3663                         break;
3664                     }
3665                     break;
3666                   case H_REQUESTED:
3667                     /* Is this the right one? */
3668                     if (gameInfo.white && gameInfo.black &&
3669                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3670                         strcmp(gameInfo.black, star_match[2]) == 0) {
3671                         /* All is well */
3672                         ics_getting_history = H_GOT_REQ_HEADER;
3673                     }
3674                     break;
3675                   case H_GOT_REQ_HEADER:
3676                   case H_GOT_UNREQ_HEADER:
3677                   case H_GOT_UNWANTED_HEADER:
3678                   case H_GETTING_MOVES:
3679                     /* Should not happen */
3680                     DisplayError(_("Error gathering move list: two headers"), 0);
3681                     ics_getting_history = H_FALSE;
3682                     break;
3683                 }
3684
3685                 /* Save player ratings into gameInfo if needed */
3686                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3687                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3688                     (gameInfo.whiteRating == -1 ||
3689                      gameInfo.blackRating == -1)) {
3690
3691                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3692                     gameInfo.blackRating = string_to_rating(star_match[3]);
3693                     if (appData.debugMode)
3694                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3695                               gameInfo.whiteRating, gameInfo.blackRating);
3696                 }
3697                 continue;
3698             }
3699
3700             if (looking_at(buf, &i,
3701               "* * match, initial time: * minute*, increment: * second")) {
3702                 /* Header for a move list -- second line */
3703                 /* Initial board will follow if this is a wild game */
3704                 if (gameInfo.event != NULL) free(gameInfo.event);
3705                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3706                 gameInfo.event = StrSave(str);
3707                 /* [HGM] we switched variant. Translate boards if needed. */
3708                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3709                 continue;
3710             }
3711
3712             if (looking_at(buf, &i, "Move  ")) {
3713                 /* Beginning of a move list */
3714                 switch (ics_getting_history) {
3715                   case H_FALSE:
3716                     /* Normally should not happen */
3717                     /* Maybe user hit reset while we were parsing */
3718                     break;
3719                   case H_REQUESTED:
3720                     /* Happens if we are ignoring a move list that is not
3721                      * the one we just requested.  Common if the user
3722                      * tries to observe two games without turning off
3723                      * getMoveList */
3724                     break;
3725                   case H_GETTING_MOVES:
3726                     /* Should not happen */
3727                     DisplayError(_("Error gathering move list: nested"), 0);
3728                     ics_getting_history = H_FALSE;
3729                     break;
3730                   case H_GOT_REQ_HEADER:
3731                     ics_getting_history = H_GETTING_MOVES;
3732                     started = STARTED_MOVES;
3733                     parse_pos = 0;
3734                     if (oldi > next_out) {
3735                         SendToPlayer(&buf[next_out], oldi - next_out);
3736                     }
3737                     break;
3738                   case H_GOT_UNREQ_HEADER:
3739                     ics_getting_history = H_GETTING_MOVES;
3740                     started = STARTED_MOVES_NOHIDE;
3741                     parse_pos = 0;
3742                     break;
3743                   case H_GOT_UNWANTED_HEADER:
3744                     ics_getting_history = H_FALSE;
3745                     break;
3746                 }
3747                 continue;
3748             }
3749
3750             if (looking_at(buf, &i, "% ") ||
3751                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3752                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3753                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3754                     soughtPending = FALSE;
3755                     seekGraphUp = TRUE;
3756                     DrawSeekGraph();
3757                 }
3758                 if(suppressKibitz) next_out = i;
3759                 savingComment = FALSE;
3760                 suppressKibitz = 0;
3761                 switch (started) {
3762                   case STARTED_MOVES:
3763                   case STARTED_MOVES_NOHIDE:
3764                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3765                     parse[parse_pos + i - oldi] = NULLCHAR;
3766                     ParseGameHistory(parse);
3767 #if ZIPPY
3768                     if (appData.zippyPlay && first.initDone) {
3769                         FeedMovesToProgram(&first, forwardMostMove);
3770                         if (gameMode == IcsPlayingWhite) {
3771                             if (WhiteOnMove(forwardMostMove)) {
3772                                 if (first.sendTime) {
3773                                   if (first.useColors) {
3774                                     SendToProgram("black\n", &first);
3775                                   }
3776                                   SendTimeRemaining(&first, TRUE);
3777                                 }
3778                                 if (first.useColors) {
3779                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3780                                 }
3781                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3782                                 first.maybeThinking = TRUE;
3783                             } else {
3784                                 if (first.usePlayother) {
3785                                   if (first.sendTime) {
3786                                     SendTimeRemaining(&first, TRUE);
3787                                   }
3788                                   SendToProgram("playother\n", &first);
3789                                   firstMove = FALSE;
3790                                 } else {
3791                                   firstMove = TRUE;
3792                                 }
3793                             }
3794                         } else if (gameMode == IcsPlayingBlack) {
3795                             if (!WhiteOnMove(forwardMostMove)) {
3796                                 if (first.sendTime) {
3797                                   if (first.useColors) {
3798                                     SendToProgram("white\n", &first);
3799                                   }
3800                                   SendTimeRemaining(&first, FALSE);
3801                                 }
3802                                 if (first.useColors) {
3803                                   SendToProgram("black\n", &first);
3804                                 }
3805                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3806                                 first.maybeThinking = TRUE;
3807                             } else {
3808                                 if (first.usePlayother) {
3809                                   if (first.sendTime) {
3810                                     SendTimeRemaining(&first, FALSE);
3811                                   }
3812                                   SendToProgram("playother\n", &first);
3813                                   firstMove = FALSE;
3814                                 } else {
3815                                   firstMove = TRUE;
3816                                 }
3817                             }
3818                         }
3819                     }
3820 #endif
3821                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3822                         /* Moves came from oldmoves or moves command
3823                            while we weren't doing anything else.
3824                            */
3825                         currentMove = forwardMostMove;
3826                         ClearHighlights();/*!!could figure this out*/
3827                         flipView = appData.flipView;
3828                         DrawPosition(TRUE, boards[currentMove]);
3829                         DisplayBothClocks();
3830                         snprintf(str, MSG_SIZ, "%s %s %s",
3831                                 gameInfo.white, _("vs."),  gameInfo.black);
3832                         DisplayTitle(str);
3833                         gameMode = IcsIdle;
3834                     } else {
3835                         /* Moves were history of an active game */
3836                         if (gameInfo.resultDetails != NULL) {
3837                             free(gameInfo.resultDetails);
3838                             gameInfo.resultDetails = NULL;
3839                         }
3840                     }
3841                     HistorySet(parseList, backwardMostMove,
3842                                forwardMostMove, currentMove-1);
3843                     DisplayMove(currentMove - 1);
3844                     if (started == STARTED_MOVES) next_out = i;
3845                     started = STARTED_NONE;
3846                     ics_getting_history = H_FALSE;
3847                     break;
3848
3849                   case STARTED_OBSERVE:
3850                     started = STARTED_NONE;
3851                     SendToICS(ics_prefix);
3852                     SendToICS("refresh\n");
3853                     break;
3854
3855                   default:
3856                     break;
3857                 }
3858                 if(bookHit) { // [HGM] book: simulate book reply
3859                     static char bookMove[MSG_SIZ]; // a bit generous?
3860
3861                     programStats.nodes = programStats.depth = programStats.time =
3862                     programStats.score = programStats.got_only_move = 0;
3863                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3864
3865                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3866                     strcat(bookMove, bookHit);
3867                     HandleMachineMove(bookMove, &first);
3868                 }
3869                 continue;
3870             }
3871
3872             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3873                  started == STARTED_HOLDINGS ||
3874                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3875                 /* Accumulate characters in move list or board */
3876                 parse[parse_pos++] = buf[i];
3877             }
3878
3879             /* Start of game messages.  Mostly we detect start of game
3880                when the first board image arrives.  On some versions
3881                of the ICS, though, we need to do a "refresh" after starting
3882                to observe in order to get the current board right away. */
3883             if (looking_at(buf, &i, "Adding game * to observation list")) {
3884                 started = STARTED_OBSERVE;
3885                 continue;
3886             }
3887
3888             /* Handle auto-observe */
3889             if (appData.autoObserve &&
3890                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3891                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3892                 char *player;
3893                 /* Choose the player that was highlighted, if any. */
3894                 if (star_match[0][0] == '\033' ||
3895                     star_match[1][0] != '\033') {
3896                     player = star_match[0];
3897                 } else {
3898                     player = star_match[2];
3899                 }
3900                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3901                         ics_prefix, StripHighlightAndTitle(player));
3902                 SendToICS(str);
3903
3904                 /* Save ratings from notify string */
3905                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3906                 player1Rating = string_to_rating(star_match[1]);
3907                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3908                 player2Rating = string_to_rating(star_match[3]);
3909
3910                 if (appData.debugMode)
3911                   fprintf(debugFP,
3912                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3913                           player1Name, player1Rating,
3914                           player2Name, player2Rating);
3915
3916                 continue;
3917             }
3918
3919             /* Deal with automatic examine mode after a game,
3920                and with IcsObserving -> IcsExamining transition */
3921             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3922                 looking_at(buf, &i, "has made you an examiner of game *")) {
3923
3924                 int gamenum = atoi(star_match[0]);
3925                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3926                     gamenum == ics_gamenum) {
3927                     /* We were already playing or observing this game;
3928                        no need to refetch history */
3929                     gameMode = IcsExamining;
3930                     if (pausing) {
3931                         pauseExamForwardMostMove = forwardMostMove;
3932                     } else if (currentMove < forwardMostMove) {
3933                         ForwardInner(forwardMostMove);
3934                     }
3935                 } else {
3936                     /* I don't think this case really can happen */
3937                     SendToICS(ics_prefix);
3938                     SendToICS("refresh\n");
3939                 }
3940                 continue;
3941             }
3942
3943             /* Error messages */
3944 //          if (ics_user_moved) {
3945             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3946                 if (looking_at(buf, &i, "Illegal move") ||
3947                     looking_at(buf, &i, "Not a legal move") ||
3948                     looking_at(buf, &i, "Your king is in check") ||
3949                     looking_at(buf, &i, "It isn't your turn") ||
3950                     looking_at(buf, &i, "It is not your move")) {
3951                     /* Illegal move */
3952                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3953                         currentMove = forwardMostMove-1;
3954                         DisplayMove(currentMove - 1); /* before DMError */
3955                         DrawPosition(FALSE, boards[currentMove]);
3956                         SwitchClocks(forwardMostMove-1); // [HGM] race
3957                         DisplayBothClocks();
3958                     }
3959                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3960                     ics_user_moved = 0;
3961                     continue;
3962                 }
3963             }
3964
3965             if (looking_at(buf, &i, "still have time") ||
3966                 looking_at(buf, &i, "not out of time") ||
3967                 looking_at(buf, &i, "either player is out of time") ||
3968                 looking_at(buf, &i, "has timeseal; checking")) {
3969                 /* We must have called his flag a little too soon */
3970                 whiteFlag = blackFlag = FALSE;
3971                 continue;
3972             }
3973
3974             if (looking_at(buf, &i, "added * seconds to") ||
3975                 looking_at(buf, &i, "seconds were added to")) {
3976                 /* Update the clocks */
3977                 SendToICS(ics_prefix);
3978                 SendToICS("refresh\n");
3979                 continue;
3980             }
3981
3982             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3983                 ics_clock_paused = TRUE;
3984                 StopClocks();
3985                 continue;
3986             }
3987
3988             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3989                 ics_clock_paused = FALSE;
3990                 StartClocks();
3991                 continue;
3992             }
3993
3994             /* Grab player ratings from the Creating: message.
3995                Note we have to check for the special case when
3996                the ICS inserts things like [white] or [black]. */
3997             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3998                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3999                 /* star_matches:
4000                    0    player 1 name (not necessarily white)
4001                    1    player 1 rating
4002                    2    empty, white, or black (IGNORED)
4003                    3    player 2 name (not necessarily black)
4004                    4    player 2 rating
4005
4006                    The names/ratings are sorted out when the game
4007                    actually starts (below).
4008                 */
4009                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4010                 player1Rating = string_to_rating(star_match[1]);
4011                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4012                 player2Rating = string_to_rating(star_match[4]);
4013
4014                 if (appData.debugMode)
4015                   fprintf(debugFP,
4016                           "Ratings from 'Creating:' %s %d, %s %d\n",
4017                           player1Name, player1Rating,
4018                           player2Name, player2Rating);
4019
4020                 continue;
4021             }
4022
4023             /* Improved generic start/end-of-game messages */
4024             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4025                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4026                 /* If tkind == 0: */
4027                 /* star_match[0] is the game number */
4028                 /*           [1] is the white player's name */
4029                 /*           [2] is the black player's name */
4030                 /* For end-of-game: */
4031                 /*           [3] is the reason for the game end */
4032                 /*           [4] is a PGN end game-token, preceded by " " */
4033                 /* For start-of-game: */
4034                 /*           [3] begins with "Creating" or "Continuing" */
4035                 /*           [4] is " *" or empty (don't care). */
4036                 int gamenum = atoi(star_match[0]);
4037                 char *whitename, *blackname, *why, *endtoken;
4038                 ChessMove endtype = EndOfFile;
4039
4040                 if (tkind == 0) {
4041                   whitename = star_match[1];
4042                   blackname = star_match[2];
4043                   why = star_match[3];
4044                   endtoken = star_match[4];
4045                 } else {
4046                   whitename = star_match[1];
4047                   blackname = star_match[3];
4048                   why = star_match[5];
4049                   endtoken = star_match[6];
4050                 }
4051
4052                 /* Game start messages */
4053                 if (strncmp(why, "Creating ", 9) == 0 ||
4054                     strncmp(why, "Continuing ", 11) == 0) {
4055                     gs_gamenum = gamenum;
4056                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4057                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4058                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4059 #if ZIPPY
4060                     if (appData.zippyPlay) {
4061                         ZippyGameStart(whitename, blackname);
4062                     }
4063 #endif /*ZIPPY*/
4064                     partnerBoardValid = FALSE; // [HGM] bughouse
4065                     continue;
4066                 }
4067
4068                 /* Game end messages */
4069                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4070                     ics_gamenum != gamenum) {
4071                     continue;
4072                 }
4073                 while (endtoken[0] == ' ') endtoken++;
4074                 switch (endtoken[0]) {
4075                   case '*':
4076                   default:
4077                     endtype = GameUnfinished;
4078                     break;
4079                   case '0':
4080                     endtype = BlackWins;
4081                     break;
4082                   case '1':
4083                     if (endtoken[1] == '/')
4084                       endtype = GameIsDrawn;
4085                     else
4086                       endtype = WhiteWins;
4087                     break;
4088                 }
4089                 GameEnds(endtype, why, GE_ICS);
4090 #if ZIPPY
4091                 if (appData.zippyPlay && first.initDone) {
4092                     ZippyGameEnd(endtype, why);
4093                     if (first.pr == NoProc) {
4094                       /* Start the next process early so that we'll
4095                          be ready for the next challenge */
4096                       StartChessProgram(&first);
4097                     }
4098                     /* Send "new" early, in case this command takes
4099                        a long time to finish, so that we'll be ready
4100                        for the next challenge. */
4101                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4102                     Reset(TRUE, TRUE);
4103                 }
4104 #endif /*ZIPPY*/
4105                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4106                 continue;
4107             }
4108
4109             if (looking_at(buf, &i, "Removing game * from observation") ||
4110                 looking_at(buf, &i, "no longer observing game *") ||
4111                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4112                 if (gameMode == IcsObserving &&
4113                     atoi(star_match[0]) == ics_gamenum)
4114                   {
4115                       /* icsEngineAnalyze */
4116                       if (appData.icsEngineAnalyze) {
4117                             ExitAnalyzeMode();
4118                             ModeHighlight();
4119                       }
4120                       StopClocks();
4121                       gameMode = IcsIdle;
4122                       ics_gamenum = -1;
4123                       ics_user_moved = FALSE;
4124                   }
4125                 continue;
4126             }
4127
4128             if (looking_at(buf, &i, "no longer examining game *")) {
4129                 if (gameMode == IcsExamining &&
4130                     atoi(star_match[0]) == ics_gamenum)
4131                   {
4132                       gameMode = IcsIdle;
4133                       ics_gamenum = -1;
4134                       ics_user_moved = FALSE;
4135                   }
4136                 continue;
4137             }
4138
4139             /* Advance leftover_start past any newlines we find,
4140                so only partial lines can get reparsed */
4141             if (looking_at(buf, &i, "\n")) {
4142                 prevColor = curColor;
4143                 if (curColor != ColorNormal) {
4144                     if (oldi > next_out) {
4145                         SendToPlayer(&buf[next_out], oldi - next_out);
4146                         next_out = oldi;
4147                     }
4148                     Colorize(ColorNormal, FALSE);
4149                     curColor = ColorNormal;
4150                 }
4151                 if (started == STARTED_BOARD) {
4152                     started = STARTED_NONE;
4153                     parse[parse_pos] = NULLCHAR;
4154                     ParseBoard12(parse);
4155                     ics_user_moved = 0;
4156
4157                     /* Send premove here */
4158                     if (appData.premove) {
4159                       char str[MSG_SIZ];
4160                       if (currentMove == 0 &&
4161                           gameMode == IcsPlayingWhite &&
4162                           appData.premoveWhite) {
4163                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4164                         if (appData.debugMode)
4165                           fprintf(debugFP, "Sending premove:\n");
4166                         SendToICS(str);
4167                       } else if (currentMove == 1 &&
4168                                  gameMode == IcsPlayingBlack &&
4169                                  appData.premoveBlack) {
4170                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4171                         if (appData.debugMode)
4172                           fprintf(debugFP, "Sending premove:\n");
4173                         SendToICS(str);
4174                       } else if (gotPremove) {
4175                         int oldFMM = forwardMostMove;
4176                         gotPremove = 0;
4177                         ClearPremoveHighlights();
4178                         if (appData.debugMode)
4179                           fprintf(debugFP, "Sending premove:\n");
4180                           UserMoveEvent(premoveFromX, premoveFromY,
4181                                         premoveToX, premoveToY,
4182                                         premovePromoChar);
4183                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4184                           if(moveList[oldFMM-1][1] != '@')
4185                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4186                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4187                           else // (drop)
4188                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4189                         }
4190                       }
4191                     }
4192
4193                     /* Usually suppress following prompt */
4194                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4195                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4196                         if (looking_at(buf, &i, "*% ")) {
4197                             savingComment = FALSE;
4198                             suppressKibitz = 0;
4199                         }
4200                     }
4201                     next_out = i;
4202                 } else if (started == STARTED_HOLDINGS) {
4203                     int gamenum;
4204                     char new_piece[MSG_SIZ];
4205                     started = STARTED_NONE;
4206                     parse[parse_pos] = NULLCHAR;
4207                     if (appData.debugMode)
4208                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4209                                                         parse, currentMove);
4210                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4211                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4212                         if (gameInfo.variant == VariantNormal) {
4213                           /* [HGM] We seem to switch variant during a game!
4214                            * Presumably no holdings were displayed, so we have
4215                            * to move the position two files to the right to
4216                            * create room for them!
4217                            */
4218                           VariantClass newVariant;
4219                           switch(gameInfo.boardWidth) { // base guess on board width
4220                                 case 9:  newVariant = VariantShogi; break;
4221                                 case 10: newVariant = VariantGreat; break;
4222                                 default: newVariant = VariantCrazyhouse; break;
4223                           }
4224                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4225                           /* Get a move list just to see the header, which
4226                              will tell us whether this is really bug or zh */
4227                           if (ics_getting_history == H_FALSE) {
4228                             ics_getting_history = H_REQUESTED;
4229                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4230                             SendToICS(str);
4231                           }
4232                         }
4233                         new_piece[0] = NULLCHAR;
4234                         sscanf(parse, "game %d white [%s black [%s <- %s",
4235                                &gamenum, white_holding, black_holding,
4236                                new_piece);
4237                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4238                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4239                         /* [HGM] copy holdings to board holdings area */
4240                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4241                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4242                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4243 #if ZIPPY
4244                         if (appData.zippyPlay && first.initDone) {
4245                             ZippyHoldings(white_holding, black_holding,
4246                                           new_piece);
4247                         }
4248 #endif /*ZIPPY*/
4249                         if (tinyLayout || smallLayout) {
4250                             char wh[16], bh[16];
4251                             PackHolding(wh, white_holding);
4252                             PackHolding(bh, black_holding);
4253                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4254                                     gameInfo.white, gameInfo.black);
4255                         } else {
4256                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4257                                     gameInfo.white, white_holding, _("vs."),
4258                                     gameInfo.black, black_holding);
4259                         }
4260                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4261                         DrawPosition(FALSE, boards[currentMove]);
4262                         DisplayTitle(str);
4263                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4264                         sscanf(parse, "game %d white [%s black [%s <- %s",
4265                                &gamenum, white_holding, black_holding,
4266                                new_piece);
4267                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4268                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4269                         /* [HGM] copy holdings to partner-board holdings area */
4270                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4271                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4272                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4273                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4274                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4275                       }
4276                     }
4277                     /* Suppress following prompt */
4278                     if (looking_at(buf, &i, "*% ")) {
4279                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4280                         savingComment = FALSE;
4281                         suppressKibitz = 0;
4282                     }
4283                     next_out = i;
4284                 }
4285                 continue;
4286             }
4287
4288             i++;                /* skip unparsed character and loop back */
4289         }
4290
4291         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4292 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4293 //          SendToPlayer(&buf[next_out], i - next_out);
4294             started != STARTED_HOLDINGS && leftover_start > next_out) {
4295             SendToPlayer(&buf[next_out], leftover_start - next_out);
4296             next_out = i;
4297         }
4298
4299         leftover_len = buf_len - leftover_start;
4300         /* if buffer ends with something we couldn't parse,
4301            reparse it after appending the next read */
4302
4303     } else if (count == 0) {
4304         RemoveInputSource(isr);
4305         DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4306     } else {
4307         DisplayFatalError(_("Error reading from ICS"), error, 1);
4308     }
4309 }
4310
4311
4312 /* Board style 12 looks like this:
4313
4314    <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
4315
4316  * The "<12> " is stripped before it gets to this routine.  The two
4317  * trailing 0's (flip state and clock ticking) are later addition, and
4318  * some chess servers may not have them, or may have only the first.
4319  * Additional trailing fields may be added in the future.
4320  */
4321
4322 #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"
4323
4324 #define RELATION_OBSERVING_PLAYED    0
4325 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4326 #define RELATION_PLAYING_MYMOVE      1
4327 #define RELATION_PLAYING_NOTMYMOVE  -1
4328 #define RELATION_EXAMINING           2
4329 #define RELATION_ISOLATED_BOARD     -3
4330 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4331
4332 void
4333 ParseBoard12 (char *string)
4334 {
4335 #if ZIPPY
4336     int i, takeback;
4337     char *bookHit = NULL; // [HGM] book
4338 #endif
4339     GameMode newGameMode;
4340     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4341     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4342     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4343     char to_play, board_chars[200];
4344     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4345     char black[32], white[32];
4346     Board board;
4347     int prevMove = currentMove;
4348     int ticking = 2;
4349     ChessMove moveType;
4350     int fromX, fromY, toX, toY;
4351     char promoChar;
4352     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4353     Boolean weird = FALSE, reqFlag = FALSE;
4354
4355     fromX = fromY = toX = toY = -1;
4356
4357     newGame = FALSE;
4358
4359     if (appData.debugMode)
4360       fprintf(debugFP, "Parsing board: %s\n", string);
4361
4362     move_str[0] = NULLCHAR;
4363     elapsed_time[0] = NULLCHAR;
4364     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4365         int  i = 0, j;
4366         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4367             if(string[i] == ' ') { ranks++; files = 0; }
4368             else files++;
4369             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4370             i++;
4371         }
4372         for(j = 0; j <i; j++) board_chars[j] = string[j];
4373         board_chars[i] = '\0';
4374         string += i + 1;
4375     }
4376     n = sscanf(string, PATTERN, &to_play, &double_push,
4377                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4378                &gamenum, white, black, &relation, &basetime, &increment,
4379                &white_stren, &black_stren, &white_time, &black_time,
4380                &moveNum, str, elapsed_time, move_str, &ics_flip,
4381                &ticking);
4382
4383     if (n < 21) {
4384         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4385         DisplayError(str, 0);
4386         return;
4387     }
4388
4389     /* Convert the move number to internal form */
4390     moveNum = (moveNum - 1) * 2;
4391     if (to_play == 'B') moveNum++;
4392     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4393       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4394                         0, 1);
4395       return;
4396     }
4397
4398     switch (relation) {
4399       case RELATION_OBSERVING_PLAYED:
4400       case RELATION_OBSERVING_STATIC:
4401         if (gamenum == -1) {
4402             /* Old ICC buglet */
4403             relation = RELATION_OBSERVING_STATIC;
4404         }
4405         newGameMode = IcsObserving;
4406         break;
4407       case RELATION_PLAYING_MYMOVE:
4408       case RELATION_PLAYING_NOTMYMOVE:
4409         newGameMode =
4410           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4411             IcsPlayingWhite : IcsPlayingBlack;
4412         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4413         break;
4414       case RELATION_EXAMINING:
4415         newGameMode = IcsExamining;
4416         break;
4417       case RELATION_ISOLATED_BOARD:
4418       default:
4419         /* Just display this board.  If user was doing something else,
4420            we will forget about it until the next board comes. */
4421         newGameMode = IcsIdle;
4422         break;
4423       case RELATION_STARTING_POSITION:
4424         newGameMode = gameMode;
4425         break;
4426     }
4427
4428     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4429         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4430          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4431       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4432       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4433       static int lastBgGame = -1;
4434       char *toSqr;
4435       for (k = 0; k < ranks; k++) {
4436         for (j = 0; j < files; j++)
4437           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4438         if(gameInfo.holdingsWidth > 1) {
4439              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4440              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4441         }
4442       }
4443       CopyBoard(partnerBoard, board);
4444       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4445         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4446         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4447       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4448       if(toSqr = strchr(str, '-')) {
4449         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4450         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4451       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4452       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4453       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4454       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4455       if(twoBoards) {
4456           DisplayWhiteClock(white_time*fac, to_play == 'W');
4457           DisplayBlackClock(black_time*fac, to_play != 'W');
4458           activePartner = to_play;
4459           if(gamenum != lastBgGame) {
4460               char buf[MSG_SIZ];
4461               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4462               DisplayTitle(buf);
4463           }
4464           lastBgGame = gamenum;
4465           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4466                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4467       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4468                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4469       if(!twoBoards) DisplayMessage(partnerStatus, "");
4470         partnerBoardValid = TRUE;
4471       return;
4472     }
4473
4474     if(appData.dualBoard && appData.bgObserve) {
4475         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4476             SendToICS(ics_prefix), SendToICS("pobserve\n");
4477         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4478             char buf[MSG_SIZ];
4479             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4480             SendToICS(buf);
4481         }
4482     }
4483
4484     /* Modify behavior for initial board display on move listing
4485        of wild games.
4486        */
4487     switch (ics_getting_history) {
4488       case H_FALSE:
4489       case H_REQUESTED:
4490         break;
4491       case H_GOT_REQ_HEADER:
4492       case H_GOT_UNREQ_HEADER:
4493         /* This is the initial position of the current game */
4494         gamenum = ics_gamenum;
4495         moveNum = 0;            /* old ICS bug workaround */
4496         if (to_play == 'B') {
4497           startedFromSetupPosition = TRUE;
4498           blackPlaysFirst = TRUE;
4499           moveNum = 1;
4500           if (forwardMostMove == 0) forwardMostMove = 1;
4501           if (backwardMostMove == 0) backwardMostMove = 1;
4502           if (currentMove == 0) currentMove = 1;
4503         }
4504         newGameMode = gameMode;
4505         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4506         break;
4507       case H_GOT_UNWANTED_HEADER:
4508         /* This is an initial board that we don't want */
4509         return;
4510       case H_GETTING_MOVES:
4511         /* Should not happen */
4512         DisplayError(_("Error gathering move list: extra board"), 0);
4513         ics_getting_history = H_FALSE;
4514         return;
4515     }
4516
4517    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4518                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4519                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4520      /* [HGM] We seem to have switched variant unexpectedly
4521       * Try to guess new variant from board size
4522       */
4523           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4524           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4525           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4526           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4527           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4528           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4529           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4530           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4531           /* Get a move list just to see the header, which
4532              will tell us whether this is really bug or zh */
4533           if (ics_getting_history == H_FALSE) {
4534             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4535             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4536             SendToICS(str);
4537           }
4538     }
4539
4540     /* Take action if this is the first board of a new game, or of a
4541        different game than is currently being displayed.  */
4542     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4543         relation == RELATION_ISOLATED_BOARD) {
4544
4545         /* Forget the old game and get the history (if any) of the new one */
4546         if (gameMode != BeginningOfGame) {
4547           Reset(TRUE, TRUE);
4548         }
4549         newGame = TRUE;
4550         if (appData.autoRaiseBoard) BoardToTop();
4551         prevMove = -3;
4552         if (gamenum == -1) {
4553             newGameMode = IcsIdle;
4554         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4555                    appData.getMoveList && !reqFlag) {
4556             /* Need to get game history */
4557             ics_getting_history = H_REQUESTED;
4558             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4559             SendToICS(str);
4560         }
4561
4562         /* Initially flip the board to have black on the bottom if playing
4563            black or if the ICS flip flag is set, but let the user change
4564            it with the Flip View button. */
4565         flipView = appData.autoFlipView ?
4566           (newGameMode == IcsPlayingBlack) || ics_flip :
4567           appData.flipView;
4568
4569         /* Done with values from previous mode; copy in new ones */
4570         gameMode = newGameMode;
4571         ModeHighlight();
4572         ics_gamenum = gamenum;
4573         if (gamenum == gs_gamenum) {
4574             int klen = strlen(gs_kind);
4575             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4576             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4577             gameInfo.event = StrSave(str);
4578         } else {
4579             gameInfo.event = StrSave("ICS game");
4580         }
4581         gameInfo.site = StrSave(appData.icsHost);
4582         gameInfo.date = PGNDate();
4583         gameInfo.round = StrSave("-");
4584         gameInfo.white = StrSave(white);
4585         gameInfo.black = StrSave(black);
4586         timeControl = basetime * 60 * 1000;
4587         timeControl_2 = 0;
4588         timeIncrement = increment * 1000;
4589         movesPerSession = 0;
4590         gameInfo.timeControl = TimeControlTagValue();
4591         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4592   if (appData.debugMode) {
4593     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4594     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4595     setbuf(debugFP, NULL);
4596   }
4597
4598         gameInfo.outOfBook = NULL;
4599
4600         /* Do we have the ratings? */
4601         if (strcmp(player1Name, white) == 0 &&
4602             strcmp(player2Name, black) == 0) {
4603             if (appData.debugMode)
4604               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4605                       player1Rating, player2Rating);
4606             gameInfo.whiteRating = player1Rating;
4607             gameInfo.blackRating = player2Rating;
4608         } else if (strcmp(player2Name, white) == 0 &&
4609                    strcmp(player1Name, black) == 0) {
4610             if (appData.debugMode)
4611               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4612                       player2Rating, player1Rating);
4613             gameInfo.whiteRating = player2Rating;
4614             gameInfo.blackRating = player1Rating;
4615         }
4616         player1Name[0] = player2Name[0] = NULLCHAR;
4617
4618         /* Silence shouts if requested */
4619         if (appData.quietPlay &&
4620             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4621             SendToICS(ics_prefix);
4622             SendToICS("set shout 0\n");
4623         }
4624     }
4625
4626     /* Deal with midgame name changes */
4627     if (!newGame) {
4628         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4629             if (gameInfo.white) free(gameInfo.white);
4630             gameInfo.white = StrSave(white);
4631         }
4632         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4633             if (gameInfo.black) free(gameInfo.black);
4634             gameInfo.black = StrSave(black);
4635         }
4636     }
4637
4638     /* Throw away game result if anything actually changes in examine mode */
4639     if (gameMode == IcsExamining && !newGame) {
4640         gameInfo.result = GameUnfinished;
4641         if (gameInfo.resultDetails != NULL) {
4642             free(gameInfo.resultDetails);
4643             gameInfo.resultDetails = NULL;
4644         }
4645     }
4646
4647     /* In pausing && IcsExamining mode, we ignore boards coming
4648        in if they are in a different variation than we are. */
4649     if (pauseExamInvalid) return;
4650     if (pausing && gameMode == IcsExamining) {
4651         if (moveNum <= pauseExamForwardMostMove) {
4652             pauseExamInvalid = TRUE;
4653             forwardMostMove = pauseExamForwardMostMove;
4654             return;
4655         }
4656     }
4657
4658   if (appData.debugMode) {
4659     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4660   }
4661     /* Parse the board */
4662     for (k = 0; k < ranks; k++) {
4663       for (j = 0; j < files; j++)
4664         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4665       if(gameInfo.holdingsWidth > 1) {
4666            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4667            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4668       }
4669     }
4670     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4671       board[5][BOARD_RGHT+1] = WhiteAngel;
4672       board[6][BOARD_RGHT+1] = WhiteMarshall;
4673       board[1][0] = BlackMarshall;
4674       board[2][0] = BlackAngel;
4675       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4676     }
4677     CopyBoard(boards[moveNum], board);
4678     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4679     if (moveNum == 0) {
4680         startedFromSetupPosition =
4681           !CompareBoards(board, initialPosition);
4682         if(startedFromSetupPosition)
4683             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4684     }
4685
4686     /* [HGM] Set castling rights. Take the outermost Rooks,
4687        to make it also work for FRC opening positions. Note that board12
4688        is really defective for later FRC positions, as it has no way to
4689        indicate which Rook can castle if they are on the same side of King.
4690        For the initial position we grant rights to the outermost Rooks,
4691        and remember thos rights, and we then copy them on positions
4692        later in an FRC game. This means WB might not recognize castlings with
4693        Rooks that have moved back to their original position as illegal,
4694        but in ICS mode that is not its job anyway.
4695     */
4696     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4697     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4698
4699         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4700             if(board[0][i] == WhiteRook) j = i;
4701         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4702         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4703             if(board[0][i] == WhiteRook) j = i;
4704         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4705         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4706             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4707         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4708         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4709             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4710         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4711
4712         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4713         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4714         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4715             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4716         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4717             if(board[BOARD_HEIGHT-1][k] == bKing)
4718                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4719         if(gameInfo.variant == VariantTwoKings) {
4720             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4721             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4722             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4723         }
4724     } else { int r;
4725         r = boards[moveNum][CASTLING][0] = initialRights[0];
4726         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4727         r = boards[moveNum][CASTLING][1] = initialRights[1];
4728         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4729         r = boards[moveNum][CASTLING][3] = initialRights[3];
4730         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4731         r = boards[moveNum][CASTLING][4] = initialRights[4];
4732         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4733         /* wildcastle kludge: always assume King has rights */
4734         r = boards[moveNum][CASTLING][2] = initialRights[2];
4735         r = boards[moveNum][CASTLING][5] = initialRights[5];
4736     }
4737     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4738     boards[moveNum][EP_STATUS] = EP_NONE;
4739     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4740     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4741     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4742
4743
4744     if (ics_getting_history == H_GOT_REQ_HEADER ||
4745         ics_getting_history == H_GOT_UNREQ_HEADER) {
4746         /* This was an initial position from a move list, not
4747            the current position */
4748         return;
4749     }
4750
4751     /* Update currentMove and known move number limits */
4752     newMove = newGame || moveNum > forwardMostMove;
4753
4754     if (newGame) {
4755         forwardMostMove = backwardMostMove = currentMove = moveNum;
4756         if (gameMode == IcsExamining && moveNum == 0) {
4757           /* Workaround for ICS limitation: we are not told the wild
4758              type when starting to examine a game.  But if we ask for
4759              the move list, the move list header will tell us */
4760             ics_getting_history = H_REQUESTED;
4761             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4762             SendToICS(str);
4763         }
4764     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4765                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4766 #if ZIPPY
4767         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4768         /* [HGM] applied this also to an engine that is silently watching        */
4769         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4770             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4771             gameInfo.variant == currentlyInitializedVariant) {
4772           takeback = forwardMostMove - moveNum;
4773           for (i = 0; i < takeback; i++) {
4774             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4775             SendToProgram("undo\n", &first);
4776           }
4777         }
4778 #endif
4779
4780         forwardMostMove = moveNum;
4781         if (!pausing || currentMove > forwardMostMove)
4782           currentMove = forwardMostMove;
4783     } else {
4784         /* New part of history that is not contiguous with old part */
4785         if (pausing && gameMode == IcsExamining) {
4786             pauseExamInvalid = TRUE;
4787             forwardMostMove = pauseExamForwardMostMove;
4788             return;
4789         }
4790         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4791 #if ZIPPY
4792             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4793                 // [HGM] when we will receive the move list we now request, it will be
4794                 // fed to the engine from the first move on. So if the engine is not
4795                 // in the initial position now, bring it there.
4796                 InitChessProgram(&first, 0);
4797             }
4798 #endif
4799             ics_getting_history = H_REQUESTED;
4800             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4801             SendToICS(str);
4802         }
4803         forwardMostMove = backwardMostMove = currentMove = moveNum;
4804     }
4805
4806     /* Update the clocks */
4807     if (strchr(elapsed_time, '.')) {
4808       /* Time is in ms */
4809       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4810       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4811     } else {
4812       /* Time is in seconds */
4813       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4814       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4815     }
4816
4817
4818 #if ZIPPY
4819     if (appData.zippyPlay && newGame &&
4820         gameMode != IcsObserving && gameMode != IcsIdle &&
4821         gameMode != IcsExamining)
4822       ZippyFirstBoard(moveNum, basetime, increment);
4823 #endif
4824
4825     /* Put the move on the move list, first converting
4826        to canonical algebraic form. */
4827     if (moveNum > 0) {
4828   if (appData.debugMode) {
4829     int f = forwardMostMove;
4830     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4831             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4832             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4833     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4834     fprintf(debugFP, "moveNum = %d\n", moveNum);
4835     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4836     setbuf(debugFP, NULL);
4837   }
4838         if (moveNum <= backwardMostMove) {
4839             /* We don't know what the board looked like before
4840                this move.  Punt. */
4841           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4842             strcat(parseList[moveNum - 1], " ");
4843             strcat(parseList[moveNum - 1], elapsed_time);
4844             moveList[moveNum - 1][0] = NULLCHAR;
4845         } else if (strcmp(move_str, "none") == 0) {
4846             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4847             /* Again, we don't know what the board looked like;
4848                this is really the start of the game. */
4849             parseList[moveNum - 1][0] = NULLCHAR;
4850             moveList[moveNum - 1][0] = NULLCHAR;
4851             backwardMostMove = moveNum;
4852             startedFromSetupPosition = TRUE;
4853             fromX = fromY = toX = toY = -1;
4854         } else {
4855           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4856           //                 So we parse the long-algebraic move string in stead of the SAN move
4857           int valid; char buf[MSG_SIZ], *prom;
4858
4859           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4860                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4861           // str looks something like "Q/a1-a2"; kill the slash
4862           if(str[1] == '/')
4863             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4864           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4865           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4866                 strcat(buf, prom); // long move lacks promo specification!
4867           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4868                 if(appData.debugMode)
4869                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4870                 safeStrCpy(move_str, buf, MSG_SIZ);
4871           }
4872           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4873                                 &fromX, &fromY, &toX, &toY, &promoChar)
4874                || ParseOneMove(buf, moveNum - 1, &moveType,
4875                                 &fromX, &fromY, &toX, &toY, &promoChar);
4876           // end of long SAN patch
4877           if (valid) {
4878             (void) CoordsToAlgebraic(boards[moveNum - 1],
4879                                      PosFlags(moveNum - 1),
4880                                      fromY, fromX, toY, toX, promoChar,
4881                                      parseList[moveNum-1]);
4882             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4883               case MT_NONE:
4884               case MT_STALEMATE:
4885               default:
4886                 break;
4887               case MT_CHECK:
4888                 if(!IS_SHOGI(gameInfo.variant))
4889                     strcat(parseList[moveNum - 1], "+");
4890                 break;
4891               case MT_CHECKMATE:
4892               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4893                 strcat(parseList[moveNum - 1], "#");
4894                 break;
4895             }
4896             strcat(parseList[moveNum - 1], " ");
4897             strcat(parseList[moveNum - 1], elapsed_time);
4898             /* currentMoveString is set as a side-effect of ParseOneMove */
4899             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4900             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4901             strcat(moveList[moveNum - 1], "\n");
4902
4903             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4904                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4905               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4906                 ChessSquare old, new = boards[moveNum][k][j];
4907                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4908                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4909                   if(old == new) continue;
4910                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4911                   else if(new == WhiteWazir || new == BlackWazir) {
4912                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4913                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4914                       else boards[moveNum][k][j] = old; // preserve type of Gold
4915                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4916                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4917               }
4918           } else {
4919             /* Move from ICS was illegal!?  Punt. */
4920             if (appData.debugMode) {
4921               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4922               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4923             }
4924             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4925             strcat(parseList[moveNum - 1], " ");
4926             strcat(parseList[moveNum - 1], elapsed_time);
4927             moveList[moveNum - 1][0] = NULLCHAR;
4928             fromX = fromY = toX = toY = -1;
4929           }
4930         }
4931   if (appData.debugMode) {
4932     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4933     setbuf(debugFP, NULL);
4934   }
4935
4936 #if ZIPPY
4937         /* Send move to chess program (BEFORE animating it). */
4938         if (appData.zippyPlay && !newGame && newMove &&
4939            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4940
4941             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4942                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4943                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4944                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4945                             move_str);
4946                     DisplayError(str, 0);
4947                 } else {
4948                     if (first.sendTime) {
4949                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4950                     }
4951                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4952                     if (firstMove && !bookHit) {
4953                         firstMove = FALSE;
4954                         if (first.useColors) {
4955                           SendToProgram(gameMode == IcsPlayingWhite ?
4956                                         "white\ngo\n" :
4957                                         "black\ngo\n", &first);
4958                         } else {
4959                           SendToProgram("go\n", &first);
4960                         }
4961                         first.maybeThinking = TRUE;
4962                     }
4963                 }
4964             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4965               if (moveList[moveNum - 1][0] == NULLCHAR) {
4966                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4967                 DisplayError(str, 0);
4968               } else {
4969                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4970                 SendMoveToProgram(moveNum - 1, &first);
4971               }
4972             }
4973         }
4974 #endif
4975     }
4976
4977     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4978         /* If move comes from a remote source, animate it.  If it
4979            isn't remote, it will have already been animated. */
4980         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4981             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4982         }
4983         if (!pausing && appData.highlightLastMove) {
4984             SetHighlights(fromX, fromY, toX, toY);
4985         }
4986     }
4987
4988     /* Start the clocks */
4989     whiteFlag = blackFlag = FALSE;
4990     appData.clockMode = !(basetime == 0 && increment == 0);
4991     if (ticking == 0) {
4992       ics_clock_paused = TRUE;
4993       StopClocks();
4994     } else if (ticking == 1) {
4995       ics_clock_paused = FALSE;
4996     }
4997     if (gameMode == IcsIdle ||
4998         relation == RELATION_OBSERVING_STATIC ||
4999         relation == RELATION_EXAMINING ||
5000         ics_clock_paused)
5001       DisplayBothClocks();
5002     else
5003       StartClocks();
5004
5005     /* Display opponents and material strengths */
5006     if (gameInfo.variant != VariantBughouse &&
5007         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5008         if (tinyLayout || smallLayout) {
5009             if(gameInfo.variant == VariantNormal)
5010               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5011                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5012                     basetime, increment);
5013             else
5014               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5015                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5016                     basetime, increment, (int) gameInfo.variant);
5017         } else {
5018             if(gameInfo.variant == VariantNormal)
5019               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5020                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5021                     basetime, increment);
5022             else
5023               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5024                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5025                     basetime, increment, VariantName(gameInfo.variant));
5026         }
5027         DisplayTitle(str);
5028   if (appData.debugMode) {
5029     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5030   }
5031     }
5032
5033
5034     /* Display the board */
5035     if (!pausing && !appData.noGUI) {
5036
5037       if (appData.premove)
5038           if (!gotPremove ||
5039              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5040              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5041               ClearPremoveHighlights();
5042
5043       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5044         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5045       DrawPosition(j, boards[currentMove]);
5046
5047       DisplayMove(moveNum - 1);
5048       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5049             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5050               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5051         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5052       }
5053     }
5054
5055     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5056 #if ZIPPY
5057     if(bookHit) { // [HGM] book: simulate book reply
5058         static char bookMove[MSG_SIZ]; // a bit generous?
5059
5060         programStats.nodes = programStats.depth = programStats.time =
5061         programStats.score = programStats.got_only_move = 0;
5062         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5063
5064         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5065         strcat(bookMove, bookHit);
5066         HandleMachineMove(bookMove, &first);
5067     }
5068 #endif
5069 }
5070
5071 void
5072 GetMoveListEvent ()
5073 {
5074     char buf[MSG_SIZ];
5075     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5076         ics_getting_history = H_REQUESTED;
5077         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5078         SendToICS(buf);
5079     }
5080 }
5081
5082 void
5083 SendToBoth (char *msg)
5084 {   // to make it easy to keep two engines in step in dual analysis
5085     SendToProgram(msg, &first);
5086     if(second.analyzing) SendToProgram(msg, &second);
5087 }
5088
5089 void
5090 AnalysisPeriodicEvent (int force)
5091 {
5092     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5093          && !force) || !appData.periodicUpdates)
5094       return;
5095
5096     /* Send . command to Crafty to collect stats */
5097     SendToBoth(".\n");
5098
5099     /* Don't send another until we get a response (this makes
5100        us stop sending to old Crafty's which don't understand
5101        the "." command (sending illegal cmds resets node count & time,
5102        which looks bad)) */
5103     programStats.ok_to_send = 0;
5104 }
5105
5106 void
5107 ics_update_width (int new_width)
5108 {
5109         ics_printf("set width %d\n", new_width);
5110 }
5111
5112 void
5113 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5114 {
5115     char buf[MSG_SIZ];
5116
5117     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5118         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5119             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5120             SendToProgram(buf, cps);
5121             return;
5122         }
5123         // null move in variant where engine does not understand it (for analysis purposes)
5124         SendBoard(cps, moveNum + 1); // send position after move in stead.
5125         return;
5126     }
5127     if (cps->useUsermove) {
5128       SendToProgram("usermove ", cps);
5129     }
5130     if (cps->useSAN) {
5131       char *space;
5132       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5133         int len = space - parseList[moveNum];
5134         memcpy(buf, parseList[moveNum], len);
5135         buf[len++] = '\n';
5136         buf[len] = NULLCHAR;
5137       } else {
5138         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5139       }
5140       SendToProgram(buf, cps);
5141     } else {
5142       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5143         AlphaRank(moveList[moveNum], 4);
5144         SendToProgram(moveList[moveNum], cps);
5145         AlphaRank(moveList[moveNum], 4); // and back
5146       } else
5147       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5148        * the engine. It would be nice to have a better way to identify castle
5149        * moves here. */
5150       if(appData.fischerCastling && cps->useOOCastle) {
5151         int fromX = moveList[moveNum][0] - AAA;
5152         int fromY = moveList[moveNum][1] - ONE;
5153         int toX = moveList[moveNum][2] - AAA;
5154         int toY = moveList[moveNum][3] - ONE;
5155         if((boards[moveNum][fromY][fromX] == WhiteKing
5156             && boards[moveNum][toY][toX] == WhiteRook)
5157            || (boards[moveNum][fromY][fromX] == BlackKing
5158                && boards[moveNum][toY][toX] == BlackRook)) {
5159           if(toX > fromX) SendToProgram("O-O\n", cps);
5160           else SendToProgram("O-O-O\n", cps);
5161         }
5162         else SendToProgram(moveList[moveNum], cps);
5163       } else
5164       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5165         char *m = moveList[moveNum];
5166         static char c[2];
5167         *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5168         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5169           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5170                                                m[2], m[3] - '0',
5171                                                m[5], m[6] - '0',
5172                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5173         else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5174           *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5175           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
5176                                                m[7], m[8] - '0',
5177                                                m[7], m[8] - '0',
5178                                                m[5], m[6] - '0',
5179                                                m[5], m[6] - '0',
5180                                                m[2], m[3] - '0', c);
5181         } else
5182           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5183                                                m[5], m[6] - '0',
5184                                                m[5], m[6] - '0',
5185                                                m[2], m[3] - '0', c);
5186           SendToProgram(buf, cps);
5187       } else
5188       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5189         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5190           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5191           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5192                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5193         } else
5194           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5195                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5196         SendToProgram(buf, cps);
5197       }
5198       else SendToProgram(moveList[moveNum], cps);
5199       /* End of additions by Tord */
5200     }
5201
5202     /* [HGM] setting up the opening has brought engine in force mode! */
5203     /*       Send 'go' if we are in a mode where machine should play. */
5204     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5205         (gameMode == TwoMachinesPlay   ||
5206 #if ZIPPY
5207          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5208 #endif
5209          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5210         SendToProgram("go\n", cps);
5211   if (appData.debugMode) {
5212     fprintf(debugFP, "(extra)\n");
5213   }
5214     }
5215     setboardSpoiledMachineBlack = 0;
5216 }
5217
5218 void
5219 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5220 {
5221     char user_move[MSG_SIZ];
5222     char suffix[4];
5223
5224     if(gameInfo.variant == VariantSChess && promoChar) {
5225         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5226         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5227     } else suffix[0] = NULLCHAR;
5228
5229     switch (moveType) {
5230       default:
5231         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5232                 (int)moveType, fromX, fromY, toX, toY);
5233         DisplayError(user_move + strlen("say "), 0);
5234         break;
5235       case WhiteKingSideCastle:
5236       case BlackKingSideCastle:
5237       case WhiteQueenSideCastleWild:
5238       case BlackQueenSideCastleWild:
5239       /* PUSH Fabien */
5240       case WhiteHSideCastleFR:
5241       case BlackHSideCastleFR:
5242       /* POP Fabien */
5243         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5244         break;
5245       case WhiteQueenSideCastle:
5246       case BlackQueenSideCastle:
5247       case WhiteKingSideCastleWild:
5248       case BlackKingSideCastleWild:
5249       /* PUSH Fabien */
5250       case WhiteASideCastleFR:
5251       case BlackASideCastleFR:
5252       /* POP Fabien */
5253         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5254         break;
5255       case WhiteNonPromotion:
5256       case BlackNonPromotion:
5257         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5258         break;
5259       case WhitePromotion:
5260       case BlackPromotion:
5261         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5262            gameInfo.variant == VariantMakruk)
5263           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5264                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5265                 PieceToChar(WhiteFerz));
5266         else if(gameInfo.variant == VariantGreat)
5267           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5268                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5269                 PieceToChar(WhiteMan));
5270         else
5271           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5272                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5273                 promoChar);
5274         break;
5275       case WhiteDrop:
5276       case BlackDrop:
5277       drop:
5278         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5279                  ToUpper(PieceToChar((ChessSquare) fromX)),
5280                  AAA + toX, ONE + toY);
5281         break;
5282       case IllegalMove:  /* could be a variant we don't quite understand */
5283         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5284       case NormalMove:
5285       case WhiteCapturesEnPassant:
5286       case BlackCapturesEnPassant:
5287         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5288                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5289         break;
5290     }
5291     SendToICS(user_move);
5292     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5293         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5294 }
5295
5296 void
5297 UploadGameEvent ()
5298 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5299     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5300     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5301     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5302       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5303       return;
5304     }
5305     if(gameMode != IcsExamining) { // is this ever not the case?
5306         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5307
5308         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5309           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5310         } else { // on FICS we must first go to general examine mode
5311           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5312         }
5313         if(gameInfo.variant != VariantNormal) {
5314             // try figure out wild number, as xboard names are not always valid on ICS
5315             for(i=1; i<=36; i++) {
5316               snprintf(buf, MSG_SIZ, "wild/%d", i);
5317                 if(StringToVariant(buf) == gameInfo.variant) break;
5318             }
5319             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5320             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5321             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5322         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5323         SendToICS(ics_prefix);
5324         SendToICS(buf);
5325         if(startedFromSetupPosition || backwardMostMove != 0) {
5326           fen = PositionToFEN(backwardMostMove, NULL, 1);
5327           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5328             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5329             SendToICS(buf);
5330           } else { // FICS: everything has to set by separate bsetup commands
5331             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5332             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5333             SendToICS(buf);
5334             if(!WhiteOnMove(backwardMostMove)) {
5335                 SendToICS("bsetup tomove black\n");
5336             }
5337             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5338             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5339             SendToICS(buf);
5340             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5341             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5342             SendToICS(buf);
5343             i = boards[backwardMostMove][EP_STATUS];
5344             if(i >= 0) { // set e.p.
5345               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5346                 SendToICS(buf);
5347             }
5348             bsetup++;
5349           }
5350         }
5351       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5352             SendToICS("bsetup done\n"); // switch to normal examining.
5353     }
5354     for(i = backwardMostMove; i<last; i++) {
5355         char buf[20];
5356         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5357         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5358             int len = strlen(moveList[i]);
5359             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5360             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5361         }
5362         SendToICS(buf);
5363     }
5364     SendToICS(ics_prefix);
5365     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5366 }
5367
5368 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5369 int legNr = 1;
5370
5371 void
5372 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5373 {
5374     if (rf == DROP_RANK) {
5375       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5376       sprintf(move, "%c@%c%c\n",
5377                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5378     } else {
5379         if (promoChar == 'x' || promoChar == NULLCHAR) {
5380           sprintf(move, "%c%c%c%c\n",
5381                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5382           if(killX >= 0 && killY >= 0) {
5383             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5384             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5385           }
5386         } else {
5387             sprintf(move, "%c%c%c%c%c\n",
5388                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5389           if(killX >= 0 && killY >= 0) {
5390             sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5391             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5392           }
5393         }
5394     }
5395 }
5396
5397 void
5398 ProcessICSInitScript (FILE *f)
5399 {
5400     char buf[MSG_SIZ];
5401
5402     while (fgets(buf, MSG_SIZ, f)) {
5403         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5404     }
5405
5406     fclose(f);
5407 }
5408
5409
5410 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5411 int dragging;
5412 static ClickType lastClickType;
5413
5414 int
5415 PieceInString (char *s, ChessSquare piece)
5416 {
5417   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5418   while((p = strchr(s, ID))) {
5419     if(!suffix || p[1] == suffix) return TRUE;
5420     s = p;
5421   }
5422   return FALSE;
5423 }
5424
5425 int
5426 Partner (ChessSquare *p)
5427 { // change piece into promotion partner if one shogi-promotes to the other
5428   ChessSquare partner = promoPartner[*p];
5429   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5430   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5431   *p = partner;
5432   return 1;
5433 }
5434
5435 void
5436 Sweep (int step)
5437 {
5438     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5439     static int toggleFlag;
5440     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5441     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5442     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5443     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5444     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5445     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5446     do {
5447         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5448         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5449         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5450         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5451         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5452         if(!step) step = -1;
5453     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5454             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5455             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5456             promoSweep == pawn ||
5457             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5458             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5459     if(toX >= 0) {
5460         int victim = boards[currentMove][toY][toX];
5461         boards[currentMove][toY][toX] = promoSweep;
5462         DrawPosition(FALSE, boards[currentMove]);
5463         boards[currentMove][toY][toX] = victim;
5464     } else
5465     ChangeDragPiece(promoSweep);
5466 }
5467
5468 int
5469 PromoScroll (int x, int y)
5470 {
5471   int step = 0;
5472
5473   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5474   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5475   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5476   if(!step) return FALSE;
5477   lastX = x; lastY = y;
5478   if((promoSweep < BlackPawn) == flipView) step = -step;
5479   if(step > 0) selectFlag = 1;
5480   if(!selectFlag) Sweep(step);
5481   return FALSE;
5482 }
5483
5484 void
5485 NextPiece (int step)
5486 {
5487     ChessSquare piece = boards[currentMove][toY][toX];
5488     do {
5489         pieceSweep -= step;
5490         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5491         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5492         if(!step) step = -1;
5493     } while(PieceToChar(pieceSweep) == '.');
5494     boards[currentMove][toY][toX] = pieceSweep;
5495     DrawPosition(FALSE, boards[currentMove]);
5496     boards[currentMove][toY][toX] = piece;
5497 }
5498 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5499 void
5500 AlphaRank (char *move, int n)
5501 {
5502 //    char *p = move, c; int x, y;
5503
5504     if (appData.debugMode) {
5505         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5506     }
5507
5508     if(move[1]=='*' &&
5509        move[2]>='0' && move[2]<='9' &&
5510        move[3]>='a' && move[3]<='x'    ) {
5511         move[1] = '@';
5512         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5513         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5514     } else
5515     if(move[0]>='0' && move[0]<='9' &&
5516        move[1]>='a' && move[1]<='x' &&
5517        move[2]>='0' && move[2]<='9' &&
5518        move[3]>='a' && move[3]<='x'    ) {
5519         /* input move, Shogi -> normal */
5520         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5521         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5522         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5523         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5524     } else
5525     if(move[1]=='@' &&
5526        move[3]>='0' && move[3]<='9' &&
5527        move[2]>='a' && move[2]<='x'    ) {
5528         move[1] = '*';
5529         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5530         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5531     } else
5532     if(
5533        move[0]>='a' && move[0]<='x' &&
5534        move[3]>='0' && move[3]<='9' &&
5535        move[2]>='a' && move[2]<='x'    ) {
5536          /* output move, normal -> Shogi */
5537         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5538         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5539         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5540         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5541         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5542     }
5543     if (appData.debugMode) {
5544         fprintf(debugFP, "   out = '%s'\n", move);
5545     }
5546 }
5547
5548 char yy_textstr[8000];
5549
5550 /* Parser for moves from gnuchess, ICS, or user typein box */
5551 Boolean
5552 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5553 {
5554     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5555
5556     switch (*moveType) {
5557       case WhitePromotion:
5558       case BlackPromotion:
5559       case WhiteNonPromotion:
5560       case BlackNonPromotion:
5561       case NormalMove:
5562       case FirstLeg:
5563       case WhiteCapturesEnPassant:
5564       case BlackCapturesEnPassant:
5565       case WhiteKingSideCastle:
5566       case WhiteQueenSideCastle:
5567       case BlackKingSideCastle:
5568       case BlackQueenSideCastle:
5569       case WhiteKingSideCastleWild:
5570       case WhiteQueenSideCastleWild:
5571       case BlackKingSideCastleWild:
5572       case BlackQueenSideCastleWild:
5573       /* Code added by Tord: */
5574       case WhiteHSideCastleFR:
5575       case WhiteASideCastleFR:
5576       case BlackHSideCastleFR:
5577       case BlackASideCastleFR:
5578       /* End of code added by Tord */
5579       case IllegalMove:         /* bug or odd chess variant */
5580         if(currentMoveString[1] == '@') { // illegal drop
5581           *fromX = WhiteOnMove(moveNum) ?
5582             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5583             (int) CharToPiece(ToLower(currentMoveString[0]));
5584           goto drop;
5585         }
5586         *fromX = currentMoveString[0] - AAA;
5587         *fromY = currentMoveString[1] - ONE;
5588         *toX = currentMoveString[2] - AAA;
5589         *toY = currentMoveString[3] - ONE;
5590         *promoChar = currentMoveString[4];
5591         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5592         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5593             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5594     if (appData.debugMode) {
5595         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5596     }
5597             *fromX = *fromY = *toX = *toY = 0;
5598             return FALSE;
5599         }
5600         if (appData.testLegality) {
5601           return (*moveType != IllegalMove);
5602         } else {
5603           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5604                          // [HGM] lion: if this is a double move we are less critical
5605                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5606         }
5607
5608       case WhiteDrop:
5609       case BlackDrop:
5610         *fromX = *moveType == WhiteDrop ?
5611           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5612           (int) CharToPiece(ToLower(currentMoveString[0]));
5613       drop:
5614         *fromY = DROP_RANK;
5615         *toX = currentMoveString[2] - AAA;
5616         *toY = currentMoveString[3] - ONE;
5617         *promoChar = NULLCHAR;
5618         return TRUE;
5619
5620       case AmbiguousMove:
5621       case ImpossibleMove:
5622       case EndOfFile:
5623       case ElapsedTime:
5624       case Comment:
5625       case PGNTag:
5626       case NAG:
5627       case WhiteWins:
5628       case BlackWins:
5629       case GameIsDrawn:
5630       default:
5631     if (appData.debugMode) {
5632         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5633     }
5634         /* bug? */
5635         *fromX = *fromY = *toX = *toY = 0;
5636         *promoChar = NULLCHAR;
5637         return FALSE;
5638     }
5639 }
5640
5641 Boolean pushed = FALSE;
5642 char *lastParseAttempt;
5643
5644 void
5645 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5646 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5647   int fromX, fromY, toX, toY; char promoChar;
5648   ChessMove moveType;
5649   Boolean valid;
5650   int nr = 0;
5651
5652   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5653   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5654     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5655     pushed = TRUE;
5656   }
5657   endPV = forwardMostMove;
5658   do {
5659     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5660     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5661     lastParseAttempt = pv;
5662     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5663     if(!valid && nr == 0 &&
5664        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5665         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5666         // Hande case where played move is different from leading PV move
5667         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5668         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5669         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5670         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5671           endPV += 2; // if position different, keep this
5672           moveList[endPV-1][0] = fromX + AAA;
5673           moveList[endPV-1][1] = fromY + ONE;
5674           moveList[endPV-1][2] = toX + AAA;
5675           moveList[endPV-1][3] = toY + ONE;
5676           parseList[endPV-1][0] = NULLCHAR;
5677           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5678         }
5679       }
5680     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5681     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5682     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5683     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5684         valid++; // allow comments in PV
5685         continue;
5686     }
5687     nr++;
5688     if(endPV+1 > framePtr) break; // no space, truncate
5689     if(!valid) break;
5690     endPV++;
5691     CopyBoard(boards[endPV], boards[endPV-1]);
5692     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5693     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5694     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5695     CoordsToAlgebraic(boards[endPV - 1],
5696                              PosFlags(endPV - 1),
5697                              fromY, fromX, toY, toX, promoChar,
5698                              parseList[endPV - 1]);
5699   } while(valid);
5700   if(atEnd == 2) return; // used hidden, for PV conversion
5701   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5702   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5703   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5704                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5705   DrawPosition(TRUE, boards[currentMove]);
5706 }
5707
5708 int
5709 MultiPV (ChessProgramState *cps, int kind)
5710 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5711         int i;
5712         for(i=0; i<cps->nrOptions; i++) {
5713             char *s = cps->option[i].name;
5714             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5715             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5716                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5717         }
5718         return -1;
5719 }
5720
5721 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5722 static int multi, pv_margin;
5723 static ChessProgramState *activeCps;
5724
5725 Boolean
5726 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5727 {
5728         int startPV, lineStart, origIndex = index;
5729         char *p, buf2[MSG_SIZ];
5730         ChessProgramState *cps = (pane ? &second : &first);
5731
5732         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5733         lastX = x; lastY = y;
5734         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5735         lineStart = startPV = index;
5736         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5737         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5738         index = startPV;
5739         do{ while(buf[index] && buf[index] != '\n') index++;
5740         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5741         buf[index] = 0;
5742         if(lineStart == 0 && gameMode == AnalyzeMode) {
5743             int n = 0;
5744             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5745             if(n == 0) { // click not on "fewer" or "more"
5746                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5747                     pv_margin = cps->option[multi].value;
5748                     activeCps = cps; // non-null signals margin adjustment
5749                 }
5750             } else if((multi = MultiPV(cps, 1)) >= 0) {
5751                 n += cps->option[multi].value; if(n < 1) n = 1;
5752                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5753                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5754                 cps->option[multi].value = n;
5755                 *start = *end = 0;
5756                 return FALSE;
5757             }
5758         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5759                 ExcludeClick(origIndex - lineStart);
5760                 return FALSE;
5761         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5762                 Collapse(origIndex - lineStart);
5763                 return FALSE;
5764         }
5765         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5766         *start = startPV; *end = index-1;
5767         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5768         return TRUE;
5769 }
5770
5771 char *
5772 PvToSAN (char *pv)
5773 {
5774         static char buf[10*MSG_SIZ];
5775         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5776         *buf = NULLCHAR;
5777         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5778         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5779         for(i = forwardMostMove; i<endPV; i++){
5780             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5781             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5782             k += strlen(buf+k);
5783         }
5784         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5785         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5786         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5787         endPV = savedEnd;
5788         return buf;
5789 }
5790
5791 Boolean
5792 LoadPV (int x, int y)
5793 { // called on right mouse click to load PV
5794   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5795   lastX = x; lastY = y;
5796   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5797   extendGame = FALSE;
5798   return TRUE;
5799 }
5800
5801 void
5802 UnLoadPV ()
5803 {
5804   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5805   if(activeCps) {
5806     if(pv_margin != activeCps->option[multi].value) {
5807       char buf[MSG_SIZ];
5808       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5809       SendToProgram(buf, activeCps);
5810       activeCps->option[multi].value = pv_margin;
5811     }
5812     activeCps = NULL;
5813     return;
5814   }
5815   if(endPV < 0) return;
5816   if(appData.autoCopyPV) CopyFENToClipboard();
5817   endPV = -1;
5818   if(extendGame && currentMove > forwardMostMove) {
5819         Boolean saveAnimate = appData.animate;
5820         if(pushed) {
5821             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5822                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5823             } else storedGames--; // abandon shelved tail of original game
5824         }
5825         pushed = FALSE;
5826         forwardMostMove = currentMove;
5827         currentMove = oldFMM;
5828         appData.animate = FALSE;
5829         ToNrEvent(forwardMostMove);
5830         appData.animate = saveAnimate;
5831   }
5832   currentMove = forwardMostMove;
5833   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5834   ClearPremoveHighlights();
5835   DrawPosition(TRUE, boards[currentMove]);
5836 }
5837
5838 void
5839 MovePV (int x, int y, int h)
5840 { // step through PV based on mouse coordinates (called on mouse move)
5841   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5842
5843   if(activeCps) { // adjusting engine's multi-pv margin
5844     if(x > lastX) pv_margin++; else
5845     if(x < lastX) pv_margin -= (pv_margin > 0);
5846     if(x != lastX) {
5847       char buf[MSG_SIZ];
5848       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5849       DisplayMessage(buf, "");
5850     }
5851     lastX = x;
5852     return;
5853   }
5854   // we must somehow check if right button is still down (might be released off board!)
5855   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5856   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5857   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5858   if(!step) return;
5859   lastX = x; lastY = y;
5860
5861   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5862   if(endPV < 0) return;
5863   if(y < margin) step = 1; else
5864   if(y > h - margin) step = -1;
5865   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5866   currentMove += step;
5867   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5868   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5869                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5870   DrawPosition(FALSE, boards[currentMove]);
5871 }
5872
5873
5874 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5875 // All positions will have equal probability, but the current method will not provide a unique
5876 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5877 #define DARK 1
5878 #define LITE 2
5879 #define ANY 3
5880
5881 int squaresLeft[4];
5882 int piecesLeft[(int)BlackPawn];
5883 int seed, nrOfShuffles;
5884
5885 void
5886 GetPositionNumber ()
5887 {       // sets global variable seed
5888         int i;
5889
5890         seed = appData.defaultFrcPosition;
5891         if(seed < 0) { // randomize based on time for negative FRC position numbers
5892                 for(i=0; i<50; i++) seed += random();
5893                 seed = random() ^ random() >> 8 ^ random() << 8;
5894                 if(seed<0) seed = -seed;
5895         }
5896 }
5897
5898 int
5899 put (Board board, int pieceType, int rank, int n, int shade)
5900 // put the piece on the (n-1)-th empty squares of the given shade
5901 {
5902         int i;
5903
5904         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5905                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5906                         board[rank][i] = (ChessSquare) pieceType;
5907                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5908                         squaresLeft[ANY]--;
5909                         piecesLeft[pieceType]--;
5910                         return i;
5911                 }
5912         }
5913         return -1;
5914 }
5915
5916
5917 void
5918 AddOnePiece (Board board, int pieceType, int rank, int shade)
5919 // calculate where the next piece goes, (any empty square), and put it there
5920 {
5921         int i;
5922
5923         i = seed % squaresLeft[shade];
5924         nrOfShuffles *= squaresLeft[shade];
5925         seed /= squaresLeft[shade];
5926         put(board, pieceType, rank, i, shade);
5927 }
5928
5929 void
5930 AddTwoPieces (Board board, int pieceType, int rank)
5931 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5932 {
5933         int i, n=squaresLeft[ANY], j=n-1, k;
5934
5935         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5936         i = seed % k;  // pick one
5937         nrOfShuffles *= k;
5938         seed /= k;
5939         while(i >= j) i -= j--;
5940         j = n - 1 - j; i += j;
5941         put(board, pieceType, rank, j, ANY);
5942         put(board, pieceType, rank, i, ANY);
5943 }
5944
5945 void
5946 SetUpShuffle (Board board, int number)
5947 {
5948         int i, p, first=1;
5949
5950         GetPositionNumber(); nrOfShuffles = 1;
5951
5952         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5953         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5954         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5955
5956         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5957
5958         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5959             p = (int) board[0][i];
5960             if(p < (int) BlackPawn) piecesLeft[p] ++;
5961             board[0][i] = EmptySquare;
5962         }
5963
5964         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5965             // shuffles restricted to allow normal castling put KRR first
5966             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5967                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5968             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5969                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5970             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5971                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5972             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5973                 put(board, WhiteRook, 0, 0, ANY);
5974             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5975         }
5976
5977         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5978             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5979             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5980                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5981                 while(piecesLeft[p] >= 2) {
5982                     AddOnePiece(board, p, 0, LITE);
5983                     AddOnePiece(board, p, 0, DARK);
5984                 }
5985                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5986             }
5987
5988         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5989             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5990             // but we leave King and Rooks for last, to possibly obey FRC restriction
5991             if(p == (int)WhiteRook) continue;
5992             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5993             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5994         }
5995
5996         // now everything is placed, except perhaps King (Unicorn) and Rooks
5997
5998         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5999             // Last King gets castling rights
6000             while(piecesLeft[(int)WhiteUnicorn]) {
6001                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6002                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6003             }
6004
6005             while(piecesLeft[(int)WhiteKing]) {
6006                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6007                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6008             }
6009
6010
6011         } else {
6012             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6013             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6014         }
6015
6016         // Only Rooks can be left; simply place them all
6017         while(piecesLeft[(int)WhiteRook]) {
6018                 i = put(board, WhiteRook, 0, 0, ANY);
6019                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6020                         if(first) {
6021                                 first=0;
6022                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6023                         }
6024                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6025                 }
6026         }
6027         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6028             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6029         }
6030
6031         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6032 }
6033
6034 int
6035 ptclen (const char *s, char *escapes)
6036 {
6037     int n = 0;
6038     if(!*escapes) return strlen(s);
6039     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6040     return n;
6041 }
6042
6043 int
6044 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6045 /* [HGM] moved here from winboard.c because of its general usefulness */
6046 /*       Basically a safe strcpy that uses the last character as King */
6047 {
6048     int result = FALSE; int NrPieces;
6049     unsigned char partner[EmptySquare];
6050
6051     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6052                     && NrPieces >= 12 && !(NrPieces&1)) {
6053         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6054
6055         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6056         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6057             char *p, c=0;
6058             if(map[j] == '/') offs = WhitePBishop - i, j++;
6059             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6060             table[i+offs] = map[j++];
6061             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6062             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6063             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6064         }
6065         table[(int) WhiteKing]  = map[j++];
6066         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6067             char *p, c=0;
6068             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6069             i = WHITE_TO_BLACK ii;
6070             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6071             table[i+offs] = map[j++];
6072             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6073             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6074             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6075         }
6076         table[(int) BlackKing]  = map[j++];
6077
6078
6079         if(*escapes) { // set up promotion pairing
6080             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6081             // pieceToChar entirely filled, so we can look up specified partners
6082             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6083                 int c = table[i];
6084                 if(c == '^' || c == '-') { // has specified partner
6085                     int p;
6086                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6087                     if(c == '^') table[i] = '+';
6088                     if(p < EmptySquare) {
6089                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6090                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6091                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6092                     }
6093                 } else if(c == '*') {
6094                     table[i] = partner[i];
6095                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6096                 }
6097             }
6098         }
6099
6100         result = TRUE;
6101     }
6102
6103     return result;
6104 }
6105
6106 int
6107 SetCharTable (unsigned char *table, const char * map)
6108 {
6109     return SetCharTableEsc(table, map, "");
6110 }
6111
6112 void
6113 Prelude (Board board)
6114 {       // [HGM] superchess: random selection of exo-pieces
6115         int i, j, k; ChessSquare p;
6116         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6117
6118         GetPositionNumber(); // use FRC position number
6119
6120         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6121             SetCharTable(pieceToChar, appData.pieceToCharTable);
6122             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6123                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6124         }
6125
6126         j = seed%4;                 seed /= 4;
6127         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6128         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6129         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6130         j = seed%3 + (seed%3 >= j); seed /= 3;
6131         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6132         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6133         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6134         j = seed%3;                 seed /= 3;
6135         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6136         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6137         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6138         j = seed%2 + (seed%2 >= j); seed /= 2;
6139         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6140         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6141         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6142         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6143         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6144         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6145         put(board, exoPieces[0],    0, 0, ANY);
6146         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6147 }
6148
6149 void
6150 InitPosition (int redraw)
6151 {
6152     ChessSquare (* pieces)[BOARD_FILES];
6153     int i, j, pawnRow=1, pieceRows=1, overrule,
6154     oldx = gameInfo.boardWidth,
6155     oldy = gameInfo.boardHeight,
6156     oldh = gameInfo.holdingsWidth;
6157     static int oldv;
6158
6159     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6160
6161     /* [AS] Initialize pv info list [HGM] and game status */
6162     {
6163         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6164             pvInfoList[i].depth = 0;
6165             boards[i][EP_STATUS] = EP_NONE;
6166             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6167         }
6168
6169         initialRulePlies = 0; /* 50-move counter start */
6170
6171         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6172         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6173     }
6174
6175
6176     /* [HGM] logic here is completely changed. In stead of full positions */
6177     /* the initialized data only consist of the two backranks. The switch */
6178     /* selects which one we will use, which is than copied to the Board   */
6179     /* initialPosition, which for the rest is initialized by Pawns and    */
6180     /* empty squares. This initial position is then copied to boards[0],  */
6181     /* possibly after shuffling, so that it remains available.            */
6182
6183     gameInfo.holdingsWidth = 0; /* default board sizes */
6184     gameInfo.boardWidth    = 8;
6185     gameInfo.boardHeight   = 8;
6186     gameInfo.holdingsSize  = 0;
6187     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6188     for(i=0; i<BOARD_FILES-6; i++)
6189       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6190     initialPosition[EP_STATUS] = EP_NONE;
6191     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6192     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6193     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6194          SetCharTable(pieceNickName, appData.pieceNickNames);
6195     else SetCharTable(pieceNickName, "............");
6196     pieces = FIDEArray;
6197
6198     switch (gameInfo.variant) {
6199     case VariantFischeRandom:
6200       shuffleOpenings = TRUE;
6201       appData.fischerCastling = TRUE;
6202     default:
6203       break;
6204     case VariantShatranj:
6205       pieces = ShatranjArray;
6206       nrCastlingRights = 0;
6207       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6208       break;
6209     case VariantMakruk:
6210       pieces = makrukArray;
6211       nrCastlingRights = 0;
6212       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6213       break;
6214     case VariantASEAN:
6215       pieces = aseanArray;
6216       nrCastlingRights = 0;
6217       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6218       break;
6219     case VariantTwoKings:
6220       pieces = twoKingsArray;
6221       break;
6222     case VariantGrand:
6223       pieces = GrandArray;
6224       nrCastlingRights = 0;
6225       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6226       gameInfo.boardWidth = 10;
6227       gameInfo.boardHeight = 10;
6228       gameInfo.holdingsSize = 7;
6229       break;
6230     case VariantCapaRandom:
6231       shuffleOpenings = TRUE;
6232       appData.fischerCastling = TRUE;
6233     case VariantCapablanca:
6234       pieces = CapablancaArray;
6235       gameInfo.boardWidth = 10;
6236       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6237       break;
6238     case VariantGothic:
6239       pieces = GothicArray;
6240       gameInfo.boardWidth = 10;
6241       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6242       break;
6243     case VariantSChess:
6244       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6245       gameInfo.holdingsSize = 7;
6246       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6247       break;
6248     case VariantJanus:
6249       pieces = JanusArray;
6250       gameInfo.boardWidth = 10;
6251       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6252       nrCastlingRights = 6;
6253         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6254         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6255         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6256         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6257         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6258         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6259       break;
6260     case VariantFalcon:
6261       pieces = FalconArray;
6262       gameInfo.boardWidth = 10;
6263       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6264       break;
6265     case VariantXiangqi:
6266       pieces = XiangqiArray;
6267       gameInfo.boardWidth  = 9;
6268       gameInfo.boardHeight = 10;
6269       nrCastlingRights = 0;
6270       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6271       break;
6272     case VariantShogi:
6273       pieces = ShogiArray;
6274       gameInfo.boardWidth  = 9;
6275       gameInfo.boardHeight = 9;
6276       gameInfo.holdingsSize = 7;
6277       nrCastlingRights = 0;
6278       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6279       break;
6280     case VariantChu:
6281       pieces = ChuArray; pieceRows = 3;
6282       gameInfo.boardWidth  = 12;
6283       gameInfo.boardHeight = 12;
6284       nrCastlingRights = 0;
6285 //      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6286   //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6287       SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
6288                                    "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
6289       break;
6290     case VariantCourier:
6291       pieces = CourierArray;
6292       gameInfo.boardWidth  = 12;
6293       nrCastlingRights = 0;
6294       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6295       break;
6296     case VariantKnightmate:
6297       pieces = KnightmateArray;
6298       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6299       break;
6300     case VariantSpartan:
6301       pieces = SpartanArray;
6302       SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6303       break;
6304     case VariantLion:
6305       pieces = lionArray;
6306       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6307       break;
6308     case VariantChuChess:
6309       pieces = ChuChessArray;
6310       gameInfo.boardWidth = 10;
6311       gameInfo.boardHeight = 10;
6312       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6313       break;
6314     case VariantFairy:
6315       pieces = fairyArray;
6316       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6317       break;
6318     case VariantGreat:
6319       pieces = GreatArray;
6320       gameInfo.boardWidth = 10;
6321       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6322       gameInfo.holdingsSize = 8;
6323       break;
6324     case VariantSuper:
6325       pieces = FIDEArray;
6326       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6327       gameInfo.holdingsSize = 8;
6328       startedFromSetupPosition = TRUE;
6329       break;
6330     case VariantCrazyhouse:
6331     case VariantBughouse:
6332       pieces = FIDEArray;
6333       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6334       gameInfo.holdingsSize = 5;
6335       break;
6336     case VariantWildCastle:
6337       pieces = FIDEArray;
6338       /* !!?shuffle with kings guaranteed to be on d or e file */
6339       shuffleOpenings = 1;
6340       break;
6341     case VariantNoCastle:
6342       pieces = FIDEArray;
6343       nrCastlingRights = 0;
6344       /* !!?unconstrained back-rank shuffle */
6345       shuffleOpenings = 1;
6346       break;
6347     }
6348
6349     overrule = 0;
6350     if(appData.NrFiles >= 0) {
6351         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6352         gameInfo.boardWidth = appData.NrFiles;
6353     }
6354     if(appData.NrRanks >= 0) {
6355         gameInfo.boardHeight = appData.NrRanks;
6356     }
6357     if(appData.holdingsSize >= 0) {
6358         i = appData.holdingsSize;
6359         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6360         gameInfo.holdingsSize = i;
6361     }
6362     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6363     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6364         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6365
6366     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6367     if(pawnRow < 1) pawnRow = 1;
6368     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6369        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6370     if(gameInfo.variant == VariantChu) pawnRow = 3;
6371
6372     /* User pieceToChar list overrules defaults */
6373     if(appData.pieceToCharTable != NULL)
6374         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6375
6376     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6377
6378         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6379             s = (ChessSquare) 0; /* account holding counts in guard band */
6380         for( i=0; i<BOARD_HEIGHT; i++ )
6381             initialPosition[i][j] = s;
6382
6383         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6384         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6385         initialPosition[pawnRow][j] = WhitePawn;
6386         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6387         if(gameInfo.variant == VariantXiangqi) {
6388             if(j&1) {
6389                 initialPosition[pawnRow][j] =
6390                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6391                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6392                    initialPosition[2][j] = WhiteCannon;
6393                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6394                 }
6395             }
6396         }
6397         if(gameInfo.variant == VariantChu) {
6398              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6399                initialPosition[pawnRow+1][j] = WhiteCobra,
6400                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6401              for(i=1; i<pieceRows; i++) {
6402                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6403                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6404              }
6405         }
6406         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6407             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6408                initialPosition[0][j] = WhiteRook;
6409                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6410             }
6411         }
6412         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6413     }
6414     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6415     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6416
6417             j=BOARD_LEFT+1;
6418             initialPosition[1][j] = WhiteBishop;
6419             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6420             j=BOARD_RGHT-2;
6421             initialPosition[1][j] = WhiteRook;
6422             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6423     }
6424
6425     if( nrCastlingRights == -1) {
6426         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6427         /*       This sets default castling rights from none to normal corners   */
6428         /* Variants with other castling rights must set them themselves above    */
6429         nrCastlingRights = 6;
6430
6431         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6432         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6433         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6434         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6435         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6436         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6437      }
6438
6439      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6440      if(gameInfo.variant == VariantGreat) { // promotion commoners
6441         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6442         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6443         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6444         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6445      }
6446      if( gameInfo.variant == VariantSChess ) {
6447       initialPosition[1][0] = BlackMarshall;
6448       initialPosition[2][0] = BlackAngel;
6449       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6450       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6451       initialPosition[1][1] = initialPosition[2][1] =
6452       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6453      }
6454      initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6455   if (appData.debugMode) {
6456     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6457   }
6458     if(shuffleOpenings) {
6459         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6460         startedFromSetupPosition = TRUE;
6461     }
6462     if(startedFromPositionFile) {
6463       /* [HGM] loadPos: use PositionFile for every new game */
6464       CopyBoard(initialPosition, filePosition);
6465       for(i=0; i<nrCastlingRights; i++)
6466           initialRights[i] = filePosition[CASTLING][i];
6467       startedFromSetupPosition = TRUE;
6468     }
6469     if(*appData.men) LoadPieceDesc(appData.men);
6470
6471     CopyBoard(boards[0], initialPosition);
6472
6473     if(oldx != gameInfo.boardWidth ||
6474        oldy != gameInfo.boardHeight ||
6475        oldv != gameInfo.variant ||
6476        oldh != gameInfo.holdingsWidth
6477                                          )
6478             InitDrawingSizes(-2 ,0);
6479
6480     oldv = gameInfo.variant;
6481     if (redraw)
6482       DrawPosition(TRUE, boards[currentMove]);
6483 }
6484
6485 void
6486 SendBoard (ChessProgramState *cps, int moveNum)
6487 {
6488     char message[MSG_SIZ];
6489
6490     if (cps->useSetboard) {
6491       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6492       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6493       SendToProgram(message, cps);
6494       free(fen);
6495
6496     } else {
6497       ChessSquare *bp;
6498       int i, j, left=0, right=BOARD_WIDTH;
6499       /* Kludge to set black to move, avoiding the troublesome and now
6500        * deprecated "black" command.
6501        */
6502       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6503         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6504
6505       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6506
6507       SendToProgram("edit\n", cps);
6508       SendToProgram("#\n", cps);
6509       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6510         bp = &boards[moveNum][i][left];
6511         for (j = left; j < right; j++, bp++) {
6512           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6513           if ((int) *bp < (int) BlackPawn) {
6514             if(j == BOARD_RGHT+1)
6515                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6516             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6517             if(message[0] == '+' || message[0] == '~') {
6518               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6519                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6520                         AAA + j, ONE + i - '0');
6521             }
6522             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6523                 message[1] = BOARD_RGHT   - 1 - j + '1';
6524                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6525             }
6526             SendToProgram(message, cps);
6527           }
6528         }
6529       }
6530
6531       SendToProgram("c\n", cps);
6532       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6533         bp = &boards[moveNum][i][left];
6534         for (j = left; j < right; j++, bp++) {
6535           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6536           if (((int) *bp != (int) EmptySquare)
6537               && ((int) *bp >= (int) BlackPawn)) {
6538             if(j == BOARD_LEFT-2)
6539                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6540             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6541                     AAA + j, ONE + i - '0');
6542             if(message[0] == '+' || message[0] == '~') {
6543               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6544                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6545                         AAA + j, ONE + i - '0');
6546             }
6547             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6548                 message[1] = BOARD_RGHT   - 1 - j + '1';
6549                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6550             }
6551             SendToProgram(message, cps);
6552           }
6553         }
6554       }
6555
6556       SendToProgram(".\n", cps);
6557     }
6558     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6559 }
6560
6561 char exclusionHeader[MSG_SIZ];
6562 int exCnt, excludePtr;
6563 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6564 static Exclusion excluTab[200];
6565 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6566
6567 static void
6568 WriteMap (int s)
6569 {
6570     int j;
6571     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6572     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6573 }
6574
6575 static void
6576 ClearMap ()
6577 {
6578     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6579     excludePtr = 24; exCnt = 0;
6580     WriteMap(0);
6581 }
6582
6583 static void
6584 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6585 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6586     char buf[2*MOVE_LEN], *p;
6587     Exclusion *e = excluTab;
6588     int i;
6589     for(i=0; i<exCnt; i++)
6590         if(e[i].ff == fromX && e[i].fr == fromY &&
6591            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6592     if(i == exCnt) { // was not in exclude list; add it
6593         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6594         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6595             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6596             return; // abort
6597         }
6598         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6599         excludePtr++; e[i].mark = excludePtr++;
6600         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6601         exCnt++;
6602     }
6603     exclusionHeader[e[i].mark] = state;
6604 }
6605
6606 static int
6607 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6608 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6609     char buf[MSG_SIZ];
6610     int j, k;
6611     ChessMove moveType;
6612     if((signed char)promoChar == -1) { // kludge to indicate best move
6613         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6614             return 1; // if unparsable, abort
6615     }
6616     // update exclusion map (resolving toggle by consulting existing state)
6617     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6618     j = k%8; k >>= 3;
6619     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6620     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6621          excludeMap[k] |=   1<<j;
6622     else excludeMap[k] &= ~(1<<j);
6623     // update header
6624     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6625     // inform engine
6626     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6627     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6628     SendToBoth(buf);
6629     return (state == '+');
6630 }
6631
6632 static void
6633 ExcludeClick (int index)
6634 {
6635     int i, j;
6636     Exclusion *e = excluTab;
6637     if(index < 25) { // none, best or tail clicked
6638         if(index < 13) { // none: include all
6639             WriteMap(0); // clear map
6640             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6641             SendToBoth("include all\n"); // and inform engine
6642         } else if(index > 18) { // tail
6643             if(exclusionHeader[19] == '-') { // tail was excluded
6644                 SendToBoth("include all\n");
6645                 WriteMap(0); // clear map completely
6646                 // now re-exclude selected moves
6647                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6648                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6649             } else { // tail was included or in mixed state
6650                 SendToBoth("exclude all\n");
6651                 WriteMap(0xFF); // fill map completely
6652                 // now re-include selected moves
6653                 j = 0; // count them
6654                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6655                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6656                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6657             }
6658         } else { // best
6659             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6660         }
6661     } else {
6662         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6663             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6664             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6665             break;
6666         }
6667     }
6668 }
6669
6670 ChessSquare
6671 DefaultPromoChoice (int white)
6672 {
6673     ChessSquare result;
6674     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6675        gameInfo.variant == VariantMakruk)
6676         result = WhiteFerz; // no choice
6677     else if(gameInfo.variant == VariantASEAN)
6678         result = WhiteRook; // no choice
6679     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6680         result= WhiteKing; // in Suicide Q is the last thing we want
6681     else if(gameInfo.variant == VariantSpartan)
6682         result = white ? WhiteQueen : WhiteAngel;
6683     else result = WhiteQueen;
6684     if(!white) result = WHITE_TO_BLACK result;
6685     return result;
6686 }
6687
6688 static int autoQueen; // [HGM] oneclick
6689
6690 int
6691 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6692 {
6693     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6694     /* [HGM] add Shogi promotions */
6695     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6696     ChessSquare piece, partner;
6697     ChessMove moveType;
6698     Boolean premove;
6699
6700     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6701     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6702
6703     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6704       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6705         return FALSE;
6706
6707     if(legal[toY][toX] == 4) return FALSE;
6708
6709     piece = boards[currentMove][fromY][fromX];
6710     if(gameInfo.variant == VariantChu) {
6711         promotionZoneSize = BOARD_HEIGHT/3;
6712         if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6713         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6714     } else if(gameInfo.variant == VariantShogi) {
6715         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6716         highestPromotingPiece = (int)WhiteAlfil;
6717     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6718         promotionZoneSize = 3;
6719     }
6720
6721     // Treat Lance as Pawn when it is not representing Amazon or Lance
6722     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6723         if(piece == WhiteLance) piece = WhitePawn; else
6724         if(piece == BlackLance) piece = BlackPawn;
6725     }
6726
6727     // next weed out all moves that do not touch the promotion zone at all
6728     if((int)piece >= BlackPawn) {
6729         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6730              return FALSE;
6731         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6732         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6733     } else {
6734         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6735            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6736         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6737              return FALSE;
6738     }
6739
6740     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6741
6742     // weed out mandatory Shogi promotions
6743     if(gameInfo.variant == VariantShogi) {
6744         if(piece >= BlackPawn) {
6745             if(toY == 0 && piece == BlackPawn ||
6746                toY == 0 && piece == BlackQueen ||
6747                toY <= 1 && piece == BlackKnight) {
6748                 *promoChoice = '+';
6749                 return FALSE;
6750             }
6751         } else {
6752             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6753                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6754                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6755                 *promoChoice = '+';
6756                 return FALSE;
6757             }
6758         }
6759     }
6760
6761     // weed out obviously illegal Pawn moves
6762     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6763         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6764         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6765         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6766         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6767         // note we are not allowed to test for valid (non-)capture, due to premove
6768     }
6769
6770     // we either have a choice what to promote to, or (in Shogi) whether to promote
6771     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6772        gameInfo.variant == VariantMakruk) {
6773         ChessSquare p=BlackFerz;  // no choice
6774         while(p < EmptySquare) {  //but make sure we use piece that exists
6775             *promoChoice = PieceToChar(p++);
6776             if(*promoChoice != '.') break;
6777         }
6778         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6779     }
6780     // no sense asking what we must promote to if it is going to explode...
6781     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6782         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6783         return FALSE;
6784     }
6785     // give caller the default choice even if we will not make it
6786     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6787     partner = piece; // pieces can promote if the pieceToCharTable says so
6788     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6789     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6790     if(        sweepSelect && gameInfo.variant != VariantGreat
6791                            && gameInfo.variant != VariantGrand
6792                            && gameInfo.variant != VariantSuper) return FALSE;
6793     if(autoQueen) return FALSE; // predetermined
6794
6795     // suppress promotion popup on illegal moves that are not premoves
6796     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6797               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6798     if(appData.testLegality && !premove) {
6799         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6800                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6801         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6802         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6803             return FALSE;
6804     }
6805
6806     return TRUE;
6807 }
6808
6809 int
6810 InPalace (int row, int column)
6811 {   /* [HGM] for Xiangqi */
6812     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6813          column < (BOARD_WIDTH + 4)/2 &&
6814          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6815     return FALSE;
6816 }
6817
6818 int
6819 PieceForSquare (int x, int y)
6820 {
6821   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6822      return -1;
6823   else
6824      return boards[currentMove][y][x];
6825 }
6826
6827 int
6828 OKToStartUserMove (int x, int y)
6829 {
6830     ChessSquare from_piece;
6831     int white_piece;
6832
6833     if (matchMode) return FALSE;
6834     if (gameMode == EditPosition) return TRUE;
6835
6836     if (x >= 0 && y >= 0)
6837       from_piece = boards[currentMove][y][x];
6838     else
6839       from_piece = EmptySquare;
6840
6841     if (from_piece == EmptySquare) return FALSE;
6842
6843     white_piece = (int)from_piece >= (int)WhitePawn &&
6844       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6845
6846     switch (gameMode) {
6847       case AnalyzeFile:
6848       case TwoMachinesPlay:
6849       case EndOfGame:
6850         return FALSE;
6851
6852       case IcsObserving:
6853       case IcsIdle:
6854         return FALSE;
6855
6856       case MachinePlaysWhite:
6857       case IcsPlayingBlack:
6858         if (appData.zippyPlay) return FALSE;
6859         if (white_piece) {
6860             DisplayMoveError(_("You are playing Black"));
6861             return FALSE;
6862         }
6863         break;
6864
6865       case MachinePlaysBlack:
6866       case IcsPlayingWhite:
6867         if (appData.zippyPlay) return FALSE;
6868         if (!white_piece) {
6869             DisplayMoveError(_("You are playing White"));
6870             return FALSE;
6871         }
6872         break;
6873
6874       case PlayFromGameFile:
6875             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6876       case EditGame:
6877       case AnalyzeMode:
6878         if (!white_piece && WhiteOnMove(currentMove)) {
6879             DisplayMoveError(_("It is White's turn"));
6880             return FALSE;
6881         }
6882         if (white_piece && !WhiteOnMove(currentMove)) {
6883             DisplayMoveError(_("It is Black's turn"));
6884             return FALSE;
6885         }
6886         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6887             /* Editing correspondence game history */
6888             /* Could disallow this or prompt for confirmation */
6889             cmailOldMove = -1;
6890         }
6891         break;
6892
6893       case BeginningOfGame:
6894         if (appData.icsActive) return FALSE;
6895         if (!appData.noChessProgram) {
6896             if (!white_piece) {
6897                 DisplayMoveError(_("You are playing White"));
6898                 return FALSE;
6899             }
6900         }
6901         break;
6902
6903       case Training:
6904         if (!white_piece && WhiteOnMove(currentMove)) {
6905             DisplayMoveError(_("It is White's turn"));
6906             return FALSE;
6907         }
6908         if (white_piece && !WhiteOnMove(currentMove)) {
6909             DisplayMoveError(_("It is Black's turn"));
6910             return FALSE;
6911         }
6912         break;
6913
6914       default:
6915       case IcsExamining:
6916         break;
6917     }
6918     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6919         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6920         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6921         && gameMode != AnalyzeFile && gameMode != Training) {
6922         DisplayMoveError(_("Displayed position is not current"));
6923         return FALSE;
6924     }
6925     return TRUE;
6926 }
6927
6928 Boolean
6929 OnlyMove (int *x, int *y, Boolean captures)
6930 {
6931     DisambiguateClosure cl;
6932     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6933     switch(gameMode) {
6934       case MachinePlaysBlack:
6935       case IcsPlayingWhite:
6936       case BeginningOfGame:
6937         if(!WhiteOnMove(currentMove)) return FALSE;
6938         break;
6939       case MachinePlaysWhite:
6940       case IcsPlayingBlack:
6941         if(WhiteOnMove(currentMove)) return FALSE;
6942         break;
6943       case EditGame:
6944         break;
6945       default:
6946         return FALSE;
6947     }
6948     cl.pieceIn = EmptySquare;
6949     cl.rfIn = *y;
6950     cl.ffIn = *x;
6951     cl.rtIn = -1;
6952     cl.ftIn = -1;
6953     cl.promoCharIn = NULLCHAR;
6954     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6955     if( cl.kind == NormalMove ||
6956         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6957         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6958         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6959       fromX = cl.ff;
6960       fromY = cl.rf;
6961       *x = cl.ft;
6962       *y = cl.rt;
6963       return TRUE;
6964     }
6965     if(cl.kind != ImpossibleMove) return FALSE;
6966     cl.pieceIn = EmptySquare;
6967     cl.rfIn = -1;
6968     cl.ffIn = -1;
6969     cl.rtIn = *y;
6970     cl.ftIn = *x;
6971     cl.promoCharIn = NULLCHAR;
6972     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6973     if( cl.kind == NormalMove ||
6974         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6975         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6976         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6977       fromX = cl.ff;
6978       fromY = cl.rf;
6979       *x = cl.ft;
6980       *y = cl.rt;
6981       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6982       return TRUE;
6983     }
6984     return FALSE;
6985 }
6986
6987 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6988 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6989 int lastLoadGameUseList = FALSE;
6990 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6991 ChessMove lastLoadGameStart = EndOfFile;
6992 int doubleClick;
6993 Boolean addToBookFlag;
6994 static Board rightsBoard, nullBoard;
6995
6996 void
6997 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6998 {
6999     ChessMove moveType;
7000     ChessSquare pup;
7001     int ff=fromX, rf=fromY, ft=toX, rt=toY;
7002
7003     /* Check if the user is playing in turn.  This is complicated because we
7004        let the user "pick up" a piece before it is his turn.  So the piece he
7005        tried to pick up may have been captured by the time he puts it down!
7006        Therefore we use the color the user is supposed to be playing in this
7007        test, not the color of the piece that is currently on the starting
7008        square---except in EditGame mode, where the user is playing both
7009        sides; fortunately there the capture race can't happen.  (It can
7010        now happen in IcsExamining mode, but that's just too bad.  The user
7011        will get a somewhat confusing message in that case.)
7012        */
7013
7014     switch (gameMode) {
7015       case AnalyzeFile:
7016       case TwoMachinesPlay:
7017       case EndOfGame:
7018       case IcsObserving:
7019       case IcsIdle:
7020         /* We switched into a game mode where moves are not accepted,
7021            perhaps while the mouse button was down. */
7022         return;
7023
7024       case MachinePlaysWhite:
7025         /* User is moving for Black */
7026         if (WhiteOnMove(currentMove)) {
7027             DisplayMoveError(_("It is White's turn"));
7028             return;
7029         }
7030         break;
7031
7032       case MachinePlaysBlack:
7033         /* User is moving for White */
7034         if (!WhiteOnMove(currentMove)) {
7035             DisplayMoveError(_("It is Black's turn"));
7036             return;
7037         }
7038         break;
7039
7040       case PlayFromGameFile:
7041             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7042       case EditGame:
7043       case IcsExamining:
7044       case BeginningOfGame:
7045       case AnalyzeMode:
7046       case Training:
7047         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7048         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7049             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7050             /* User is moving for Black */
7051             if (WhiteOnMove(currentMove)) {
7052                 DisplayMoveError(_("It is White's turn"));
7053                 return;
7054             }
7055         } else {
7056             /* User is moving for White */
7057             if (!WhiteOnMove(currentMove)) {
7058                 DisplayMoveError(_("It is Black's turn"));
7059                 return;
7060             }
7061         }
7062         break;
7063
7064       case IcsPlayingBlack:
7065         /* User is moving for Black */
7066         if (WhiteOnMove(currentMove)) {
7067             if (!appData.premove) {
7068                 DisplayMoveError(_("It is White's turn"));
7069             } else if (toX >= 0 && toY >= 0) {
7070                 premoveToX = toX;
7071                 premoveToY = toY;
7072                 premoveFromX = fromX;
7073                 premoveFromY = fromY;
7074                 premovePromoChar = promoChar;
7075                 gotPremove = 1;
7076                 if (appData.debugMode)
7077                     fprintf(debugFP, "Got premove: fromX %d,"
7078                             "fromY %d, toX %d, toY %d\n",
7079                             fromX, fromY, toX, toY);
7080             }
7081             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7082             return;
7083         }
7084         break;
7085
7086       case IcsPlayingWhite:
7087         /* User is moving for White */
7088         if (!WhiteOnMove(currentMove)) {
7089             if (!appData.premove) {
7090                 DisplayMoveError(_("It is Black's turn"));
7091             } else if (toX >= 0 && toY >= 0) {
7092                 premoveToX = toX;
7093                 premoveToY = toY;
7094                 premoveFromX = fromX;
7095                 premoveFromY = fromY;
7096                 premovePromoChar = promoChar;
7097                 gotPremove = 1;
7098                 if (appData.debugMode)
7099                     fprintf(debugFP, "Got premove: fromX %d,"
7100                             "fromY %d, toX %d, toY %d\n",
7101                             fromX, fromY, toX, toY);
7102             }
7103             DrawPosition(TRUE, boards[currentMove]);
7104             return;
7105         }
7106         break;
7107
7108       default:
7109         break;
7110
7111       case EditPosition:
7112         /* EditPosition, empty square, or different color piece;
7113            click-click move is possible */
7114         if (toX == -2 || toY == -2) {
7115             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7116             DrawPosition(FALSE, boards[currentMove]);
7117             return;
7118         } else if (toX >= 0 && toY >= 0) {
7119             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7120                 ChessSquare p = boards[0][rf][ff];
7121                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7122                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7123                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7124                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7125                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7126                     gatingPiece = p;
7127                 }
7128             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7129             boards[0][toY][toX] = boards[0][fromY][fromX];
7130             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7131                 if(boards[0][fromY][0] != EmptySquare) {
7132                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7133                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7134                 }
7135             } else
7136             if(fromX == BOARD_RGHT+1) {
7137                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7138                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7139                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7140                 }
7141             } else
7142             boards[0][fromY][fromX] = gatingPiece;
7143             ClearHighlights();
7144             DrawPosition(FALSE, boards[currentMove]);
7145             return;
7146         }
7147         return;
7148     }
7149
7150     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7151     pup = boards[currentMove][toY][toX];
7152
7153     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7154     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7155          if( pup != EmptySquare ) return;
7156          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7157            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7158                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7159            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7160            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7161            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7162            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7163          fromY = DROP_RANK;
7164     }
7165
7166     /* [HGM] always test for legality, to get promotion info */
7167     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7168                                          fromY, fromX, toY, toX, promoChar);
7169
7170     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7171
7172     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7173
7174     /* [HGM] but possibly ignore an IllegalMove result */
7175     if (appData.testLegality) {
7176         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7177             DisplayMoveError(_("Illegal move"));
7178             return;
7179         }
7180     }
7181
7182     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7183         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7184              ClearPremoveHighlights(); // was included
7185         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7186         DrawPosition(FALSE, NULL);
7187         return;
7188     }
7189
7190     if(addToBookFlag) { // adding moves to book
7191         char buf[MSG_SIZ], move[MSG_SIZ];
7192         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7193         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7194                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7195         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7196         AddBookMove(buf);
7197         addToBookFlag = FALSE;
7198         ClearHighlights();
7199         return;
7200     }
7201
7202     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7203 }
7204
7205 /* Common tail of UserMoveEvent and DropMenuEvent */
7206 int
7207 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7208 {
7209     char *bookHit = 0;
7210
7211     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7212         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7213         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7214         if(WhiteOnMove(currentMove)) {
7215             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7216         } else {
7217             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7218         }
7219     }
7220
7221     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7222        move type in caller when we know the move is a legal promotion */
7223     if(moveType == NormalMove && promoChar)
7224         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7225
7226     /* [HGM] <popupFix> The following if has been moved here from
7227        UserMoveEvent(). Because it seemed to belong here (why not allow
7228        piece drops in training games?), and because it can only be
7229        performed after it is known to what we promote. */
7230     if (gameMode == Training) {
7231       /* compare the move played on the board to the next move in the
7232        * game. If they match, display the move and the opponent's response.
7233        * If they don't match, display an error message.
7234        */
7235       int saveAnimate;
7236       Board testBoard;
7237       CopyBoard(testBoard, boards[currentMove]);
7238       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7239
7240       if (CompareBoards(testBoard, boards[currentMove+1])) {
7241         ForwardInner(currentMove+1);
7242
7243         /* Autoplay the opponent's response.
7244          * if appData.animate was TRUE when Training mode was entered,
7245          * the response will be animated.
7246          */
7247         saveAnimate = appData.animate;
7248         appData.animate = animateTraining;
7249         ForwardInner(currentMove+1);
7250         appData.animate = saveAnimate;
7251
7252         /* check for the end of the game */
7253         if (currentMove >= forwardMostMove) {
7254           gameMode = PlayFromGameFile;
7255           ModeHighlight();
7256           SetTrainingModeOff();
7257           DisplayInformation(_("End of game"));
7258         }
7259       } else {
7260         DisplayError(_("Incorrect move"), 0);
7261       }
7262       return 1;
7263     }
7264
7265   /* Ok, now we know that the move is good, so we can kill
7266      the previous line in Analysis Mode */
7267   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7268                                 && currentMove < forwardMostMove) {
7269     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7270     else forwardMostMove = currentMove;
7271   }
7272
7273   ClearMap();
7274
7275   /* If we need the chess program but it's dead, restart it */
7276   ResurrectChessProgram();
7277
7278   /* A user move restarts a paused game*/
7279   if (pausing)
7280     PauseEvent();
7281
7282   thinkOutput[0] = NULLCHAR;
7283
7284   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7285
7286   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7287     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7288     return 1;
7289   }
7290
7291   if (gameMode == BeginningOfGame) {
7292     if (appData.noChessProgram) {
7293       gameMode = EditGame;
7294       SetGameInfo();
7295     } else {
7296       char buf[MSG_SIZ];
7297       gameMode = MachinePlaysBlack;
7298       StartClocks();
7299       SetGameInfo();
7300       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7301       DisplayTitle(buf);
7302       if (first.sendName) {
7303         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7304         SendToProgram(buf, &first);
7305       }
7306       StartClocks();
7307     }
7308     ModeHighlight();
7309   }
7310
7311   /* Relay move to ICS or chess engine */
7312   if (appData.icsActive) {
7313     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7314         gameMode == IcsExamining) {
7315       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7316         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7317         SendToICS("draw ");
7318         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7319       }
7320       // also send plain move, in case ICS does not understand atomic claims
7321       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7322       ics_user_moved = 1;
7323     }
7324   } else {
7325     if (first.sendTime && (gameMode == BeginningOfGame ||
7326                            gameMode == MachinePlaysWhite ||
7327                            gameMode == MachinePlaysBlack)) {
7328       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7329     }
7330     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7331          // [HGM] book: if program might be playing, let it use book
7332         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7333         first.maybeThinking = TRUE;
7334     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7335         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7336         SendBoard(&first, currentMove+1);
7337         if(second.analyzing) {
7338             if(!second.useSetboard) SendToProgram("undo\n", &second);
7339             SendBoard(&second, currentMove+1);
7340         }
7341     } else {
7342         SendMoveToProgram(forwardMostMove-1, &first);
7343         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7344     }
7345     if (currentMove == cmailOldMove + 1) {
7346       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7347     }
7348   }
7349
7350   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7351
7352   switch (gameMode) {
7353   case EditGame:
7354     if(appData.testLegality)
7355     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7356     case MT_NONE:
7357     case MT_CHECK:
7358       break;
7359     case MT_CHECKMATE:
7360     case MT_STAINMATE:
7361       if (WhiteOnMove(currentMove)) {
7362         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7363       } else {
7364         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7365       }
7366       break;
7367     case MT_STALEMATE:
7368       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7369       break;
7370     }
7371     break;
7372
7373   case MachinePlaysBlack:
7374   case MachinePlaysWhite:
7375     /* disable certain menu options while machine is thinking */
7376     SetMachineThinkingEnables();
7377     break;
7378
7379   default:
7380     break;
7381   }
7382
7383   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7384   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7385
7386   if(bookHit) { // [HGM] book: simulate book reply
7387         static char bookMove[MSG_SIZ]; // a bit generous?
7388
7389         programStats.nodes = programStats.depth = programStats.time =
7390         programStats.score = programStats.got_only_move = 0;
7391         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7392
7393         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7394         strcat(bookMove, bookHit);
7395         HandleMachineMove(bookMove, &first);
7396   }
7397   return 1;
7398 }
7399
7400 void
7401 MarkByFEN(char *fen)
7402 {
7403         int r, f;
7404         if(!appData.markers || !appData.highlightDragging) return;
7405         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7406         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7407         while(*fen) {
7408             int s = 0;
7409             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7410             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7411             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7412             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7413             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7414             if(*fen == 'T') marker[r][f++] = 0; else
7415             if(*fen == 'Y') marker[r][f++] = 1; else
7416             if(*fen == 'G') marker[r][f++] = 3; else
7417             if(*fen == 'B') marker[r][f++] = 4; else
7418             if(*fen == 'C') marker[r][f++] = 5; else
7419             if(*fen == 'M') marker[r][f++] = 6; else
7420             if(*fen == 'W') marker[r][f++] = 7; else
7421             if(*fen == 'D') marker[r][f++] = 8; else
7422             if(*fen == 'R') marker[r][f++] = 2; else {
7423                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7424               f += s; fen -= s>0;
7425             }
7426             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7427             if(r < 0) break;
7428             fen++;
7429         }
7430         DrawPosition(TRUE, NULL);
7431 }
7432
7433 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7434
7435 void
7436 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7437 {
7438     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7439     Markers *m = (Markers *) closure;
7440     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7441                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7442         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7443                          || kind == WhiteCapturesEnPassant
7444                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7445     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7446 }
7447
7448 static int hoverSavedValid;
7449
7450 void
7451 MarkTargetSquares (int clear)
7452 {
7453   int x, y, sum=0;
7454   if(clear) { // no reason to ever suppress clearing
7455     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7456     hoverSavedValid = 0;
7457     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7458   } else {
7459     int capt = 0;
7460     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7461        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7462     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7463     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7464       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7465       if(capt)
7466       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = legal[y][x] = 0;
7467     }
7468   }
7469   DrawPosition(FALSE, NULL);
7470 }
7471
7472 int
7473 Explode (Board board, int fromX, int fromY, int toX, int toY)
7474 {
7475     if(gameInfo.variant == VariantAtomic &&
7476        (board[toY][toX] != EmptySquare ||                     // capture?
7477         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7478                          board[fromY][fromX] == BlackPawn   )
7479       )) {
7480         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7481         return TRUE;
7482     }
7483     return FALSE;
7484 }
7485
7486 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7487
7488 int
7489 CanPromote (ChessSquare piece, int y)
7490 {
7491         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7492         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7493         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7494         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7495            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7496           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7497            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7498         return (piece == BlackPawn && y <= zone ||
7499                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7500                 piece == BlackLance && y <= zone ||
7501                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7502 }
7503
7504 void
7505 HoverEvent (int xPix, int yPix, int x, int y)
7506 {
7507         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7508         int r, f;
7509         if(!first.highlight) return;
7510         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7511         if(x == oldX && y == oldY) return; // only do something if we enter new square
7512         oldFromX = fromX; oldFromY = fromY;
7513         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7514           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7515             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7516           hoverSavedValid = 1;
7517         } else if(oldX != x || oldY != y) {
7518           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7519           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7520           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7521             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7522           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7523             char buf[MSG_SIZ];
7524             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7525             SendToProgram(buf, &first);
7526           }
7527           oldX = x; oldY = y;
7528 //        SetHighlights(fromX, fromY, x, y);
7529         }
7530 }
7531
7532 void ReportClick(char *action, int x, int y)
7533 {
7534         char buf[MSG_SIZ]; // Inform engine of what user does
7535         int r, f;
7536         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7537           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7538             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7539         if(!first.highlight || gameMode == EditPosition) return;
7540         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7541         SendToProgram(buf, &first);
7542 }
7543
7544 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7545 Boolean deferChoice;
7546
7547 void
7548 LeftClick (ClickType clickType, int xPix, int yPix)
7549 {
7550     int x, y;
7551     static Boolean saveAnimate;
7552     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7553     char promoChoice = NULLCHAR;
7554     ChessSquare piece;
7555     static TimeMark lastClickTime, prevClickTime;
7556
7557     if(flashing) return;
7558
7559   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7560     x = EventToSquare(xPix, BOARD_WIDTH);
7561     y = EventToSquare(yPix, BOARD_HEIGHT);
7562     if (!flipView && y >= 0) {
7563         y = BOARD_HEIGHT - 1 - y;
7564     }
7565     if (flipView && x >= 0) {
7566         x = BOARD_WIDTH - 1 - x;
7567     }
7568
7569     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7570         static int dummy;
7571         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7572         right = TRUE;
7573         return;
7574     }
7575
7576     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7577
7578     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7579
7580     if (clickType == Press) ErrorPopDown();
7581     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7582
7583     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7584         defaultPromoChoice = promoSweep;
7585         promoSweep = EmptySquare;   // terminate sweep
7586         promoDefaultAltered = TRUE;
7587         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7588     }
7589
7590     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7591         if(clickType == Release) return; // ignore upclick of click-click destination
7592         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7593         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7594         if(gameInfo.holdingsWidth &&
7595                 (WhiteOnMove(currentMove)
7596                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7597                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7598             // click in right holdings, for determining promotion piece
7599             ChessSquare p = boards[currentMove][y][x];
7600             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7601             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7602             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7603                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7604                 fromX = fromY = -1;
7605                 return;
7606             }
7607         }
7608         DrawPosition(FALSE, boards[currentMove]);
7609         return;
7610     }
7611
7612     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7613     if(clickType == Press
7614             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7615               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7616               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7617         return;
7618
7619     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7620         // could be static click on premove from-square: abort premove
7621         gotPremove = 0;
7622         ClearPremoveHighlights();
7623     }
7624
7625     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7626         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7627
7628     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7629         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7630                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7631         defaultPromoChoice = DefaultPromoChoice(side);
7632     }
7633
7634     autoQueen = appData.alwaysPromoteToQueen;
7635
7636     if (fromX == -1) {
7637       int originalY = y;
7638       gatingPiece = EmptySquare;
7639       if (clickType != Press) {
7640         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7641             DragPieceEnd(xPix, yPix); dragging = 0;
7642             DrawPosition(FALSE, NULL);
7643         }
7644         return;
7645       }
7646       doubleClick = FALSE;
7647       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7648         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7649       }
7650       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7651       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7652          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7653          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7654             /* First square */
7655             if (OKToStartUserMove(fromX, fromY)) {
7656                 second = 0;
7657                 ReportClick("lift", x, y);
7658                 MarkTargetSquares(0);
7659                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7660                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7661                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7662                     promoSweep = defaultPromoChoice;
7663                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7664                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7665                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7666                 }
7667                 if (appData.highlightDragging) {
7668                     SetHighlights(fromX, fromY, -1, -1);
7669                 } else {
7670                     ClearHighlights();
7671                 }
7672             } else fromX = fromY = -1;
7673             return;
7674         }
7675     }
7676
7677     /* fromX != -1 */
7678     if (clickType == Press && gameMode != EditPosition) {
7679         ChessSquare fromP;
7680         ChessSquare toP;
7681         int frc;
7682
7683         // ignore off-board to clicks
7684         if(y < 0 || x < 0) return;
7685
7686         /* Check if clicking again on the same color piece */
7687         fromP = boards[currentMove][fromY][fromX];
7688         toP = boards[currentMove][y][x];
7689         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7690         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7691             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7692            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7693              WhitePawn <= toP && toP <= WhiteKing &&
7694              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7695              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7696             (BlackPawn <= fromP && fromP <= BlackKing &&
7697              BlackPawn <= toP && toP <= BlackKing &&
7698              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7699              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7700             /* Clicked again on same color piece -- changed his mind */
7701             second = (x == fromX && y == fromY);
7702             killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7703             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7704                 second = FALSE; // first double-click rather than scond click
7705                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7706             }
7707             promoDefaultAltered = FALSE;
7708            if(!second) MarkTargetSquares(1);
7709            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7710             if (appData.highlightDragging) {
7711                 SetHighlights(x, y, -1, -1);
7712             } else {
7713                 ClearHighlights();
7714             }
7715             if (OKToStartUserMove(x, y)) {
7716                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7717                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7718                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7719                  gatingPiece = boards[currentMove][fromY][fromX];
7720                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7721                 fromX = x;
7722                 fromY = y; dragging = 1;
7723                 if(!second) ReportClick("lift", x, y);
7724                 MarkTargetSquares(0);
7725                 DragPieceBegin(xPix, yPix, FALSE);
7726                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7727                     promoSweep = defaultPromoChoice;
7728                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7729                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7730                 }
7731             }
7732            }
7733            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7734            second = FALSE;
7735         }
7736         // ignore clicks on holdings
7737         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7738     }
7739
7740     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7741         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7742         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7743         return;
7744     }
7745
7746     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7747         DragPieceEnd(xPix, yPix); dragging = 0;
7748         if(clearFlag) {
7749             // a deferred attempt to click-click move an empty square on top of a piece
7750             boards[currentMove][y][x] = EmptySquare;
7751             ClearHighlights();
7752             DrawPosition(FALSE, boards[currentMove]);
7753             fromX = fromY = -1; clearFlag = 0;
7754             return;
7755         }
7756         if (appData.animateDragging) {
7757             /* Undo animation damage if any */
7758             DrawPosition(FALSE, NULL);
7759         }
7760         if (second) {
7761             /* Second up/down in same square; just abort move */
7762             second = 0;
7763             fromX = fromY = -1;
7764             gatingPiece = EmptySquare;
7765             ClearHighlights();
7766             gotPremove = 0;
7767             ClearPremoveHighlights();
7768             MarkTargetSquares(-1);
7769             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7770         } else {
7771             /* First upclick in same square; start click-click mode */
7772             SetHighlights(x, y, -1, -1);
7773         }
7774         return;
7775     }
7776
7777     clearFlag = 0;
7778
7779     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7780        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7781         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7782         DisplayMessage(_("only marked squares are legal"),"");
7783         DrawPosition(TRUE, NULL);
7784         return; // ignore to-click
7785     }
7786
7787     /* we now have a different from- and (possibly off-board) to-square */
7788     /* Completed move */
7789     if(!sweepSelecting) {
7790         toX = x;
7791         toY = y;
7792     }
7793
7794     piece = boards[currentMove][fromY][fromX];
7795
7796     saveAnimate = appData.animate;
7797     if (clickType == Press) {
7798         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7799         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7800             // must be Edit Position mode with empty-square selected
7801             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7802             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7803             return;
7804         }
7805         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7806             return;
7807         }
7808         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7809             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7810         } else
7811         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7812         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7813           if(appData.sweepSelect) {
7814             promoSweep = defaultPromoChoice;
7815             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7816             selectFlag = 0; lastX = xPix; lastY = yPix;
7817             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7818             saveFlash = appData.flashCount; appData.flashCount = 0;
7819             Sweep(0); // Pawn that is going to promote: preview promotion piece
7820             sweepSelecting = 1;
7821             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7822             MarkTargetSquares(1);
7823           }
7824           return; // promo popup appears on up-click
7825         }
7826         /* Finish clickclick move */
7827         if (appData.animate || appData.highlightLastMove) {
7828             SetHighlights(fromX, fromY, toX, toY);
7829         } else {
7830             ClearHighlights();
7831         }
7832         MarkTargetSquares(1);
7833     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7834         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7835         *promoRestrict = 0; appData.flashCount = saveFlash;
7836         if (appData.animate || appData.highlightLastMove) {
7837             SetHighlights(fromX, fromY, toX, toY);
7838         } else {
7839             ClearHighlights();
7840         }
7841         MarkTargetSquares(1);
7842     } else {
7843 #if 0
7844 // [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
7845         /* Finish drag move */
7846         if (appData.highlightLastMove) {
7847             SetHighlights(fromX, fromY, toX, toY);
7848         } else {
7849             ClearHighlights();
7850         }
7851 #endif
7852         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7853           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7854         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7855         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7856           dragging *= 2;            // flag button-less dragging if we are dragging
7857           MarkTargetSquares(1);
7858           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7859           else {
7860             kill2X = killX; kill2Y = killY;
7861             killX = x; killY = y;     // remember this square as intermediate
7862             ReportClick("put", x, y); // and inform engine
7863             ReportClick("lift", x, y);
7864             MarkTargetSquares(0);
7865             return;
7866           }
7867         }
7868         DragPieceEnd(xPix, yPix); dragging = 0;
7869         /* Don't animate move and drag both */
7870         appData.animate = FALSE;
7871         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7872     }
7873
7874     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7875     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7876         ChessSquare piece = boards[currentMove][fromY][fromX];
7877         if(gameMode == EditPosition && piece != EmptySquare &&
7878            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7879             int n;
7880
7881             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7882                 n = PieceToNumber(piece - (int)BlackPawn);
7883                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7884                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7885                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7886             } else
7887             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7888                 n = PieceToNumber(piece);
7889                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7890                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7891                 boards[currentMove][n][BOARD_WIDTH-2]++;
7892             }
7893             boards[currentMove][fromY][fromX] = EmptySquare;
7894         }
7895         ClearHighlights();
7896         fromX = fromY = -1;
7897         MarkTargetSquares(1);
7898         DrawPosition(TRUE, boards[currentMove]);
7899         return;
7900     }
7901
7902     // off-board moves should not be highlighted
7903     if(x < 0 || y < 0) {
7904         ClearHighlights();
7905         DrawPosition(FALSE, NULL);
7906     } else ReportClick("put", x, y);
7907
7908     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7909  }
7910
7911     if(legal[toY][toX] == 2) { // highlight-induced promotion
7912         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7913         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7914     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7915       if(!*promoRestrict) {           // but has not done that yet
7916         deferChoice = TRUE;           // set up retry for when it does
7917         return;                       // and wait for that
7918       }
7919       promoChoice = ToLower(*promoRestrict); // force engine's choice
7920       deferChoice = FALSE;
7921     }
7922
7923     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7924         SetHighlights(fromX, fromY, toX, toY);
7925         MarkTargetSquares(1);
7926         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7927             // [HGM] super: promotion to captured piece selected from holdings
7928             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7929             promotionChoice = TRUE;
7930             // kludge follows to temporarily execute move on display, without promoting yet
7931             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7932             boards[currentMove][toY][toX] = p;
7933             DrawPosition(FALSE, boards[currentMove]);
7934             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7935             boards[currentMove][toY][toX] = q;
7936             DisplayMessage("Click in holdings to choose piece", "");
7937             return;
7938         }
7939         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7940         PromotionPopUp(promoChoice);
7941     } else {
7942         int oldMove = currentMove;
7943         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7944         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7945         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7946         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7947         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7948            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7949             DrawPosition(TRUE, boards[currentMove]);
7950         else DrawPosition(FALSE, NULL);
7951         fromX = fromY = -1;
7952         flashing = 0;
7953     }
7954     appData.animate = saveAnimate;
7955     if (appData.animate || appData.animateDragging) {
7956         /* Undo animation damage if needed */
7957 //      DrawPosition(FALSE, NULL);
7958     }
7959 }
7960
7961 int
7962 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7963 {   // front-end-free part taken out of PieceMenuPopup
7964     int whichMenu; int xSqr, ySqr;
7965
7966     if(seekGraphUp) { // [HGM] seekgraph
7967         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7968         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7969         return -2;
7970     }
7971
7972     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7973          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7974         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7975         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7976         if(action == Press)   {
7977             originalFlip = flipView;
7978             flipView = !flipView; // temporarily flip board to see game from partners perspective
7979             DrawPosition(TRUE, partnerBoard);
7980             DisplayMessage(partnerStatus, "");
7981             partnerUp = TRUE;
7982         } else if(action == Release) {
7983             flipView = originalFlip;
7984             DrawPosition(TRUE, boards[currentMove]);
7985             partnerUp = FALSE;
7986         }
7987         return -2;
7988     }
7989
7990     xSqr = EventToSquare(x, BOARD_WIDTH);
7991     ySqr = EventToSquare(y, BOARD_HEIGHT);
7992     if (action == Release) {
7993         if(pieceSweep != EmptySquare) {
7994             EditPositionMenuEvent(pieceSweep, toX, toY);
7995             pieceSweep = EmptySquare;
7996         } else UnLoadPV(); // [HGM] pv
7997     }
7998     if (action != Press) return -2; // return code to be ignored
7999     switch (gameMode) {
8000       case IcsExamining:
8001         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8002       case EditPosition:
8003         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8004         if (xSqr < 0 || ySqr < 0) return -1;
8005         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8006         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8007         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
8008         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
8009         NextPiece(0);
8010         return 2; // grab
8011       case IcsObserving:
8012         if(!appData.icsEngineAnalyze) return -1;
8013       case IcsPlayingWhite:
8014       case IcsPlayingBlack:
8015         if(!appData.zippyPlay) goto noZip;
8016       case AnalyzeMode:
8017       case AnalyzeFile:
8018       case MachinePlaysWhite:
8019       case MachinePlaysBlack:
8020       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8021         if (!appData.dropMenu) {
8022           LoadPV(x, y);
8023           return 2; // flag front-end to grab mouse events
8024         }
8025         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8026            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8027       case EditGame:
8028       noZip:
8029         if (xSqr < 0 || ySqr < 0) return -1;
8030         if (!appData.dropMenu || appData.testLegality &&
8031             gameInfo.variant != VariantBughouse &&
8032             gameInfo.variant != VariantCrazyhouse) return -1;
8033         whichMenu = 1; // drop menu
8034         break;
8035       default:
8036         return -1;
8037     }
8038
8039     if (((*fromX = xSqr) < 0) ||
8040         ((*fromY = ySqr) < 0)) {
8041         *fromX = *fromY = -1;
8042         return -1;
8043     }
8044     if (flipView)
8045       *fromX = BOARD_WIDTH - 1 - *fromX;
8046     else
8047       *fromY = BOARD_HEIGHT - 1 - *fromY;
8048
8049     return whichMenu;
8050 }
8051
8052 void
8053 Wheel (int dir, int x, int y)
8054 {
8055     if(gameMode == EditPosition) {
8056         int xSqr = EventToSquare(x, BOARD_WIDTH);
8057         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8058         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8059         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8060         do {
8061             boards[currentMove][ySqr][xSqr] += dir;
8062             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8063             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8064         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8065         DrawPosition(FALSE, boards[currentMove]);
8066     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8067 }
8068
8069 void
8070 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8071 {
8072 //    char * hint = lastHint;
8073     FrontEndProgramStats stats;
8074
8075     stats.which = cps == &first ? 0 : 1;
8076     stats.depth = cpstats->depth;
8077     stats.nodes = cpstats->nodes;
8078     stats.score = cpstats->score;
8079     stats.time = cpstats->time;
8080     stats.pv = cpstats->movelist;
8081     stats.hint = lastHint;
8082     stats.an_move_index = 0;
8083     stats.an_move_count = 0;
8084
8085     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8086         stats.hint = cpstats->move_name;
8087         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8088         stats.an_move_count = cpstats->nr_moves;
8089     }
8090
8091     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
8092
8093     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8094         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8095
8096     SetProgramStats( &stats );
8097 }
8098
8099 void
8100 ClearEngineOutputPane (int which)
8101 {
8102     static FrontEndProgramStats dummyStats;
8103     dummyStats.which = which;
8104     dummyStats.pv = "#";
8105     SetProgramStats( &dummyStats );
8106 }
8107
8108 #define MAXPLAYERS 500
8109
8110 char *
8111 TourneyStandings (int display)
8112 {
8113     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8114     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8115     char result, *p, *names[MAXPLAYERS];
8116
8117     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8118         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8119     names[0] = p = strdup(appData.participants);
8120     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8121
8122     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8123
8124     while(result = appData.results[nr]) {
8125         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8126         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8127         wScore = bScore = 0;
8128         switch(result) {
8129           case '+': wScore = 2; break;
8130           case '-': bScore = 2; break;
8131           case '=': wScore = bScore = 1; break;
8132           case ' ':
8133           case '*': return strdup("busy"); // tourney not finished
8134         }
8135         score[w] += wScore;
8136         score[b] += bScore;
8137         games[w]++;
8138         games[b]++;
8139         nr++;
8140     }
8141     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8142     for(w=0; w<nPlayers; w++) {
8143         bScore = -1;
8144         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8145         ranking[w] = b; points[w] = bScore; score[b] = -2;
8146     }
8147     p = malloc(nPlayers*34+1);
8148     for(w=0; w<nPlayers && w<display; w++)
8149         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8150     free(names[0]);
8151     return p;
8152 }
8153
8154 void
8155 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8156 {       // count all piece types
8157         int p, f, r;
8158         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8159         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8160         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8161                 p = board[r][f];
8162                 pCnt[p]++;
8163                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8164                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8165                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8166                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8167                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8168                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8169         }
8170 }
8171
8172 int
8173 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8174 {
8175         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8176         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8177
8178         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8179         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8180         if(myPawns == 2 && nMine == 3) // KPP
8181             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8182         if(myPawns == 1 && nMine == 2) // KP
8183             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8184         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8185             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8186         if(myPawns) return FALSE;
8187         if(pCnt[WhiteRook+side])
8188             return pCnt[BlackRook-side] ||
8189                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8190                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8191                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8192         if(pCnt[WhiteCannon+side]) {
8193             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8194             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8195         }
8196         if(pCnt[WhiteKnight+side])
8197             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8198         return FALSE;
8199 }
8200
8201 int
8202 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8203 {
8204         VariantClass v = gameInfo.variant;
8205
8206         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8207         if(v == VariantShatranj) return TRUE; // always winnable through baring
8208         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8209         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8210
8211         if(v == VariantXiangqi) {
8212                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8213
8214                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8215                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8216                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8217                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8218                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8219                 if(stale) // we have at least one last-rank P plus perhaps C
8220                     return majors // KPKX
8221                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8222                 else // KCA*E*
8223                     return pCnt[WhiteFerz+side] // KCAK
8224                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8225                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8226                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8227
8228         } else if(v == VariantKnightmate) {
8229                 if(nMine == 1) return FALSE;
8230                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8231         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8232                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8233
8234                 if(nMine == 1) return FALSE; // bare King
8235                 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
8236                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8237                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8238                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8239                 if(pCnt[WhiteKnight+side])
8240                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8241                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8242                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8243                 if(nBishops)
8244                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8245                 if(pCnt[WhiteAlfil+side])
8246                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8247                 if(pCnt[WhiteWazir+side])
8248                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8249         }
8250
8251         return TRUE;
8252 }
8253
8254 int
8255 CompareWithRights (Board b1, Board b2)
8256 {
8257     int rights = 0;
8258     if(!CompareBoards(b1, b2)) return FALSE;
8259     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8260     /* compare castling rights */
8261     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8262            rights++; /* King lost rights, while rook still had them */
8263     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8264         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8265            rights++; /* but at least one rook lost them */
8266     }
8267     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8268            rights++;
8269     if( b1[CASTLING][5] != NoRights ) {
8270         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8271            rights++;
8272     }
8273     return rights == 0;
8274 }
8275
8276 int
8277 Adjudicate (ChessProgramState *cps)
8278 {       // [HGM] some adjudications useful with buggy engines
8279         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8280         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8281         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8282         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8283         int k, drop, count = 0; static int bare = 1;
8284         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8285         Boolean canAdjudicate = !appData.icsActive;
8286
8287         // most tests only when we understand the game, i.e. legality-checking on
8288             if( appData.testLegality )
8289             {   /* [HGM] Some more adjudications for obstinate engines */
8290                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8291                 static int moveCount = 6;
8292                 ChessMove result;
8293                 char *reason = NULL;
8294
8295                 /* Count what is on board. */
8296                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8297
8298                 /* Some material-based adjudications that have to be made before stalemate test */
8299                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8300                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8301                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8302                      if(canAdjudicate && appData.checkMates) {
8303                          if(engineOpponent)
8304                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8305                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8306                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8307                          return 1;
8308                      }
8309                 }
8310
8311                 /* Bare King in Shatranj (loses) or Losers (wins) */
8312                 if( nrW == 1 || nrB == 1) {
8313                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8314                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8315                      if(canAdjudicate && appData.checkMates) {
8316                          if(engineOpponent)
8317                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8318                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8319                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8320                          return 1;
8321                      }
8322                   } else
8323                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8324                   {    /* bare King */
8325                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8326                         if(canAdjudicate && appData.checkMates) {
8327                             /* but only adjudicate if adjudication enabled */
8328                             if(engineOpponent)
8329                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8330                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8331                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8332                             return 1;
8333                         }
8334                   }
8335                 } else bare = 1;
8336
8337
8338             // don't wait for engine to announce game end if we can judge ourselves
8339             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8340               case MT_CHECK:
8341                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8342                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8343                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8344                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8345                             checkCnt++;
8346                         if(checkCnt >= 2) {
8347                             reason = "Xboard adjudication: 3rd check";
8348                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8349                             break;
8350                         }
8351                     }
8352                 }
8353               case MT_NONE:
8354               default:
8355                 break;
8356               case MT_STEALMATE:
8357               case MT_STALEMATE:
8358               case MT_STAINMATE:
8359                 reason = "Xboard adjudication: Stalemate";
8360                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8361                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8362                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8363                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8364                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8365                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8366                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8367                                                                         EP_CHECKMATE : EP_WINS);
8368                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8369                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8370                 }
8371                 break;
8372               case MT_CHECKMATE:
8373                 reason = "Xboard adjudication: Checkmate";
8374                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8375                 if(gameInfo.variant == VariantShogi) {
8376                     if(forwardMostMove > backwardMostMove
8377                        && moveList[forwardMostMove-1][1] == '@'
8378                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8379                         reason = "XBoard adjudication: pawn-drop mate";
8380                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8381                     }
8382                 }
8383                 break;
8384             }
8385
8386                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8387                     case EP_STALEMATE:
8388                         result = GameIsDrawn; break;
8389                     case EP_CHECKMATE:
8390                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8391                     case EP_WINS:
8392                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8393                     default:
8394                         result = EndOfFile;
8395                 }
8396                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8397                     if(engineOpponent)
8398                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8399                     GameEnds( result, reason, GE_XBOARD );
8400                     return 1;
8401                 }
8402
8403                 /* Next absolutely insufficient mating material. */
8404                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8405                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8406                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8407
8408                      /* always flag draws, for judging claims */
8409                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8410
8411                      if(canAdjudicate && appData.materialDraws) {
8412                          /* but only adjudicate them if adjudication enabled */
8413                          if(engineOpponent) {
8414                            SendToProgram("force\n", engineOpponent); // suppress reply
8415                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8416                          }
8417                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8418                          return 1;
8419                      }
8420                 }
8421
8422                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8423                 if(gameInfo.variant == VariantXiangqi ?
8424                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8425                  : nrW + nrB == 4 &&
8426                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8427                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8428                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8429                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8430                    ) ) {
8431                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8432                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8433                           if(engineOpponent) {
8434                             SendToProgram("force\n", engineOpponent); // suppress reply
8435                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8436                           }
8437                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8438                           return 1;
8439                      }
8440                 } else moveCount = 6;
8441             }
8442
8443         // Repetition draws and 50-move rule can be applied independently of legality testing
8444
8445                 /* Check for rep-draws */
8446                 count = 0;
8447                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8448                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8449                 for(k = forwardMostMove-2;
8450                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8451                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8452                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8453                     k-=2)
8454                 {   int rights=0;
8455                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8456                         /* compare castling rights */
8457                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8458                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8459                                 rights++; /* King lost rights, while rook still had them */
8460                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8461                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8462                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8463                                    rights++; /* but at least one rook lost them */
8464                         }
8465                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8466                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8467                                 rights++;
8468                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8469                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8470                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8471                                    rights++;
8472                         }
8473                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8474                             && appData.drawRepeats > 1) {
8475                              /* adjudicate after user-specified nr of repeats */
8476                              int result = GameIsDrawn;
8477                              char *details = "XBoard adjudication: repetition draw";
8478                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8479                                 // [HGM] xiangqi: check for forbidden perpetuals
8480                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8481                                 for(m=forwardMostMove; m>k; m-=2) {
8482                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8483                                         ourPerpetual = 0; // the current mover did not always check
8484                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8485                                         hisPerpetual = 0; // the opponent did not always check
8486                                 }
8487                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8488                                                                         ourPerpetual, hisPerpetual);
8489                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8490                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8491                                     details = "Xboard adjudication: perpetual checking";
8492                                 } else
8493                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8494                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8495                                 } else
8496                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8497                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8498                                         result = BlackWins;
8499                                         details = "Xboard adjudication: repetition";
8500                                     }
8501                                 } else // it must be XQ
8502                                 // Now check for perpetual chases
8503                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8504                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8505                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8506                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8507                                         static char resdet[MSG_SIZ];
8508                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8509                                         details = resdet;
8510                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8511                                     } else
8512                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8513                                         break; // Abort repetition-checking loop.
8514                                 }
8515                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8516                              }
8517                              if(engineOpponent) {
8518                                SendToProgram("force\n", engineOpponent); // suppress reply
8519                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8520                              }
8521                              GameEnds( result, details, GE_XBOARD );
8522                              return 1;
8523                         }
8524                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8525                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8526                     }
8527                 }
8528
8529                 /* Now we test for 50-move draws. Determine ply count */
8530                 count = forwardMostMove;
8531                 /* look for last irreversble move */
8532                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8533                     count--;
8534                 /* if we hit starting position, add initial plies */
8535                 if( count == backwardMostMove )
8536                     count -= initialRulePlies;
8537                 count = forwardMostMove - count;
8538                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8539                         // adjust reversible move counter for checks in Xiangqi
8540                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8541                         if(i < backwardMostMove) i = backwardMostMove;
8542                         while(i <= forwardMostMove) {
8543                                 lastCheck = inCheck; // check evasion does not count
8544                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8545                                 if(inCheck || lastCheck) count--; // check does not count
8546                                 i++;
8547                         }
8548                 }
8549                 if( count >= 100)
8550                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8551                          /* this is used to judge if draw claims are legal */
8552                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8553                          if(engineOpponent) {
8554                            SendToProgram("force\n", engineOpponent); // suppress reply
8555                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8556                          }
8557                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8558                          return 1;
8559                 }
8560
8561                 /* if draw offer is pending, treat it as a draw claim
8562                  * when draw condition present, to allow engines a way to
8563                  * claim draws before making their move to avoid a race
8564                  * condition occurring after their move
8565                  */
8566                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8567                          char *p = NULL;
8568                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8569                              p = "Draw claim: 50-move rule";
8570                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8571                              p = "Draw claim: 3-fold repetition";
8572                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8573                              p = "Draw claim: insufficient mating material";
8574                          if( p != NULL && canAdjudicate) {
8575                              if(engineOpponent) {
8576                                SendToProgram("force\n", engineOpponent); // suppress reply
8577                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8578                              }
8579                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8580                              return 1;
8581                          }
8582                 }
8583
8584                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8585                     if(engineOpponent) {
8586                       SendToProgram("force\n", engineOpponent); // suppress reply
8587                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8588                     }
8589                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8590                     return 1;
8591                 }
8592         return 0;
8593 }
8594
8595 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8596 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8597 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8598
8599 static int
8600 BitbaseProbe ()
8601 {
8602     int pieces[10], squares[10], cnt=0, r, f, res;
8603     static int loaded;
8604     static PPROBE_EGBB probeBB;
8605     if(!appData.testLegality) return 10;
8606     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8607     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8608     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8609     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8610         ChessSquare piece = boards[forwardMostMove][r][f];
8611         int black = (piece >= BlackPawn);
8612         int type = piece - black*BlackPawn;
8613         if(piece == EmptySquare) continue;
8614         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8615         if(type == WhiteKing) type = WhiteQueen + 1;
8616         type = egbbCode[type];
8617         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8618         pieces[cnt] = type + black*6;
8619         if(++cnt > 5) return 11;
8620     }
8621     pieces[cnt] = squares[cnt] = 0;
8622     // probe EGBB
8623     if(loaded == 2) return 13; // loading failed before
8624     if(loaded == 0) {
8625         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8626         HMODULE lib;
8627         PLOAD_EGBB loadBB;
8628         loaded = 2; // prepare for failure
8629         if(!path) return 13; // no egbb installed
8630         strncpy(buf, path + 8, MSG_SIZ);
8631         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8632         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8633         lib = LoadLibrary(buf);
8634         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8635         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8636         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8637         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8638         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8639         loaded = 1; // success!
8640     }
8641     res = probeBB(forwardMostMove & 1, pieces, squares);
8642     return res > 0 ? 1 : res < 0 ? -1 : 0;
8643 }
8644
8645 char *
8646 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8647 {   // [HGM] book: this routine intercepts moves to simulate book replies
8648     char *bookHit = NULL;
8649
8650     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8651         char buf[MSG_SIZ];
8652         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8653         SendToProgram(buf, cps);
8654     }
8655     //first determine if the incoming move brings opponent into his book
8656     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8657         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8658     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8659     if(bookHit != NULL && !cps->bookSuspend) {
8660         // make sure opponent is not going to reply after receiving move to book position
8661         SendToProgram("force\n", cps);
8662         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8663     }
8664     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8665     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8666     // now arrange restart after book miss
8667     if(bookHit) {
8668         // after a book hit we never send 'go', and the code after the call to this routine
8669         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8670         char buf[MSG_SIZ], *move = bookHit;
8671         if(cps->useSAN) {
8672             int fromX, fromY, toX, toY;
8673             char promoChar;
8674             ChessMove moveType;
8675             move = buf + 30;
8676             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8677                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8678                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8679                                     PosFlags(forwardMostMove),
8680                                     fromY, fromX, toY, toX, promoChar, move);
8681             } else {
8682                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8683                 bookHit = NULL;
8684             }
8685         }
8686         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8687         SendToProgram(buf, cps);
8688         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8689     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8690         SendToProgram("go\n", cps);
8691         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8692     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8693         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8694             SendToProgram("go\n", cps);
8695         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8696     }
8697     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8698 }
8699
8700 int
8701 LoadError (char *errmess, ChessProgramState *cps)
8702 {   // unloads engine and switches back to -ncp mode if it was first
8703     if(cps->initDone) return FALSE;
8704     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8705     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8706     cps->pr = NoProc;
8707     if(cps == &first) {
8708         appData.noChessProgram = TRUE;
8709         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8710         gameMode = BeginningOfGame; ModeHighlight();
8711         SetNCPMode();
8712     }
8713     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8714     DisplayMessage("", ""); // erase waiting message
8715     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8716     return TRUE;
8717 }
8718
8719 char *savedMessage;
8720 ChessProgramState *savedState;
8721 void
8722 DeferredBookMove (void)
8723 {
8724         if(savedState->lastPing != savedState->lastPong)
8725                     ScheduleDelayedEvent(DeferredBookMove, 10);
8726         else
8727         HandleMachineMove(savedMessage, savedState);
8728 }
8729
8730 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8731 static ChessProgramState *stalledEngine;
8732 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8733
8734 void
8735 HandleMachineMove (char *message, ChessProgramState *cps)
8736 {
8737     static char firstLeg[20], legs;
8738     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8739     char realname[MSG_SIZ];
8740     int fromX, fromY, toX, toY;
8741     ChessMove moveType;
8742     char promoChar, roar;
8743     char *p, *pv=buf1;
8744     int oldError;
8745     char *bookHit;
8746
8747     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8748         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8749         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8750             DisplayError(_("Invalid pairing from pairing engine"), 0);
8751             return;
8752         }
8753         pairingReceived = 1;
8754         NextMatchGame();
8755         return; // Skim the pairing messages here.
8756     }
8757
8758     oldError = cps->userError; cps->userError = 0;
8759
8760 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8761     /*
8762      * Kludge to ignore BEL characters
8763      */
8764     while (*message == '\007') message++;
8765
8766     /*
8767      * [HGM] engine debug message: ignore lines starting with '#' character
8768      */
8769     if(cps->debug && *message == '#') return;
8770
8771     /*
8772      * Look for book output
8773      */
8774     if (cps == &first && bookRequested) {
8775         if (message[0] == '\t' || message[0] == ' ') {
8776             /* Part of the book output is here; append it */
8777             strcat(bookOutput, message);
8778             strcat(bookOutput, "  \n");
8779             return;
8780         } else if (bookOutput[0] != NULLCHAR) {
8781             /* All of book output has arrived; display it */
8782             char *p = bookOutput;
8783             while (*p != NULLCHAR) {
8784                 if (*p == '\t') *p = ' ';
8785                 p++;
8786             }
8787             DisplayInformation(bookOutput);
8788             bookRequested = FALSE;
8789             /* Fall through to parse the current output */
8790         }
8791     }
8792
8793     /*
8794      * Look for machine move.
8795      */
8796     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8797         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8798     {
8799         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8800             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8801             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8802             stalledEngine = cps;
8803             if(appData.ponderNextMove) { // bring opponent out of ponder
8804                 if(gameMode == TwoMachinesPlay) {
8805                     if(cps->other->pause)
8806                         PauseEngine(cps->other);
8807                     else
8808                         SendToProgram("easy\n", cps->other);
8809                 }
8810             }
8811             StopClocks();
8812             return;
8813         }
8814
8815       if(cps->usePing) {
8816
8817         /* This method is only useful on engines that support ping */
8818         if(abortEngineThink) {
8819             if (appData.debugMode) {
8820                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8821             }
8822             SendToProgram("undo\n", cps);
8823             return;
8824         }
8825
8826         if (cps->lastPing != cps->lastPong) {
8827             /* Extra move from before last new; ignore */
8828             if (appData.debugMode) {
8829                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8830             }
8831           return;
8832         }
8833
8834       } else {
8835
8836         int machineWhite = FALSE;
8837
8838         switch (gameMode) {
8839           case BeginningOfGame:
8840             /* Extra move from before last reset; ignore */
8841             if (appData.debugMode) {
8842                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8843             }
8844             return;
8845
8846           case EndOfGame:
8847           case IcsIdle:
8848           default:
8849             /* Extra move after we tried to stop.  The mode test is
8850                not a reliable way of detecting this problem, but it's
8851                the best we can do on engines that don't support ping.
8852             */
8853             if (appData.debugMode) {
8854                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8855                         cps->which, gameMode);
8856             }
8857             SendToProgram("undo\n", cps);
8858             return;
8859
8860           case MachinePlaysWhite:
8861           case IcsPlayingWhite:
8862             machineWhite = TRUE;
8863             break;
8864
8865           case MachinePlaysBlack:
8866           case IcsPlayingBlack:
8867             machineWhite = FALSE;
8868             break;
8869
8870           case TwoMachinesPlay:
8871             machineWhite = (cps->twoMachinesColor[0] == 'w');
8872             break;
8873         }
8874         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8875             if (appData.debugMode) {
8876                 fprintf(debugFP,
8877                         "Ignoring move out of turn by %s, gameMode %d"
8878                         ", forwardMost %d\n",
8879                         cps->which, gameMode, forwardMostMove);
8880             }
8881             return;
8882         }
8883       }
8884
8885         if(cps->alphaRank) AlphaRank(machineMove, 4);
8886
8887         // [HGM] lion: (some very limited) support for Alien protocol
8888         killX = killY = kill2X = kill2Y = -1;
8889         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8890             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8891             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8892             return;
8893         }
8894         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8895             char *q = strchr(p+1, ',');            // second comma?
8896             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8897             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8898             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8899         }
8900         if(firstLeg[0]) { // there was a previous leg;
8901             // only support case where same piece makes two step
8902             char buf[20], *p = machineMove+1, *q = buf+1, f;
8903             safeStrCpy(buf, machineMove, 20);
8904             while(isdigit(*q)) q++; // find start of to-square
8905             safeStrCpy(machineMove, firstLeg, 20);
8906             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8907             if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
8908             else if(*p == *buf)   // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
8909             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8910             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8911             firstLeg[0] = NULLCHAR; legs = 0;
8912         }
8913
8914         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8915                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8916             /* Machine move could not be parsed; ignore it. */
8917           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8918                     machineMove, _(cps->which));
8919             DisplayMoveError(buf1);
8920             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8921                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8922             if (gameMode == TwoMachinesPlay) {
8923               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8924                        buf1, GE_XBOARD);
8925             }
8926             return;
8927         }
8928
8929         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8930         /* So we have to redo legality test with true e.p. status here,  */
8931         /* to make sure an illegal e.p. capture does not slip through,   */
8932         /* to cause a forfeit on a justified illegal-move complaint      */
8933         /* of the opponent.                                              */
8934         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8935            ChessMove moveType;
8936            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8937                              fromY, fromX, toY, toX, promoChar);
8938             if(moveType == IllegalMove) {
8939               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8940                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8941                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8942                            buf1, GE_XBOARD);
8943                 return;
8944            } else if(!appData.fischerCastling)
8945            /* [HGM] Kludge to handle engines that send FRC-style castling
8946               when they shouldn't (like TSCP-Gothic) */
8947            switch(moveType) {
8948              case WhiteASideCastleFR:
8949              case BlackASideCastleFR:
8950                toX+=2;
8951                currentMoveString[2]++;
8952                break;
8953              case WhiteHSideCastleFR:
8954              case BlackHSideCastleFR:
8955                toX--;
8956                currentMoveString[2]--;
8957                break;
8958              default: ; // nothing to do, but suppresses warning of pedantic compilers
8959            }
8960         }
8961         hintRequested = FALSE;
8962         lastHint[0] = NULLCHAR;
8963         bookRequested = FALSE;
8964         /* Program may be pondering now */
8965         cps->maybeThinking = TRUE;
8966         if (cps->sendTime == 2) cps->sendTime = 1;
8967         if (cps->offeredDraw) cps->offeredDraw--;
8968
8969         /* [AS] Save move info*/
8970         pvInfoList[ forwardMostMove ].score = programStats.score;
8971         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8972         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8973
8974         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8975
8976         /* Test suites abort the 'game' after one move */
8977         if(*appData.finger) {
8978            static FILE *f;
8979            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8980            if(!f) f = fopen(appData.finger, "w");
8981            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8982            else { DisplayFatalError("Bad output file", errno, 0); return; }
8983            free(fen);
8984            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8985         }
8986         if(appData.epd) {
8987            if(solvingTime >= 0) {
8988               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8989               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8990            } else {
8991               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8992               if(solvingTime == -2) second.matchWins++;
8993            }
8994            OutputKibitz(2, buf1);
8995            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8996         }
8997
8998         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8999         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9000             int count = 0;
9001
9002             while( count < adjudicateLossPlies ) {
9003                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9004
9005                 if( count & 1 ) {
9006                     score = -score; /* Flip score for winning side */
9007                 }
9008
9009                 if( score > appData.adjudicateLossThreshold ) {
9010                     break;
9011                 }
9012
9013                 count++;
9014             }
9015
9016             if( count >= adjudicateLossPlies ) {
9017                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9018
9019                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9020                     "Xboard adjudication",
9021                     GE_XBOARD );
9022
9023                 return;
9024             }
9025         }
9026
9027         if(Adjudicate(cps)) {
9028             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9029             return; // [HGM] adjudicate: for all automatic game ends
9030         }
9031
9032 #if ZIPPY
9033         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9034             first.initDone) {
9035           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9036                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9037                 SendToICS("draw ");
9038                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9039           }
9040           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9041           ics_user_moved = 1;
9042           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9043                 char buf[3*MSG_SIZ];
9044
9045                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9046                         programStats.score / 100.,
9047                         programStats.depth,
9048                         programStats.time / 100.,
9049                         (unsigned int)programStats.nodes,
9050                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9051                         programStats.movelist);
9052                 SendToICS(buf);
9053           }
9054         }
9055 #endif
9056
9057         /* [AS] Clear stats for next move */
9058         ClearProgramStats();
9059         thinkOutput[0] = NULLCHAR;
9060         hiddenThinkOutputState = 0;
9061
9062         bookHit = NULL;
9063         if (gameMode == TwoMachinesPlay) {
9064             /* [HGM] relaying draw offers moved to after reception of move */
9065             /* and interpreting offer as claim if it brings draw condition */
9066             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9067                 SendToProgram("draw\n", cps->other);
9068             }
9069             if (cps->other->sendTime) {
9070                 SendTimeRemaining(cps->other,
9071                                   cps->other->twoMachinesColor[0] == 'w');
9072             }
9073             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9074             if (firstMove && !bookHit) {
9075                 firstMove = FALSE;
9076                 if (cps->other->useColors) {
9077                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9078                 }
9079                 SendToProgram("go\n", cps->other);
9080             }
9081             cps->other->maybeThinking = TRUE;
9082         }
9083
9084         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9085
9086         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9087
9088         if (!pausing && appData.ringBellAfterMoves) {
9089             if(!roar) RingBell();
9090         }
9091
9092         /*
9093          * Reenable menu items that were disabled while
9094          * machine was thinking
9095          */
9096         if (gameMode != TwoMachinesPlay)
9097             SetUserThinkingEnables();
9098
9099         // [HGM] book: after book hit opponent has received move and is now in force mode
9100         // force the book reply into it, and then fake that it outputted this move by jumping
9101         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9102         if(bookHit) {
9103                 static char bookMove[MSG_SIZ]; // a bit generous?
9104
9105                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9106                 strcat(bookMove, bookHit);
9107                 message = bookMove;
9108                 cps = cps->other;
9109                 programStats.nodes = programStats.depth = programStats.time =
9110                 programStats.score = programStats.got_only_move = 0;
9111                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9112
9113                 if(cps->lastPing != cps->lastPong) {
9114                     savedMessage = message; // args for deferred call
9115                     savedState = cps;
9116                     ScheduleDelayedEvent(DeferredBookMove, 10);
9117                     return;
9118                 }
9119                 goto FakeBookMove;
9120         }
9121
9122         return;
9123     }
9124
9125     /* Set special modes for chess engines.  Later something general
9126      *  could be added here; for now there is just one kludge feature,
9127      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9128      *  when "xboard" is given as an interactive command.
9129      */
9130     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9131         cps->useSigint = FALSE;
9132         cps->useSigterm = FALSE;
9133     }
9134     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9135       ParseFeatures(message+8, cps);
9136       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9137     }
9138
9139     if (!strncmp(message, "setup ", 6) && 
9140         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9141           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9142                                         ) { // [HGM] allow first engine to define opening position
9143       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9144       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9145       *buf = NULLCHAR;
9146       if(sscanf(message, "setup (%s", buf) == 1) {
9147         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9148         ASSIGN(appData.pieceToCharTable, buf);
9149       }
9150       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9151       if(dummy >= 3) {
9152         while(message[s] && message[s++] != ' ');
9153         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9154            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9155             if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9156             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9157             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9158           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9159           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9160           startedFromSetupPosition = FALSE;
9161         }
9162       }
9163       if(startedFromSetupPosition) return;
9164       ParseFEN(boards[0], &dummy, message+s, FALSE);
9165       DrawPosition(TRUE, boards[0]);
9166       CopyBoard(initialPosition, boards[0]);
9167       startedFromSetupPosition = TRUE;
9168       return;
9169     }
9170     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9171       ChessSquare piece = WhitePawn;
9172       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9173       if(*p == '+') promoted++, ID = *++p;
9174       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9175       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9176       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9177       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9178       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9179       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9180       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9181       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9182                                                && gameInfo.variant != VariantGreat
9183                                                && gameInfo.variant != VariantFairy    ) return;
9184       if(piece < EmptySquare) {
9185         pieceDefs = TRUE;
9186         ASSIGN(pieceDesc[piece], buf1);
9187         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9188       }
9189       return;
9190     }
9191     if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9192       if(deferChoice) {
9193         LeftClick(Press, 0, 0); // finish the click that was interrupted
9194       } else if(promoSweep != EmptySquare) {
9195         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9196         if(strlen(promoRestrict) > 1) Sweep(0);
9197       }
9198       return;
9199     }
9200     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9201      * want this, I was asked to put it in, and obliged.
9202      */
9203     if (!strncmp(message, "setboard ", 9)) {
9204         Board initial_position;
9205
9206         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9207
9208         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9209             DisplayError(_("Bad FEN received from engine"), 0);
9210             return ;
9211         } else {
9212            Reset(TRUE, FALSE);
9213            CopyBoard(boards[0], initial_position);
9214            initialRulePlies = FENrulePlies;
9215            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9216            else gameMode = MachinePlaysBlack;
9217            DrawPosition(FALSE, boards[currentMove]);
9218         }
9219         return;
9220     }
9221
9222     /*
9223      * Look for communication commands
9224      */
9225     if (!strncmp(message, "telluser ", 9)) {
9226         if(message[9] == '\\' && message[10] == '\\')
9227             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9228         PlayTellSound();
9229         DisplayNote(message + 9);
9230         return;
9231     }
9232     if (!strncmp(message, "tellusererror ", 14)) {
9233         cps->userError = 1;
9234         if(message[14] == '\\' && message[15] == '\\')
9235             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9236         PlayTellSound();
9237         DisplayError(message + 14, 0);
9238         return;
9239     }
9240     if (!strncmp(message, "tellopponent ", 13)) {
9241       if (appData.icsActive) {
9242         if (loggedOn) {
9243           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9244           SendToICS(buf1);
9245         }
9246       } else {
9247         DisplayNote(message + 13);
9248       }
9249       return;
9250     }
9251     if (!strncmp(message, "tellothers ", 11)) {
9252       if (appData.icsActive) {
9253         if (loggedOn) {
9254           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9255           SendToICS(buf1);
9256         }
9257       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9258       return;
9259     }
9260     if (!strncmp(message, "tellall ", 8)) {
9261       if (appData.icsActive) {
9262         if (loggedOn) {
9263           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9264           SendToICS(buf1);
9265         }
9266       } else {
9267         DisplayNote(message + 8);
9268       }
9269       return;
9270     }
9271     if (strncmp(message, "warning", 7) == 0) {
9272         /* Undocumented feature, use tellusererror in new code */
9273         DisplayError(message, 0);
9274         return;
9275     }
9276     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9277         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9278         strcat(realname, " query");
9279         AskQuestion(realname, buf2, buf1, cps->pr);
9280         return;
9281     }
9282     /* Commands from the engine directly to ICS.  We don't allow these to be
9283      *  sent until we are logged on. Crafty kibitzes have been known to
9284      *  interfere with the login process.
9285      */
9286     if (loggedOn) {
9287         if (!strncmp(message, "tellics ", 8)) {
9288             SendToICS(message + 8);
9289             SendToICS("\n");
9290             return;
9291         }
9292         if (!strncmp(message, "tellicsnoalias ", 15)) {
9293             SendToICS(ics_prefix);
9294             SendToICS(message + 15);
9295             SendToICS("\n");
9296             return;
9297         }
9298         /* The following are for backward compatibility only */
9299         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9300             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9301             SendToICS(ics_prefix);
9302             SendToICS(message);
9303             SendToICS("\n");
9304             return;
9305         }
9306     }
9307     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9308         if(initPing == cps->lastPong) {
9309             if(gameInfo.variant == VariantUnknown) {
9310                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9311                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9312                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9313             }
9314             initPing = -1;
9315         }
9316         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9317             abortEngineThink = FALSE;
9318             DisplayMessage("", "");
9319             ThawUI();
9320         }
9321         return;
9322     }
9323     if(!strncmp(message, "highlight ", 10)) {
9324         if(appData.testLegality && !*engineVariant && appData.markers) return;
9325         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9326         return;
9327     }
9328     if(!strncmp(message, "click ", 6)) {
9329         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9330         if(appData.testLegality || !appData.oneClick) return;
9331         sscanf(message+6, "%c%d%c", &f, &y, &c);
9332         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9333         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9334         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9335         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9336         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9337         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9338             LeftClick(Release, lastLeftX, lastLeftY);
9339         controlKey  = (c == ',');
9340         LeftClick(Press, x, y);
9341         LeftClick(Release, x, y);
9342         first.highlight = f;
9343         return;
9344     }
9345     /*
9346      * If the move is illegal, cancel it and redraw the board.
9347      * Also deal with other error cases.  Matching is rather loose
9348      * here to accommodate engines written before the spec.
9349      */
9350     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9351         strncmp(message, "Error", 5) == 0) {
9352         if (StrStr(message, "name") ||
9353             StrStr(message, "rating") || StrStr(message, "?") ||
9354             StrStr(message, "result") || StrStr(message, "board") ||
9355             StrStr(message, "bk") || StrStr(message, "computer") ||
9356             StrStr(message, "variant") || StrStr(message, "hint") ||
9357             StrStr(message, "random") || StrStr(message, "depth") ||
9358             StrStr(message, "accepted")) {
9359             return;
9360         }
9361         if (StrStr(message, "protover")) {
9362           /* Program is responding to input, so it's apparently done
9363              initializing, and this error message indicates it is
9364              protocol version 1.  So we don't need to wait any longer
9365              for it to initialize and send feature commands. */
9366           FeatureDone(cps, 1);
9367           cps->protocolVersion = 1;
9368           return;
9369         }
9370         cps->maybeThinking = FALSE;
9371
9372         if (StrStr(message, "draw")) {
9373             /* Program doesn't have "draw" command */
9374             cps->sendDrawOffers = 0;
9375             return;
9376         }
9377         if (cps->sendTime != 1 &&
9378             (StrStr(message, "time") || StrStr(message, "otim"))) {
9379           /* Program apparently doesn't have "time" or "otim" command */
9380           cps->sendTime = 0;
9381           return;
9382         }
9383         if (StrStr(message, "analyze")) {
9384             cps->analysisSupport = FALSE;
9385             cps->analyzing = FALSE;
9386 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9387             EditGameEvent(); // [HGM] try to preserve loaded game
9388             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9389             DisplayError(buf2, 0);
9390             return;
9391         }
9392         if (StrStr(message, "(no matching move)st")) {
9393           /* Special kludge for GNU Chess 4 only */
9394           cps->stKludge = TRUE;
9395           SendTimeControl(cps, movesPerSession, timeControl,
9396                           timeIncrement, appData.searchDepth,
9397                           searchTime);
9398           return;
9399         }
9400         if (StrStr(message, "(no matching move)sd")) {
9401           /* Special kludge for GNU Chess 4 only */
9402           cps->sdKludge = TRUE;
9403           SendTimeControl(cps, movesPerSession, timeControl,
9404                           timeIncrement, appData.searchDepth,
9405                           searchTime);
9406           return;
9407         }
9408         if (!StrStr(message, "llegal")) {
9409             return;
9410         }
9411         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9412             gameMode == IcsIdle) return;
9413         if (forwardMostMove <= backwardMostMove) return;
9414         if (pausing) PauseEvent();
9415       if(appData.forceIllegal) {
9416             // [HGM] illegal: machine refused move; force position after move into it
9417           SendToProgram("force\n", cps);
9418           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9419                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9420                 // when black is to move, while there might be nothing on a2 or black
9421                 // might already have the move. So send the board as if white has the move.
9422                 // But first we must change the stm of the engine, as it refused the last move
9423                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9424                 if(WhiteOnMove(forwardMostMove)) {
9425                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9426                     SendBoard(cps, forwardMostMove); // kludgeless board
9427                 } else {
9428                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9429                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9430                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9431                 }
9432           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9433             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9434                  gameMode == TwoMachinesPlay)
9435               SendToProgram("go\n", cps);
9436             return;
9437       } else
9438         if (gameMode == PlayFromGameFile) {
9439             /* Stop reading this game file */
9440             gameMode = EditGame;
9441             ModeHighlight();
9442         }
9443         /* [HGM] illegal-move claim should forfeit game when Xboard */
9444         /* only passes fully legal moves                            */
9445         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9446             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9447                                 "False illegal-move claim", GE_XBOARD );
9448             return; // do not take back move we tested as valid
9449         }
9450         currentMove = forwardMostMove-1;
9451         DisplayMove(currentMove-1); /* before DisplayMoveError */
9452         SwitchClocks(forwardMostMove-1); // [HGM] race
9453         DisplayBothClocks();
9454         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9455                 parseList[currentMove], _(cps->which));
9456         DisplayMoveError(buf1);
9457         DrawPosition(FALSE, boards[currentMove]);
9458
9459         SetUserThinkingEnables();
9460         return;
9461     }
9462     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9463         /* Program has a broken "time" command that
9464            outputs a string not ending in newline.
9465            Don't use it. */
9466         cps->sendTime = 0;
9467     }
9468     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9469         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9470             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9471     }
9472
9473     /*
9474      * If chess program startup fails, exit with an error message.
9475      * Attempts to recover here are futile. [HGM] Well, we try anyway
9476      */
9477     if ((StrStr(message, "unknown host") != NULL)
9478         || (StrStr(message, "No remote directory") != NULL)
9479         || (StrStr(message, "not found") != NULL)
9480         || (StrStr(message, "No such file") != NULL)
9481         || (StrStr(message, "can't alloc") != NULL)
9482         || (StrStr(message, "Permission denied") != NULL)) {
9483
9484         cps->maybeThinking = FALSE;
9485         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9486                 _(cps->which), cps->program, cps->host, message);
9487         RemoveInputSource(cps->isr);
9488         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9489             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9490             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9491         }
9492         return;
9493     }
9494
9495     /*
9496      * Look for hint output
9497      */
9498     if (sscanf(message, "Hint: %s", buf1) == 1) {
9499         if (cps == &first && hintRequested) {
9500             hintRequested = FALSE;
9501             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9502                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9503                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9504                                     PosFlags(forwardMostMove),
9505                                     fromY, fromX, toY, toX, promoChar, buf1);
9506                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9507                 DisplayInformation(buf2);
9508             } else {
9509                 /* Hint move could not be parsed!? */
9510               snprintf(buf2, sizeof(buf2),
9511                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9512                         buf1, _(cps->which));
9513                 DisplayError(buf2, 0);
9514             }
9515         } else {
9516           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9517         }
9518         return;
9519     }
9520
9521     /*
9522      * Ignore other messages if game is not in progress
9523      */
9524     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9525         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9526
9527     /*
9528      * look for win, lose, draw, or draw offer
9529      */
9530     if (strncmp(message, "1-0", 3) == 0) {
9531         char *p, *q, *r = "";
9532         p = strchr(message, '{');
9533         if (p) {
9534             q = strchr(p, '}');
9535             if (q) {
9536                 *q = NULLCHAR;
9537                 r = p + 1;
9538             }
9539         }
9540         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9541         return;
9542     } else if (strncmp(message, "0-1", 3) == 0) {
9543         char *p, *q, *r = "";
9544         p = strchr(message, '{');
9545         if (p) {
9546             q = strchr(p, '}');
9547             if (q) {
9548                 *q = NULLCHAR;
9549                 r = p + 1;
9550             }
9551         }
9552         /* Kludge for Arasan 4.1 bug */
9553         if (strcmp(r, "Black resigns") == 0) {
9554             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9555             return;
9556         }
9557         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9558         return;
9559     } else if (strncmp(message, "1/2", 3) == 0) {
9560         char *p, *q, *r = "";
9561         p = strchr(message, '{');
9562         if (p) {
9563             q = strchr(p, '}');
9564             if (q) {
9565                 *q = NULLCHAR;
9566                 r = p + 1;
9567             }
9568         }
9569
9570         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9571         return;
9572
9573     } else if (strncmp(message, "White resign", 12) == 0) {
9574         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9575         return;
9576     } else if (strncmp(message, "Black resign", 12) == 0) {
9577         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9578         return;
9579     } else if (strncmp(message, "White matches", 13) == 0 ||
9580                strncmp(message, "Black matches", 13) == 0   ) {
9581         /* [HGM] ignore GNUShogi noises */
9582         return;
9583     } else if (strncmp(message, "White", 5) == 0 &&
9584                message[5] != '(' &&
9585                StrStr(message, "Black") == NULL) {
9586         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9587         return;
9588     } else if (strncmp(message, "Black", 5) == 0 &&
9589                message[5] != '(') {
9590         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9591         return;
9592     } else if (strcmp(message, "resign") == 0 ||
9593                strcmp(message, "computer resigns") == 0) {
9594         switch (gameMode) {
9595           case MachinePlaysBlack:
9596           case IcsPlayingBlack:
9597             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9598             break;
9599           case MachinePlaysWhite:
9600           case IcsPlayingWhite:
9601             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9602             break;
9603           case TwoMachinesPlay:
9604             if (cps->twoMachinesColor[0] == 'w')
9605               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9606             else
9607               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9608             break;
9609           default:
9610             /* can't happen */
9611             break;
9612         }
9613         return;
9614     } else if (strncmp(message, "opponent mates", 14) == 0) {
9615         switch (gameMode) {
9616           case MachinePlaysBlack:
9617           case IcsPlayingBlack:
9618             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9619             break;
9620           case MachinePlaysWhite:
9621           case IcsPlayingWhite:
9622             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9623             break;
9624           case TwoMachinesPlay:
9625             if (cps->twoMachinesColor[0] == 'w')
9626               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9627             else
9628               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9629             break;
9630           default:
9631             /* can't happen */
9632             break;
9633         }
9634         return;
9635     } else if (strncmp(message, "computer mates", 14) == 0) {
9636         switch (gameMode) {
9637           case MachinePlaysBlack:
9638           case IcsPlayingBlack:
9639             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9640             break;
9641           case MachinePlaysWhite:
9642           case IcsPlayingWhite:
9643             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9644             break;
9645           case TwoMachinesPlay:
9646             if (cps->twoMachinesColor[0] == 'w')
9647               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9648             else
9649               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9650             break;
9651           default:
9652             /* can't happen */
9653             break;
9654         }
9655         return;
9656     } else if (strncmp(message, "checkmate", 9) == 0) {
9657         if (WhiteOnMove(forwardMostMove)) {
9658             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9659         } else {
9660             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9661         }
9662         return;
9663     } else if (strstr(message, "Draw") != NULL ||
9664                strstr(message, "game is a draw") != NULL) {
9665         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9666         return;
9667     } else if (strstr(message, "offer") != NULL &&
9668                strstr(message, "draw") != NULL) {
9669 #if ZIPPY
9670         if (appData.zippyPlay && first.initDone) {
9671             /* Relay offer to ICS */
9672             SendToICS(ics_prefix);
9673             SendToICS("draw\n");
9674         }
9675 #endif
9676         cps->offeredDraw = 2; /* valid until this engine moves twice */
9677         if (gameMode == TwoMachinesPlay) {
9678             if (cps->other->offeredDraw) {
9679                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9680             /* [HGM] in two-machine mode we delay relaying draw offer      */
9681             /* until after we also have move, to see if it is really claim */
9682             }
9683         } else if (gameMode == MachinePlaysWhite ||
9684                    gameMode == MachinePlaysBlack) {
9685           if (userOfferedDraw) {
9686             DisplayInformation(_("Machine accepts your draw offer"));
9687             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9688           } else {
9689             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9690           }
9691         }
9692     }
9693
9694
9695     /*
9696      * Look for thinking output
9697      */
9698     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9699           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9700                                 ) {
9701         int plylev, mvleft, mvtot, curscore, time;
9702         char mvname[MOVE_LEN];
9703         u64 nodes; // [DM]
9704         char plyext;
9705         int ignore = FALSE;
9706         int prefixHint = FALSE;
9707         mvname[0] = NULLCHAR;
9708
9709         switch (gameMode) {
9710           case MachinePlaysBlack:
9711           case IcsPlayingBlack:
9712             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9713             break;
9714           case MachinePlaysWhite:
9715           case IcsPlayingWhite:
9716             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9717             break;
9718           case AnalyzeMode:
9719           case AnalyzeFile:
9720             break;
9721           case IcsObserving: /* [DM] icsEngineAnalyze */
9722             if (!appData.icsEngineAnalyze) ignore = TRUE;
9723             break;
9724           case TwoMachinesPlay:
9725             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9726                 ignore = TRUE;
9727             }
9728             break;
9729           default:
9730             ignore = TRUE;
9731             break;
9732         }
9733
9734         if (!ignore) {
9735             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9736             int solved = 0;
9737             buf1[0] = NULLCHAR;
9738             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9739                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9740                 char score_buf[MSG_SIZ];
9741
9742                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9743                     nodes += u64Const(0x100000000);
9744
9745                 if (plyext != ' ' && plyext != '\t') {
9746                     time *= 100;
9747                 }
9748
9749                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9750                 if( cps->scoreIsAbsolute &&
9751                     ( gameMode == MachinePlaysBlack ||
9752                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9753                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9754                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9755                      !WhiteOnMove(currentMove)
9756                     ) )
9757                 {
9758                     curscore = -curscore;
9759                 }
9760
9761                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9762
9763                 if(*bestMove) { // rememer time best EPD move was first found
9764                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9765                     ChessMove mt; char *p = bestMove;
9766                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9767                     solved = 0;
9768                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9769                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9770                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9771                             solved = 1;
9772                             break;
9773                         }
9774                         while(*p && *p != ' ') p++;
9775                         while(*p == ' ') p++;
9776                     }
9777                     if(!solved) solvingTime = -1;
9778                 }
9779                 if(*avoidMove && !solved) {
9780                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9781                     ChessMove mt; char *p = avoidMove, solved = 1;
9782                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9783                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9784                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9785                             solved = 0; solvingTime = -2;
9786                             break;
9787                         }
9788                         while(*p && *p != ' ') p++;
9789                         while(*p == ' ') p++;
9790                     }
9791                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9792                 }
9793
9794                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9795                         char buf[MSG_SIZ];
9796                         FILE *f;
9797                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9798                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9799                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9800                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9801                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9802                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9803                                 fclose(f);
9804                         }
9805                         else
9806                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9807                           DisplayError(_("failed writing PV"), 0);
9808                 }
9809
9810                 tempStats.depth = plylev;
9811                 tempStats.nodes = nodes;
9812                 tempStats.time = time;
9813                 tempStats.score = curscore;
9814                 tempStats.got_only_move = 0;
9815
9816                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9817                         int ticklen;
9818
9819                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9820                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9821                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9822                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9823                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9824                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9825                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9826                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9827                 }
9828
9829                 /* Buffer overflow protection */
9830                 if (pv[0] != NULLCHAR) {
9831                     if (strlen(pv) >= sizeof(tempStats.movelist)
9832                         && appData.debugMode) {
9833                         fprintf(debugFP,
9834                                 "PV is too long; using the first %u bytes.\n",
9835                                 (unsigned) sizeof(tempStats.movelist) - 1);
9836                     }
9837
9838                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9839                 } else {
9840                     sprintf(tempStats.movelist, " no PV\n");
9841                 }
9842
9843                 if (tempStats.seen_stat) {
9844                     tempStats.ok_to_send = 1;
9845                 }
9846
9847                 if (strchr(tempStats.movelist, '(') != NULL) {
9848                     tempStats.line_is_book = 1;
9849                     tempStats.nr_moves = 0;
9850                     tempStats.moves_left = 0;
9851                 } else {
9852                     tempStats.line_is_book = 0;
9853                 }
9854
9855                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9856                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9857
9858                 SendProgramStatsToFrontend( cps, &tempStats );
9859
9860                 /*
9861                     [AS] Protect the thinkOutput buffer from overflow... this
9862                     is only useful if buf1 hasn't overflowed first!
9863                 */
9864                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9865                 if(curscore >= MATE_SCORE) 
9866                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9867                 else if(curscore <= -MATE_SCORE) 
9868                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9869                 else
9870                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9871                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9872                          plylev,
9873                          (gameMode == TwoMachinesPlay ?
9874                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9875                          score_buf,
9876                          prefixHint ? lastHint : "",
9877                          prefixHint ? " " : "" );
9878
9879                 if( buf1[0] != NULLCHAR ) {
9880                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9881
9882                     if( strlen(pv) > max_len ) {
9883                         if( appData.debugMode) {
9884                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9885                         }
9886                         pv[max_len+1] = '\0';
9887                     }
9888
9889                     strcat( thinkOutput, pv);
9890                 }
9891
9892                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9893                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9894                     DisplayMove(currentMove - 1);
9895                 }
9896                 return;
9897
9898             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9899                 /* crafty (9.25+) says "(only move) <move>"
9900                  * if there is only 1 legal move
9901                  */
9902                 sscanf(p, "(only move) %s", buf1);
9903                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9904                 sprintf(programStats.movelist, "%s (only move)", buf1);
9905                 programStats.depth = 1;
9906                 programStats.nr_moves = 1;
9907                 programStats.moves_left = 1;
9908                 programStats.nodes = 1;
9909                 programStats.time = 1;
9910                 programStats.got_only_move = 1;
9911
9912                 /* Not really, but we also use this member to
9913                    mean "line isn't going to change" (Crafty
9914                    isn't searching, so stats won't change) */
9915                 programStats.line_is_book = 1;
9916
9917                 SendProgramStatsToFrontend( cps, &programStats );
9918
9919                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9920                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9921                     DisplayMove(currentMove - 1);
9922                 }
9923                 return;
9924             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9925                               &time, &nodes, &plylev, &mvleft,
9926                               &mvtot, mvname) >= 5) {
9927                 /* The stat01: line is from Crafty (9.29+) in response
9928                    to the "." command */
9929                 programStats.seen_stat = 1;
9930                 cps->maybeThinking = TRUE;
9931
9932                 if (programStats.got_only_move || !appData.periodicUpdates)
9933                   return;
9934
9935                 programStats.depth = plylev;
9936                 programStats.time = time;
9937                 programStats.nodes = nodes;
9938                 programStats.moves_left = mvleft;
9939                 programStats.nr_moves = mvtot;
9940                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9941                 programStats.ok_to_send = 1;
9942                 programStats.movelist[0] = '\0';
9943
9944                 SendProgramStatsToFrontend( cps, &programStats );
9945
9946                 return;
9947
9948             } else if (strncmp(message,"++",2) == 0) {
9949                 /* Crafty 9.29+ outputs this */
9950                 programStats.got_fail = 2;
9951                 return;
9952
9953             } else if (strncmp(message,"--",2) == 0) {
9954                 /* Crafty 9.29+ outputs this */
9955                 programStats.got_fail = 1;
9956                 return;
9957
9958             } else if (thinkOutput[0] != NULLCHAR &&
9959                        strncmp(message, "    ", 4) == 0) {
9960                 unsigned message_len;
9961
9962                 p = message;
9963                 while (*p && *p == ' ') p++;
9964
9965                 message_len = strlen( p );
9966
9967                 /* [AS] Avoid buffer overflow */
9968                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9969                     strcat(thinkOutput, " ");
9970                     strcat(thinkOutput, p);
9971                 }
9972
9973                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9974                     strcat(programStats.movelist, " ");
9975                     strcat(programStats.movelist, p);
9976                 }
9977
9978                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9979                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9980                     DisplayMove(currentMove - 1);
9981                 }
9982                 return;
9983             }
9984         }
9985         else {
9986             buf1[0] = NULLCHAR;
9987
9988             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9989                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9990             {
9991                 ChessProgramStats cpstats;
9992
9993                 if (plyext != ' ' && plyext != '\t') {
9994                     time *= 100;
9995                 }
9996
9997                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9998                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9999                     curscore = -curscore;
10000                 }
10001
10002                 cpstats.depth = plylev;
10003                 cpstats.nodes = nodes;
10004                 cpstats.time = time;
10005                 cpstats.score = curscore;
10006                 cpstats.got_only_move = 0;
10007                 cpstats.movelist[0] = '\0';
10008
10009                 if (buf1[0] != NULLCHAR) {
10010                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10011                 }
10012
10013                 cpstats.ok_to_send = 0;
10014                 cpstats.line_is_book = 0;
10015                 cpstats.nr_moves = 0;
10016                 cpstats.moves_left = 0;
10017
10018                 SendProgramStatsToFrontend( cps, &cpstats );
10019             }
10020         }
10021     }
10022 }
10023
10024
10025 /* Parse a game score from the character string "game", and
10026    record it as the history of the current game.  The game
10027    score is NOT assumed to start from the standard position.
10028    The display is not updated in any way.
10029    */
10030 void
10031 ParseGameHistory (char *game)
10032 {
10033     ChessMove moveType;
10034     int fromX, fromY, toX, toY, boardIndex, mask;
10035     char promoChar;
10036     char *p, *q;
10037     char buf[MSG_SIZ];
10038
10039     if (appData.debugMode)
10040       fprintf(debugFP, "Parsing game history: %s\n", game);
10041
10042     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10043     gameInfo.site = StrSave(appData.icsHost);
10044     gameInfo.date = PGNDate();
10045     gameInfo.round = StrSave("-");
10046
10047     /* Parse out names of players */
10048     while (*game == ' ') game++;
10049     p = buf;
10050     while (*game != ' ') *p++ = *game++;
10051     *p = NULLCHAR;
10052     gameInfo.white = StrSave(buf);
10053     while (*game == ' ') game++;
10054     p = buf;
10055     while (*game != ' ' && *game != '\n') *p++ = *game++;
10056     *p = NULLCHAR;
10057     gameInfo.black = StrSave(buf);
10058
10059     /* Parse moves */
10060     boardIndex = blackPlaysFirst ? 1 : 0;
10061     yynewstr(game);
10062     for (;;) {
10063         yyboardindex = boardIndex;
10064         moveType = (ChessMove) Myylex();
10065         switch (moveType) {
10066           case IllegalMove:             /* maybe suicide chess, etc. */
10067   if (appData.debugMode) {
10068     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10069     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10070     setbuf(debugFP, NULL);
10071   }
10072           case WhitePromotion:
10073           case BlackPromotion:
10074           case WhiteNonPromotion:
10075           case BlackNonPromotion:
10076           case NormalMove:
10077           case FirstLeg:
10078           case WhiteCapturesEnPassant:
10079           case BlackCapturesEnPassant:
10080           case WhiteKingSideCastle:
10081           case WhiteQueenSideCastle:
10082           case BlackKingSideCastle:
10083           case BlackQueenSideCastle:
10084           case WhiteKingSideCastleWild:
10085           case WhiteQueenSideCastleWild:
10086           case BlackKingSideCastleWild:
10087           case BlackQueenSideCastleWild:
10088           /* PUSH Fabien */
10089           case WhiteHSideCastleFR:
10090           case WhiteASideCastleFR:
10091           case BlackHSideCastleFR:
10092           case BlackASideCastleFR:
10093           /* POP Fabien */
10094             fromX = currentMoveString[0] - AAA;
10095             fromY = currentMoveString[1] - ONE;
10096             toX = currentMoveString[2] - AAA;
10097             toY = currentMoveString[3] - ONE;
10098             promoChar = currentMoveString[4];
10099             break;
10100           case WhiteDrop:
10101           case BlackDrop:
10102             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10103             fromX = moveType == WhiteDrop ?
10104               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10105             (int) CharToPiece(ToLower(currentMoveString[0]));
10106             fromY = DROP_RANK;
10107             toX = currentMoveString[2] - AAA;
10108             toY = currentMoveString[3] - ONE;
10109             promoChar = NULLCHAR;
10110             break;
10111           case AmbiguousMove:
10112             /* bug? */
10113             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10114   if (appData.debugMode) {
10115     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10116     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10117     setbuf(debugFP, NULL);
10118   }
10119             DisplayError(buf, 0);
10120             return;
10121           case ImpossibleMove:
10122             /* bug? */
10123             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10124   if (appData.debugMode) {
10125     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10126     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10127     setbuf(debugFP, NULL);
10128   }
10129             DisplayError(buf, 0);
10130             return;
10131           case EndOfFile:
10132             if (boardIndex < backwardMostMove) {
10133                 /* Oops, gap.  How did that happen? */
10134                 DisplayError(_("Gap in move list"), 0);
10135                 return;
10136             }
10137             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10138             if (boardIndex > forwardMostMove) {
10139                 forwardMostMove = boardIndex;
10140             }
10141             return;
10142           case ElapsedTime:
10143             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10144                 strcat(parseList[boardIndex-1], " ");
10145                 strcat(parseList[boardIndex-1], yy_text);
10146             }
10147             continue;
10148           case Comment:
10149           case PGNTag:
10150           case NAG:
10151           default:
10152             /* ignore */
10153             continue;
10154           case WhiteWins:
10155           case BlackWins:
10156           case GameIsDrawn:
10157           case GameUnfinished:
10158             if (gameMode == IcsExamining) {
10159                 if (boardIndex < backwardMostMove) {
10160                     /* Oops, gap.  How did that happen? */
10161                     return;
10162                 }
10163                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10164                 return;
10165             }
10166             gameInfo.result = moveType;
10167             p = strchr(yy_text, '{');
10168             if (p == NULL) p = strchr(yy_text, '(');
10169             if (p == NULL) {
10170                 p = yy_text;
10171                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10172             } else {
10173                 q = strchr(p, *p == '{' ? '}' : ')');
10174                 if (q != NULL) *q = NULLCHAR;
10175                 p++;
10176             }
10177             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10178             gameInfo.resultDetails = StrSave(p);
10179             continue;
10180         }
10181         if (boardIndex >= forwardMostMove &&
10182             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10183             backwardMostMove = blackPlaysFirst ? 1 : 0;
10184             return;
10185         }
10186         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10187                                  fromY, fromX, toY, toX, promoChar,
10188                                  parseList[boardIndex]);
10189         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10190         /* currentMoveString is set as a side-effect of yylex */
10191         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10192         strcat(moveList[boardIndex], "\n");
10193         boardIndex++;
10194         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10195         mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10196         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10197           case MT_NONE:
10198           case MT_STALEMATE:
10199           default:
10200             break;
10201           case MT_CHECK:
10202             if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10203             if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10204                 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10205                 break;
10206             }
10207           case MT_CHECKMATE:
10208           case MT_STAINMATE:
10209             strcat(parseList[boardIndex - 1], "#");
10210             break;
10211         }
10212     }
10213 }
10214
10215
10216 /* Apply a move to the given board  */
10217 void
10218 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10219 {
10220   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10221   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10222
10223     /* [HGM] compute & store e.p. status and castling rights for new position */
10224     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10225
10226       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10227       oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10228       board[EP_STATUS] = EP_NONE;
10229       board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10230
10231   if (fromY == DROP_RANK) {
10232         /* must be first */
10233         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10234             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10235             return;
10236         }
10237         piece = board[toY][toX] = (ChessSquare) fromX;
10238   } else {
10239 //      ChessSquare victim;
10240       int i;
10241
10242       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10243 //           victim = board[killY][killX],
10244            killed = board[killY][killX],
10245            board[killY][killX] = EmptySquare,
10246            board[EP_STATUS] = EP_CAPTURE;
10247            if( kill2X >= 0 && kill2Y >= 0)
10248              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10249       }
10250
10251       if( board[toY][toX] != EmptySquare ) {
10252            board[EP_STATUS] = EP_CAPTURE;
10253            if( (fromX != toX || fromY != toY) && // not igui!
10254                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10255                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10256                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10257            }
10258       }
10259
10260       pawn = board[fromY][fromX];
10261       if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10262         if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10263             captured = board[lastRank][lastFile]; // remove victim
10264             board[lastRank][lastFile] = EmptySquare;
10265             pawn = EmptySquare; // kludge to suppress old e.p. code
10266         }
10267       }
10268       if( pawn == WhiteLance || pawn == BlackLance ) {
10269            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10270                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10271                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10272            }
10273       }
10274       if( pawn == WhitePawn ) {
10275            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10276                board[EP_STATUS] = EP_PAWN_MOVE;
10277            if( toY-fromY>=2) {
10278                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10279                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10280                         gameInfo.variant != VariantBerolina || toX < fromX)
10281                       board[EP_STATUS] = toX | berolina;
10282                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10283                         gameInfo.variant != VariantBerolina || toX > fromX)
10284                       board[EP_STATUS] = toX;
10285                board[LAST_TO] = toX + 256*toY;
10286            }
10287       } else
10288       if( pawn == BlackPawn ) {
10289            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10290                board[EP_STATUS] = EP_PAWN_MOVE;
10291            if( toY-fromY<= -2) {
10292                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10293                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10294                         gameInfo.variant != VariantBerolina || toX < fromX)
10295                       board[EP_STATUS] = toX | berolina;
10296                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10297                         gameInfo.variant != VariantBerolina || toX > fromX)
10298                       board[EP_STATUS] = toX;
10299                board[LAST_TO] = toX + 256*toY;
10300            }
10301        }
10302
10303        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10304        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10305        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10306        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10307
10308        for(i=0; i<nrCastlingRights; i++) {
10309            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10310               board[CASTLING][i] == toX   && castlingRank[i] == toY
10311              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10312        }
10313
10314        if(gameInfo.variant == VariantSChess) { // update virginity
10315            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10316            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10317            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10318            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10319        }
10320
10321      if (fromX == toX && fromY == toY && killX < 0) return;
10322
10323      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10324      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10325      if(gameInfo.variant == VariantKnightmate)
10326          king += (int) WhiteUnicorn - (int) WhiteKing;
10327
10328     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10329        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10330         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10331         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10332         board[EP_STATUS] = EP_NONE; // capture was fake!
10333     } else
10334     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10335         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10336         board[toY][toX] = piece;
10337         board[EP_STATUS] = EP_NONE; // capture was fake!
10338     } else
10339     /* Code added by Tord: */
10340     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10341     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10342         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10343       board[EP_STATUS] = EP_NONE; // capture was fake!
10344       board[fromY][fromX] = EmptySquare;
10345       board[toY][toX] = EmptySquare;
10346       if((toX > fromX) != (piece == WhiteRook)) {
10347         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10348       } else {
10349         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10350       }
10351     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10352                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10353       board[EP_STATUS] = EP_NONE;
10354       board[fromY][fromX] = EmptySquare;
10355       board[toY][toX] = EmptySquare;
10356       if((toX > fromX) != (piece == BlackRook)) {
10357         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10358       } else {
10359         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10360       }
10361     /* End of code added by Tord */
10362
10363     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10364         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10365         board[toY][toX] = piece;
10366     } else if (board[fromY][fromX] == king
10367         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10368         && toY == fromY && toX > fromX+1) {
10369         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10370                                                                                              ; // castle with nearest piece
10371         board[fromY][toX-1] = board[fromY][rookX];
10372         board[fromY][rookX] = EmptySquare;
10373         board[fromY][fromX] = EmptySquare;
10374         board[toY][toX] = king;
10375     } else if (board[fromY][fromX] == king
10376         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10377                && toY == fromY && toX < fromX-1) {
10378         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10379                                                                                   ; // castle with nearest piece
10380         board[fromY][toX+1] = board[fromY][rookX];
10381         board[fromY][rookX] = EmptySquare;
10382         board[fromY][fromX] = EmptySquare;
10383         board[toY][toX] = king;
10384     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10385                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10386                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10387                ) {
10388         /* white pawn promotion */
10389         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10390         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10391             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10392         board[fromY][fromX] = EmptySquare;
10393     } else if ((fromY >= BOARD_HEIGHT>>1)
10394                && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10395                && (toX != fromX)
10396                && gameInfo.variant != VariantXiangqi
10397                && gameInfo.variant != VariantBerolina
10398                && (pawn == WhitePawn)
10399                && (board[toY][toX] == EmptySquare)) {
10400         if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10401         board[fromY][fromX] = EmptySquare;
10402         board[toY][toX] = piece;
10403         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10404     } else if ((fromY == BOARD_HEIGHT-4)
10405                && (toX == fromX)
10406                && gameInfo.variant == VariantBerolina
10407                && (board[fromY][fromX] == WhitePawn)
10408                && (board[toY][toX] == EmptySquare)) {
10409         board[fromY][fromX] = EmptySquare;
10410         board[toY][toX] = WhitePawn;
10411         if(oldEP & EP_BEROLIN_A) {
10412                 captured = board[fromY][fromX-1];
10413                 board[fromY][fromX-1] = EmptySquare;
10414         }else{  captured = board[fromY][fromX+1];
10415                 board[fromY][fromX+1] = EmptySquare;
10416         }
10417     } else if (board[fromY][fromX] == king
10418         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10419                && toY == fromY && toX > fromX+1) {
10420         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10421                                                                                              ;
10422         board[fromY][toX-1] = board[fromY][rookX];
10423         board[fromY][rookX] = EmptySquare;
10424         board[fromY][fromX] = EmptySquare;
10425         board[toY][toX] = king;
10426     } else if (board[fromY][fromX] == king
10427         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10428                && toY == fromY && toX < fromX-1) {
10429         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10430                                                                                 ;
10431         board[fromY][toX+1] = board[fromY][rookX];
10432         board[fromY][rookX] = EmptySquare;
10433         board[fromY][fromX] = EmptySquare;
10434         board[toY][toX] = king;
10435     } else if (fromY == 7 && fromX == 3
10436                && board[fromY][fromX] == BlackKing
10437                && toY == 7 && toX == 5) {
10438         board[fromY][fromX] = EmptySquare;
10439         board[toY][toX] = BlackKing;
10440         board[fromY][7] = EmptySquare;
10441         board[toY][4] = BlackRook;
10442     } else if (fromY == 7 && fromX == 3
10443                && board[fromY][fromX] == BlackKing
10444                && toY == 7 && toX == 1) {
10445         board[fromY][fromX] = EmptySquare;
10446         board[toY][toX] = BlackKing;
10447         board[fromY][0] = EmptySquare;
10448         board[toY][2] = BlackRook;
10449     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10450                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10451                && toY < promoRank && promoChar
10452                ) {
10453         /* black pawn promotion */
10454         board[toY][toX] = CharToPiece(ToLower(promoChar));
10455         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10456             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10457         board[fromY][fromX] = EmptySquare;
10458     } else if ((fromY < BOARD_HEIGHT>>1)
10459                && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10460                && (toX != fromX)
10461                && gameInfo.variant != VariantXiangqi
10462                && gameInfo.variant != VariantBerolina
10463                && (pawn == BlackPawn)
10464                && (board[toY][toX] == EmptySquare)) {
10465         if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10466         board[fromY][fromX] = EmptySquare;
10467         board[toY][toX] = piece;
10468         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10469     } else if ((fromY == 3)
10470                && (toX == fromX)
10471                && gameInfo.variant == VariantBerolina
10472                && (board[fromY][fromX] == BlackPawn)
10473                && (board[toY][toX] == EmptySquare)) {
10474         board[fromY][fromX] = EmptySquare;
10475         board[toY][toX] = BlackPawn;
10476         if(oldEP & EP_BEROLIN_A) {
10477                 captured = board[fromY][fromX-1];
10478                 board[fromY][fromX-1] = EmptySquare;
10479         }else{  captured = board[fromY][fromX+1];
10480                 board[fromY][fromX+1] = EmptySquare;
10481         }
10482     } else {
10483         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10484         board[fromY][fromX] = EmptySquare;
10485         board[toY][toX] = piece;
10486     }
10487   }
10488
10489     if (gameInfo.holdingsWidth != 0) {
10490
10491       /* !!A lot more code needs to be written to support holdings  */
10492       /* [HGM] OK, so I have written it. Holdings are stored in the */
10493       /* penultimate board files, so they are automaticlly stored   */
10494       /* in the game history.                                       */
10495       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10496                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10497         /* Delete from holdings, by decreasing count */
10498         /* and erasing image if necessary            */
10499         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10500         if(p < (int) BlackPawn) { /* white drop */
10501              p -= (int)WhitePawn;
10502                  p = PieceToNumber((ChessSquare)p);
10503              if(p >= gameInfo.holdingsSize) p = 0;
10504              if(--board[p][BOARD_WIDTH-2] <= 0)
10505                   board[p][BOARD_WIDTH-1] = EmptySquare;
10506              if((int)board[p][BOARD_WIDTH-2] < 0)
10507                         board[p][BOARD_WIDTH-2] = 0;
10508         } else {                  /* black drop */
10509              p -= (int)BlackPawn;
10510                  p = PieceToNumber((ChessSquare)p);
10511              if(p >= gameInfo.holdingsSize) p = 0;
10512              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10513                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10514              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10515                         board[BOARD_HEIGHT-1-p][1] = 0;
10516         }
10517       }
10518       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10519           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10520         /* [HGM] holdings: Add to holdings, if holdings exist */
10521         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10522                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10523                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10524         }
10525         p = (int) captured;
10526         if (p >= (int) BlackPawn) {
10527           p -= (int)BlackPawn;
10528           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10529                   /* Restore shogi-promoted piece to its original  first */
10530                   captured = (ChessSquare) (DEMOTED(captured));
10531                   p = DEMOTED(p);
10532           }
10533           p = PieceToNumber((ChessSquare)p);
10534           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10535           board[p][BOARD_WIDTH-2]++;
10536           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10537         } else {
10538           p -= (int)WhitePawn;
10539           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10540                   captured = (ChessSquare) (DEMOTED(captured));
10541                   p = DEMOTED(p);
10542           }
10543           p = PieceToNumber((ChessSquare)p);
10544           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10545           board[BOARD_HEIGHT-1-p][1]++;
10546           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10547         }
10548       }
10549     } else if (gameInfo.variant == VariantAtomic) {
10550       if (captured != EmptySquare) {
10551         int y, x;
10552         for (y = toY-1; y <= toY+1; y++) {
10553           for (x = toX-1; x <= toX+1; x++) {
10554             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10555                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10556               board[y][x] = EmptySquare;
10557             }
10558           }
10559         }
10560         board[toY][toX] = EmptySquare;
10561       }
10562     }
10563
10564     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10565         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10566     } else
10567     if(promoChar == '+') {
10568         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10569         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10570         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10571           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10572     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10573         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10574         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10575            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10576         board[toY][toX] = newPiece;
10577     }
10578     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10579                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10580         // [HGM] superchess: take promotion piece out of holdings
10581         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10582         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10583             if(!--board[k][BOARD_WIDTH-2])
10584                 board[k][BOARD_WIDTH-1] = EmptySquare;
10585         } else {
10586             if(!--board[BOARD_HEIGHT-1-k][1])
10587                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10588         }
10589     }
10590 }
10591
10592 /* Updates forwardMostMove */
10593 void
10594 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10595 {
10596     int x = toX, y = toY, mask;
10597     char *s = parseList[forwardMostMove];
10598     ChessSquare p = boards[forwardMostMove][toY][toX];
10599 //    forwardMostMove++; // [HGM] bare: moved downstream
10600
10601     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10602     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10603     (void) CoordsToAlgebraic(boards[forwardMostMove],
10604                              PosFlags(forwardMostMove),
10605                              fromY, fromX, y, x, (killX < 0)*promoChar,
10606                              s);
10607     if(kill2X >= 0 && kill2Y >= 0)
10608         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10609     if(killX >= 0 && killY >= 0)
10610         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10611                                            toX + AAA, toY + ONE - '0', promoChar);
10612
10613     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10614         int timeLeft; static int lastLoadFlag=0; int king, piece;
10615         piece = boards[forwardMostMove][fromY][fromX];
10616         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10617         if(gameInfo.variant == VariantKnightmate)
10618             king += (int) WhiteUnicorn - (int) WhiteKing;
10619         if(forwardMostMove == 0) {
10620             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10621                 fprintf(serverMoves, "%s;", UserName());
10622             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10623                 fprintf(serverMoves, "%s;", second.tidy);
10624             fprintf(serverMoves, "%s;", first.tidy);
10625             if(gameMode == MachinePlaysWhite)
10626                 fprintf(serverMoves, "%s;", UserName());
10627             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10628                 fprintf(serverMoves, "%s;", second.tidy);
10629         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10630         lastLoadFlag = loadFlag;
10631         // print base move
10632         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10633         // print castling suffix
10634         if( toY == fromY && piece == king ) {
10635             if(toX-fromX > 1)
10636                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10637             if(fromX-toX >1)
10638                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10639         }
10640         // e.p. suffix
10641         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10642              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10643              boards[forwardMostMove][toY][toX] == EmptySquare
10644              && fromX != toX && fromY != toY)
10645                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10646         // promotion suffix
10647         if(promoChar != NULLCHAR) {
10648             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10649                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10650                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10651             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10652         }
10653         if(!loadFlag) {
10654                 char buf[MOVE_LEN*2], *p; int len;
10655             fprintf(serverMoves, "/%d/%d",
10656                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10657             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10658             else                      timeLeft = blackTimeRemaining/1000;
10659             fprintf(serverMoves, "/%d", timeLeft);
10660                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10661                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10662                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10663                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10664             fprintf(serverMoves, "/%s", buf);
10665         }
10666         fflush(serverMoves);
10667     }
10668
10669     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10670         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10671       return;
10672     }
10673     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10674     if (commentList[forwardMostMove+1] != NULL) {
10675         free(commentList[forwardMostMove+1]);
10676         commentList[forwardMostMove+1] = NULL;
10677     }
10678     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10679     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10680     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10681     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10682     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10683     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10684     adjustedClock = FALSE;
10685     gameInfo.result = GameUnfinished;
10686     if (gameInfo.resultDetails != NULL) {
10687         free(gameInfo.resultDetails);
10688         gameInfo.resultDetails = NULL;
10689     }
10690     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10691                               moveList[forwardMostMove - 1]);
10692     mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10693     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10694       case MT_NONE:
10695       case MT_STALEMATE:
10696       default:
10697         break;
10698       case MT_CHECK:
10699         if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10700         if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10701             if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10702             break;
10703         }
10704       case MT_CHECKMATE:
10705       case MT_STAINMATE:
10706         strcat(parseList[forwardMostMove - 1], "#");
10707         break;
10708     }
10709 }
10710
10711 /* Updates currentMove if not pausing */
10712 void
10713 ShowMove (int fromX, int fromY, int toX, int toY)
10714 {
10715     int instant = (gameMode == PlayFromGameFile) ?
10716         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10717     if(appData.noGUI) return;
10718     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10719         if (!instant) {
10720             if (forwardMostMove == currentMove + 1) {
10721                 AnimateMove(boards[forwardMostMove - 1],
10722                             fromX, fromY, toX, toY);
10723             }
10724         }
10725         currentMove = forwardMostMove;
10726     }
10727
10728     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10729
10730     if (instant) return;
10731
10732     DisplayMove(currentMove - 1);
10733     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10734             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10735                 SetHighlights(fromX, fromY, toX, toY);
10736             }
10737     }
10738     DrawPosition(FALSE, boards[currentMove]);
10739     DisplayBothClocks();
10740     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10741 }
10742
10743 void
10744 SendEgtPath (ChessProgramState *cps)
10745 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10746         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10747
10748         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10749
10750         while(*p) {
10751             char c, *q = name+1, *r, *s;
10752
10753             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10754             while(*p && *p != ',') *q++ = *p++;
10755             *q++ = ':'; *q = 0;
10756             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10757                 strcmp(name, ",nalimov:") == 0 ) {
10758                 // take nalimov path from the menu-changeable option first, if it is defined
10759               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10760                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10761             } else
10762             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10763                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10764                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10765                 s = r = StrStr(s, ":") + 1; // beginning of path info
10766                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10767                 c = *r; *r = 0;             // temporarily null-terminate path info
10768                     *--q = 0;               // strip of trailig ':' from name
10769                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10770                 *r = c;
10771                 SendToProgram(buf,cps);     // send egtbpath command for this format
10772             }
10773             if(*p == ',') p++; // read away comma to position for next format name
10774         }
10775 }
10776
10777 static int
10778 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10779 {
10780       int width = 8, height = 8, holdings = 0;             // most common sizes
10781       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10782       // correct the deviations default for each variant
10783       if( v == VariantXiangqi ) width = 9,  height = 10;
10784       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10785       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10786       if( v == VariantCapablanca || v == VariantCapaRandom ||
10787           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10788                                 width = 10;
10789       if( v == VariantCourier ) width = 12;
10790       if( v == VariantSuper )                            holdings = 8;
10791       if( v == VariantGreat )   width = 10,              holdings = 8;
10792       if( v == VariantSChess )                           holdings = 7;
10793       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10794       if( v == VariantChuChess) width = 10, height = 10;
10795       if( v == VariantChu )     width = 12, height = 12;
10796       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10797              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10798              holdingsSize >= 0 && holdingsSize != holdings;
10799 }
10800
10801 char variantError[MSG_SIZ];
10802
10803 char *
10804 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10805 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10806       char *p, *variant = VariantName(v);
10807       static char b[MSG_SIZ];
10808       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10809            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10810                                                holdingsSize, variant); // cook up sized variant name
10811            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10812            if(StrStr(list, b) == NULL) {
10813                // specific sized variant not known, check if general sizing allowed
10814                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10815                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10816                             boardWidth, boardHeight, holdingsSize, engine);
10817                    return NULL;
10818                }
10819                /* [HGM] here we really should compare with the maximum supported board size */
10820            }
10821       } else snprintf(b, MSG_SIZ,"%s", variant);
10822       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10823       p = StrStr(list, b);
10824       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10825       if(p == NULL) {
10826           // occurs not at all in list, or only as sub-string
10827           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10828           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10829               int l = strlen(variantError);
10830               char *q;
10831               while(p != list && p[-1] != ',') p--;
10832               q = strchr(p, ',');
10833               if(q) *q = NULLCHAR;
10834               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10835               if(q) *q= ',';
10836           }
10837           return NULL;
10838       }
10839       return b;
10840 }
10841
10842 void
10843 InitChessProgram (ChessProgramState *cps, int setup)
10844 /* setup needed to setup FRC opening position */
10845 {
10846     char buf[MSG_SIZ], *b;
10847     if (appData.noChessProgram) return;
10848     hintRequested = FALSE;
10849     bookRequested = FALSE;
10850
10851     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10852     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10853     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10854     if(cps->memSize) { /* [HGM] memory */
10855       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10856         SendToProgram(buf, cps);
10857     }
10858     SendEgtPath(cps); /* [HGM] EGT */
10859     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10860       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10861         SendToProgram(buf, cps);
10862     }
10863
10864     setboardSpoiledMachineBlack = FALSE;
10865     SendToProgram(cps->initString, cps);
10866     if (gameInfo.variant != VariantNormal &&
10867         gameInfo.variant != VariantLoadable
10868         /* [HGM] also send variant if board size non-standard */
10869         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10870
10871       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10872                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10873
10874       if (b == NULL) {
10875         VariantClass v;
10876         char c, *q = cps->variants, *p = strchr(q, ',');
10877         if(p) *p = NULLCHAR;
10878         v = StringToVariant(q);
10879         DisplayError(variantError, 0);
10880         if(v != VariantUnknown && cps == &first) {
10881             int w, h, s;
10882             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10883                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10884             ASSIGN(appData.variant, q);
10885             Reset(TRUE, FALSE);
10886         }
10887         if(p) *p = ',';
10888         return;
10889       }
10890
10891       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10892       SendToProgram(buf, cps);
10893     }
10894     currentlyInitializedVariant = gameInfo.variant;
10895
10896     /* [HGM] send opening position in FRC to first engine */
10897     if(setup) {
10898           SendToProgram("force\n", cps);
10899           SendBoard(cps, 0);
10900           /* engine is now in force mode! Set flag to wake it up after first move. */
10901           setboardSpoiledMachineBlack = 1;
10902     }
10903
10904     if (cps->sendICS) {
10905       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10906       SendToProgram(buf, cps);
10907     }
10908     cps->maybeThinking = FALSE;
10909     cps->offeredDraw = 0;
10910     if (!appData.icsActive) {
10911         SendTimeControl(cps, movesPerSession, timeControl,
10912                         timeIncrement, appData.searchDepth,
10913                         searchTime);
10914     }
10915     if (appData.showThinking
10916         // [HGM] thinking: four options require thinking output to be sent
10917         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10918                                 ) {
10919         SendToProgram("post\n", cps);
10920     }
10921     SendToProgram("hard\n", cps);
10922     if (!appData.ponderNextMove) {
10923         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10924            it without being sure what state we are in first.  "hard"
10925            is not a toggle, so that one is OK.
10926          */
10927         SendToProgram("easy\n", cps);
10928     }
10929     if (cps->usePing) {
10930       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10931       SendToProgram(buf, cps);
10932     }
10933     cps->initDone = TRUE;
10934     ClearEngineOutputPane(cps == &second);
10935 }
10936
10937
10938 char *
10939 ResendOptions (ChessProgramState *cps, int toEngine)
10940 { // send the stored value of the options
10941   int i;
10942   static char buf2[MSG_SIZ*10];
10943   char buf[MSG_SIZ], *p = buf2;
10944   Option *opt = cps->option;
10945   *p = NULLCHAR;
10946   for(i=0; i<cps->nrOptions; i++, opt++) {
10947       *buf = NULLCHAR;
10948       switch(opt->type) {
10949         case Spin:
10950         case Slider:
10951         case CheckBox:
10952             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10953             snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
10954           break;
10955         case ComboBox:
10956             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10957             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
10958           break;
10959         default:
10960             if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
10961             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
10962           break;
10963         case Button:
10964         case SaveButton:
10965           continue;
10966       }
10967       if(*buf) {
10968         if(toEngine) {
10969           snprintf(buf2, MSG_SIZ, "option %s\n", buf);
10970           SendToProgram(buf2, cps);
10971         } else {
10972           if(p != buf2) *p++ = ',';
10973           strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
10974           while(*p) p++;
10975         }
10976       }
10977   }
10978   return buf2;
10979 }
10980
10981 void
10982 StartChessProgram (ChessProgramState *cps)
10983 {
10984     char buf[MSG_SIZ];
10985     int err;
10986
10987     if (appData.noChessProgram) return;
10988     cps->initDone = FALSE;
10989
10990     if (strcmp(cps->host, "localhost") == 0) {
10991         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10992     } else if (*appData.remoteShell == NULLCHAR) {
10993         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10994     } else {
10995         if (*appData.remoteUser == NULLCHAR) {
10996           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10997                     cps->program);
10998         } else {
10999           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11000                     cps->host, appData.remoteUser, cps->program);
11001         }
11002         err = StartChildProcess(buf, "", &cps->pr);
11003     }
11004
11005     if (err != 0) {
11006       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11007         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11008         if(cps != &first) return;
11009         appData.noChessProgram = TRUE;
11010         ThawUI();
11011         SetNCPMode();
11012 //      DisplayFatalError(buf, err, 1);
11013 //      cps->pr = NoProc;
11014 //      cps->isr = NULL;
11015         return;
11016     }
11017
11018     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11019     if (cps->protocolVersion > 1) {
11020       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11021       if(!cps->reload) { // do not clear options when reloading because of -xreuse
11022         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11023         cps->comboCnt = 0;  //                and values of combo boxes
11024       }
11025       SendToProgram(buf, cps);
11026       if(cps->reload) ResendOptions(cps, TRUE);
11027     } else {
11028       SendToProgram("xboard\n", cps);
11029     }
11030 }
11031
11032 void
11033 TwoMachinesEventIfReady P((void))
11034 {
11035   static int curMess = 0;
11036   if (first.lastPing != first.lastPong) {
11037     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11038     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11039     return;
11040   }
11041   if (second.lastPing != second.lastPong) {
11042     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11043     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11044     return;
11045   }
11046   DisplayMessage("", ""); curMess = 0;
11047   TwoMachinesEvent();
11048 }
11049
11050 char *
11051 MakeName (char *template)
11052 {
11053     time_t clock;
11054     struct tm *tm;
11055     static char buf[MSG_SIZ];
11056     char *p = buf;
11057     int i;
11058
11059     clock = time((time_t *)NULL);
11060     tm = localtime(&clock);
11061
11062     while(*p++ = *template++) if(p[-1] == '%') {
11063         switch(*template++) {
11064           case 0:   *p = 0; return buf;
11065           case 'Y': i = tm->tm_year+1900; break;
11066           case 'y': i = tm->tm_year-100; break;
11067           case 'M': i = tm->tm_mon+1; break;
11068           case 'd': i = tm->tm_mday; break;
11069           case 'h': i = tm->tm_hour; break;
11070           case 'm': i = tm->tm_min; break;
11071           case 's': i = tm->tm_sec; break;
11072           default:  i = 0;
11073         }
11074         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11075     }
11076     return buf;
11077 }
11078
11079 int
11080 CountPlayers (char *p)
11081 {
11082     int n = 0;
11083     while(p = strchr(p, '\n')) p++, n++; // count participants
11084     return n;
11085 }
11086
11087 FILE *
11088 WriteTourneyFile (char *results, FILE *f)
11089 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11090     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11091     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11092         // create a file with tournament description
11093         fprintf(f, "-participants {%s}\n", appData.participants);
11094         fprintf(f, "-seedBase %d\n", appData.seedBase);
11095         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11096         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11097         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11098         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11099         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11100         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11101         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11102         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11103         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11104         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11105         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11106         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11107         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11108         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11109         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11110         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11111         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11112         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11113         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11114         fprintf(f, "-smpCores %d\n", appData.smpCores);
11115         if(searchTime > 0)
11116                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11117         else {
11118                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11119                 fprintf(f, "-tc %s\n", appData.timeControl);
11120                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11121         }
11122         fprintf(f, "-results \"%s\"\n", results);
11123     }
11124     return f;
11125 }
11126
11127 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11128
11129 void
11130 Substitute (char *participants, int expunge)
11131 {
11132     int i, changed, changes=0, nPlayers=0;
11133     char *p, *q, *r, buf[MSG_SIZ];
11134     if(participants == NULL) return;
11135     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11136     r = p = participants; q = appData.participants;
11137     while(*p && *p == *q) {
11138         if(*p == '\n') r = p+1, nPlayers++;
11139         p++; q++;
11140     }
11141     if(*p) { // difference
11142         while(*p && *p++ != '\n')
11143                                  ;
11144         while(*q && *q++ != '\n')
11145                                  ;
11146       changed = nPlayers;
11147         changes = 1 + (strcmp(p, q) != 0);
11148     }
11149     if(changes == 1) { // a single engine mnemonic was changed
11150         q = r; while(*q) nPlayers += (*q++ == '\n');
11151         p = buf; while(*r && (*p = *r++) != '\n') p++;
11152         *p = NULLCHAR;
11153         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11154         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11155         if(mnemonic[i]) { // The substitute is valid
11156             FILE *f;
11157             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11158                 flock(fileno(f), LOCK_EX);
11159                 ParseArgsFromFile(f);
11160                 fseek(f, 0, SEEK_SET);
11161                 FREE(appData.participants); appData.participants = participants;
11162                 if(expunge) { // erase results of replaced engine
11163                     int len = strlen(appData.results), w, b, dummy;
11164                     for(i=0; i<len; i++) {
11165                         Pairing(i, nPlayers, &w, &b, &dummy);
11166                         if((w == changed || b == changed) && appData.results[i] == '*') {
11167                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11168                             fclose(f);
11169                             return;
11170                         }
11171                     }
11172                     for(i=0; i<len; i++) {
11173                         Pairing(i, nPlayers, &w, &b, &dummy);
11174                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11175                     }
11176                 }
11177                 WriteTourneyFile(appData.results, f);
11178                 fclose(f); // release lock
11179                 return;
11180             }
11181         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11182     }
11183     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11184     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11185     free(participants);
11186     return;
11187 }
11188
11189 int
11190 CheckPlayers (char *participants)
11191 {
11192         int i;
11193         char buf[MSG_SIZ], *p;
11194         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11195         while(p = strchr(participants, '\n')) {
11196             *p = NULLCHAR;
11197             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11198             if(!mnemonic[i]) {
11199                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11200                 *p = '\n';
11201                 DisplayError(buf, 0);
11202                 return 1;
11203             }
11204             *p = '\n';
11205             participants = p + 1;
11206         }
11207         return 0;
11208 }
11209
11210 int
11211 CreateTourney (char *name)
11212 {
11213         FILE *f;
11214         if(matchMode && strcmp(name, appData.tourneyFile)) {
11215              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11216         }
11217         if(name[0] == NULLCHAR) {
11218             if(appData.participants[0])
11219                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11220             return 0;
11221         }
11222         f = fopen(name, "r");
11223         if(f) { // file exists
11224             ASSIGN(appData.tourneyFile, name);
11225             ParseArgsFromFile(f); // parse it
11226         } else {
11227             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11228             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11229                 DisplayError(_("Not enough participants"), 0);
11230                 return 0;
11231             }
11232             if(CheckPlayers(appData.participants)) return 0;
11233             ASSIGN(appData.tourneyFile, name);
11234             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11235             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11236         }
11237         fclose(f);
11238         appData.noChessProgram = FALSE;
11239         appData.clockMode = TRUE;
11240         SetGNUMode();
11241         return 1;
11242 }
11243
11244 int
11245 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11246 {
11247     char buf[2*MSG_SIZ], *p, *q;
11248     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11249     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11250     skip = !all && group[0]; // if group requested, we start in skip mode
11251     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11252         p = names; q = buf; header = 0;
11253         while(*p && *p != '\n') *q++ = *p++;
11254         *q = 0;
11255         if(*p == '\n') p++;
11256         if(buf[0] == '#') {
11257             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11258             depth++; // we must be entering a new group
11259             if(all) continue; // suppress printing group headers when complete list requested
11260             header = 1;
11261             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11262         }
11263         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11264         if(engineList[i]) free(engineList[i]);
11265         engineList[i] = strdup(buf);
11266         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11267         if(engineMnemonic[i]) free(engineMnemonic[i]);
11268         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11269             strcat(buf, " (");
11270             sscanf(q + 8, "%s", buf + strlen(buf));
11271             strcat(buf, ")");
11272         }
11273         engineMnemonic[i] = strdup(buf);
11274         i++;
11275     }
11276     engineList[i] = engineMnemonic[i] = NULL;
11277     return i;
11278 }
11279
11280 void
11281 SaveEngineSettings (int n)
11282 {
11283     int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11284     if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11285     p = strstr(firstChessProgramNames, currentEngine[n]);
11286     if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11287     optionSettings = ResendOptions(n ? &second : &first, FALSE);
11288     len = strlen(currentEngine[n]);
11289     q = p + len; *p = 0; // cut list into head and tail piece
11290     s = strstr(currentEngine[n], "firstOptions");
11291     if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11292         char *r = s + 14;
11293         while(*r && *r != s[13]) r++;
11294         s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11295         snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11296     } else if(*optionSettings) {
11297         snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11298     }
11299     ASSIGN(currentEngine[n], buf); // updated engine line
11300     len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11301     s = malloc(len);
11302     snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11303     FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11304 }
11305
11306 // following implemented as macro to avoid type limitations
11307 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11308
11309 void
11310 SwapEngines (int n)
11311 {   // swap settings for first engine and other engine (so far only some selected options)
11312     int h;
11313     char *p;
11314     if(n == 0) return;
11315     SWAP(directory, p)
11316     SWAP(chessProgram, p)
11317     SWAP(isUCI, h)
11318     SWAP(hasOwnBookUCI, h)
11319     SWAP(protocolVersion, h)
11320     SWAP(reuse, h)
11321     SWAP(scoreIsAbsolute, h)
11322     SWAP(timeOdds, h)
11323     SWAP(logo, p)
11324     SWAP(pgnName, p)
11325     SWAP(pvSAN, h)
11326     SWAP(engOptions, p)
11327     SWAP(engInitString, p)
11328     SWAP(computerString, p)
11329     SWAP(features, p)
11330     SWAP(fenOverride, p)
11331     SWAP(NPS, h)
11332     SWAP(accumulateTC, h)
11333     SWAP(drawDepth, h)
11334     SWAP(host, p)
11335     SWAP(pseudo, h)
11336 }
11337
11338 int
11339 GetEngineLine (char *s, int n)
11340 {
11341     int i;
11342     char buf[MSG_SIZ];
11343     extern char *icsNames;
11344     if(!s || !*s) return 0;
11345     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11346     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11347     if(!mnemonic[i]) return 0;
11348     if(n == 11) return 1; // just testing if there was a match
11349     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11350     if(n == 1) SwapEngines(n);
11351     ParseArgsFromString(buf);
11352     if(n == 1) SwapEngines(n);
11353     if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11354     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11355         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11356         ParseArgsFromString(buf);
11357     }
11358     return 1;
11359 }
11360
11361 int
11362 SetPlayer (int player, char *p)
11363 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11364     int i;
11365     char buf[MSG_SIZ], *engineName;
11366     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11367     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11368     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11369     if(mnemonic[i]) {
11370         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11371         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11372         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11373         ParseArgsFromString(buf);
11374     } else { // no engine with this nickname is installed!
11375         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11376         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11377         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11378         ModeHighlight();
11379         DisplayError(buf, 0);
11380         return 0;
11381     }
11382     free(engineName);
11383     return i;
11384 }
11385
11386 char *recentEngines;
11387
11388 void
11389 RecentEngineEvent (int nr)
11390 {
11391     int n;
11392 //    SwapEngines(1); // bump first to second
11393 //    ReplaceEngine(&second, 1); // and load it there
11394     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11395     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11396     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11397         ReplaceEngine(&first, 0);
11398         FloatToFront(&appData.recentEngineList, command[n]);
11399     }
11400 }
11401
11402 int
11403 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11404 {   // determine players from game number
11405     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11406
11407     if(appData.tourneyType == 0) {
11408         roundsPerCycle = (nPlayers - 1) | 1;
11409         pairingsPerRound = nPlayers / 2;
11410     } else if(appData.tourneyType > 0) {
11411         roundsPerCycle = nPlayers - appData.tourneyType;
11412         pairingsPerRound = appData.tourneyType;
11413     }
11414     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11415     gamesPerCycle = gamesPerRound * roundsPerCycle;
11416     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11417     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11418     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11419     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11420     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11421     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11422
11423     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11424     if(appData.roundSync) *syncInterval = gamesPerRound;
11425
11426     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11427
11428     if(appData.tourneyType == 0) {
11429         if(curPairing == (nPlayers-1)/2 ) {
11430             *whitePlayer = curRound;
11431             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11432         } else {
11433             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11434             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11435             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11436             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11437         }
11438     } else if(appData.tourneyType > 1) {
11439         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11440         *whitePlayer = curRound + appData.tourneyType;
11441     } else if(appData.tourneyType > 0) {
11442         *whitePlayer = curPairing;
11443         *blackPlayer = curRound + appData.tourneyType;
11444     }
11445
11446     // take care of white/black alternation per round.
11447     // For cycles and games this is already taken care of by default, derived from matchGame!
11448     return curRound & 1;
11449 }
11450
11451 int
11452 NextTourneyGame (int nr, int *swapColors)
11453 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11454     char *p, *q;
11455     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11456     FILE *tf;
11457     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11458     tf = fopen(appData.tourneyFile, "r");
11459     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11460     ParseArgsFromFile(tf); fclose(tf);
11461     InitTimeControls(); // TC might be altered from tourney file
11462
11463     nPlayers = CountPlayers(appData.participants); // count participants
11464     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11465     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11466
11467     if(syncInterval) {
11468         p = q = appData.results;
11469         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11470         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11471             DisplayMessage(_("Waiting for other game(s)"),"");
11472             waitingForGame = TRUE;
11473             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11474             return 0;
11475         }
11476         waitingForGame = FALSE;
11477     }
11478
11479     if(appData.tourneyType < 0) {
11480         if(nr>=0 && !pairingReceived) {
11481             char buf[1<<16];
11482             if(pairing.pr == NoProc) {
11483                 if(!appData.pairingEngine[0]) {
11484                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11485                     return 0;
11486                 }
11487                 StartChessProgram(&pairing); // starts the pairing engine
11488             }
11489             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11490             SendToProgram(buf, &pairing);
11491             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11492             SendToProgram(buf, &pairing);
11493             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11494         }
11495         pairingReceived = 0;                              // ... so we continue here
11496         *swapColors = 0;
11497         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11498         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11499         matchGame = 1; roundNr = nr / syncInterval + 1;
11500     }
11501
11502     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11503
11504     // redefine engines, engine dir, etc.
11505     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11506     if(first.pr == NoProc) {
11507       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11508       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11509     }
11510     if(second.pr == NoProc) {
11511       SwapEngines(1);
11512       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11513       SwapEngines(1);         // and make that valid for second engine by swapping
11514       InitEngine(&second, 1);
11515     }
11516     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11517     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11518     return OK;
11519 }
11520
11521 void
11522 NextMatchGame ()
11523 {   // performs game initialization that does not invoke engines, and then tries to start the game
11524     int res, firstWhite, swapColors = 0;
11525     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11526     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
11527         char buf[MSG_SIZ];
11528         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11529         if(strcmp(buf, currentDebugFile)) { // name has changed
11530             FILE *f = fopen(buf, "w");
11531             if(f) { // if opening the new file failed, just keep using the old one
11532                 ASSIGN(currentDebugFile, buf);
11533                 fclose(debugFP);
11534                 debugFP = f;
11535             }
11536             if(appData.serverFileName) {
11537                 if(serverFP) fclose(serverFP);
11538                 serverFP = fopen(appData.serverFileName, "w");
11539                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11540                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11541             }
11542         }
11543     }
11544     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11545     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11546     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11547     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11548     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11549     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11550     Reset(FALSE, first.pr != NoProc);
11551     res = LoadGameOrPosition(matchGame); // setup game
11552     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11553     if(!res) return; // abort when bad game/pos file
11554     if(appData.epd) {// in EPD mode we make sure first engine is to move
11555         firstWhite = !(forwardMostMove & 1);
11556         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11557         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11558     }
11559     TwoMachinesEvent();
11560 }
11561
11562 void
11563 UserAdjudicationEvent (int result)
11564 {
11565     ChessMove gameResult = GameIsDrawn;
11566
11567     if( result > 0 ) {
11568         gameResult = WhiteWins;
11569     }
11570     else if( result < 0 ) {
11571         gameResult = BlackWins;
11572     }
11573
11574     if( gameMode == TwoMachinesPlay ) {
11575         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11576     }
11577 }
11578
11579
11580 // [HGM] save: calculate checksum of game to make games easily identifiable
11581 int
11582 StringCheckSum (char *s)
11583 {
11584         int i = 0;
11585         if(s==NULL) return 0;
11586         while(*s) i = i*259 + *s++;
11587         return i;
11588 }
11589
11590 int
11591 GameCheckSum ()
11592 {
11593         int i, sum=0;
11594         for(i=backwardMostMove; i<forwardMostMove; i++) {
11595                 sum += pvInfoList[i].depth;
11596                 sum += StringCheckSum(parseList[i]);
11597                 sum += StringCheckSum(commentList[i]);
11598                 sum *= 261;
11599         }
11600         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11601         return sum + StringCheckSum(commentList[i]);
11602 } // end of save patch
11603
11604 void
11605 GameEnds (ChessMove result, char *resultDetails, int whosays)
11606 {
11607     GameMode nextGameMode;
11608     int isIcsGame;
11609     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11610
11611     if(endingGame) return; /* [HGM] crash: forbid recursion */
11612     endingGame = 1;
11613     if(twoBoards) { // [HGM] dual: switch back to one board
11614         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11615         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11616     }
11617     if (appData.debugMode) {
11618       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11619               result, resultDetails ? resultDetails : "(null)", whosays);
11620     }
11621
11622     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11623
11624     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11625
11626     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11627         /* If we are playing on ICS, the server decides when the
11628            game is over, but the engine can offer to draw, claim
11629            a draw, or resign.
11630          */
11631 #if ZIPPY
11632         if (appData.zippyPlay && first.initDone) {
11633             if (result == GameIsDrawn) {
11634                 /* In case draw still needs to be claimed */
11635                 SendToICS(ics_prefix);
11636                 SendToICS("draw\n");
11637             } else if (StrCaseStr(resultDetails, "resign")) {
11638                 SendToICS(ics_prefix);
11639                 SendToICS("resign\n");
11640             }
11641         }
11642 #endif
11643         endingGame = 0; /* [HGM] crash */
11644         return;
11645     }
11646
11647     /* If we're loading the game from a file, stop */
11648     if (whosays == GE_FILE) {
11649       (void) StopLoadGameTimer();
11650       gameFileFP = NULL;
11651     }
11652
11653     /* Cancel draw offers */
11654     first.offeredDraw = second.offeredDraw = 0;
11655
11656     /* If this is an ICS game, only ICS can really say it's done;
11657        if not, anyone can. */
11658     isIcsGame = (gameMode == IcsPlayingWhite ||
11659                  gameMode == IcsPlayingBlack ||
11660                  gameMode == IcsObserving    ||
11661                  gameMode == IcsExamining);
11662
11663     if (!isIcsGame || whosays == GE_ICS) {
11664         /* OK -- not an ICS game, or ICS said it was done */
11665         StopClocks();
11666         if (!isIcsGame && !appData.noChessProgram)
11667           SetUserThinkingEnables();
11668
11669         /* [HGM] if a machine claims the game end we verify this claim */
11670         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11671             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11672                 char claimer;
11673                 ChessMove trueResult = (ChessMove) -1;
11674
11675                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11676                                             first.twoMachinesColor[0] :
11677                                             second.twoMachinesColor[0] ;
11678
11679                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11680                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11681                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11682                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11683                 } else
11684                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11685                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11686                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11687                 } else
11688                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11689                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11690                 }
11691
11692                 // now verify win claims, but not in drop games, as we don't understand those yet
11693                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11694                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11695                     (result == WhiteWins && claimer == 'w' ||
11696                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11697                       if (appData.debugMode) {
11698                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11699                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11700                       }
11701                       if(result != trueResult) {
11702                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11703                               result = claimer == 'w' ? BlackWins : WhiteWins;
11704                               resultDetails = buf;
11705                       }
11706                 } else
11707                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11708                     && (forwardMostMove <= backwardMostMove ||
11709                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11710                         (claimer=='b')==(forwardMostMove&1))
11711                                                                                   ) {
11712                       /* [HGM] verify: draws that were not flagged are false claims */
11713                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11714                       result = claimer == 'w' ? BlackWins : WhiteWins;
11715                       resultDetails = buf;
11716                 }
11717                 /* (Claiming a loss is accepted no questions asked!) */
11718             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11719                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11720                 result = GameUnfinished;
11721                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11722             }
11723             /* [HGM] bare: don't allow bare King to win */
11724             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11725                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11726                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11727                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11728                && result != GameIsDrawn)
11729             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11730                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11731                         int p = (int)boards[forwardMostMove][i][j] - color;
11732                         if(p >= 0 && p <= (int)WhiteKing) k++;
11733                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11734                 }
11735                 if (appData.debugMode) {
11736                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11737                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11738                 }
11739                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11740                         result = GameIsDrawn;
11741                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11742                         resultDetails = buf;
11743                 }
11744             }
11745         }
11746
11747
11748         if(serverMoves != NULL && !loadFlag) { char c = '=';
11749             if(result==WhiteWins) c = '+';
11750             if(result==BlackWins) c = '-';
11751             if(resultDetails != NULL)
11752                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11753         }
11754         if (resultDetails != NULL) {
11755             gameInfo.result = result;
11756             gameInfo.resultDetails = StrSave(resultDetails);
11757
11758             /* display last move only if game was not loaded from file */
11759             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11760                 DisplayMove(currentMove - 1);
11761
11762             if (forwardMostMove != 0) {
11763                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11764                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11765                                                                 ) {
11766                     if (*appData.saveGameFile != NULLCHAR) {
11767                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11768                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11769                         else
11770                         SaveGameToFile(appData.saveGameFile, TRUE);
11771                     } else if (appData.autoSaveGames) {
11772                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11773                     }
11774                     if (*appData.savePositionFile != NULLCHAR) {
11775                         SavePositionToFile(appData.savePositionFile);
11776                     }
11777                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11778                 }
11779             }
11780
11781             /* Tell program how game ended in case it is learning */
11782             /* [HGM] Moved this to after saving the PGN, just in case */
11783             /* engine died and we got here through time loss. In that */
11784             /* case we will get a fatal error writing the pipe, which */
11785             /* would otherwise lose us the PGN.                       */
11786             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11787             /* output during GameEnds should never be fatal anymore   */
11788             if (gameMode == MachinePlaysWhite ||
11789                 gameMode == MachinePlaysBlack ||
11790                 gameMode == TwoMachinesPlay ||
11791                 gameMode == IcsPlayingWhite ||
11792                 gameMode == IcsPlayingBlack ||
11793                 gameMode == BeginningOfGame) {
11794                 char buf[MSG_SIZ];
11795                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11796                         resultDetails);
11797                 if (first.pr != NoProc) {
11798                     SendToProgram(buf, &first);
11799                 }
11800                 if (second.pr != NoProc &&
11801                     gameMode == TwoMachinesPlay) {
11802                     SendToProgram(buf, &second);
11803                 }
11804             }
11805         }
11806
11807         if (appData.icsActive) {
11808             if (appData.quietPlay &&
11809                 (gameMode == IcsPlayingWhite ||
11810                  gameMode == IcsPlayingBlack)) {
11811                 SendToICS(ics_prefix);
11812                 SendToICS("set shout 1\n");
11813             }
11814             nextGameMode = IcsIdle;
11815             ics_user_moved = FALSE;
11816             /* clean up premove.  It's ugly when the game has ended and the
11817              * premove highlights are still on the board.
11818              */
11819             if (gotPremove) {
11820               gotPremove = FALSE;
11821               ClearPremoveHighlights();
11822               DrawPosition(FALSE, boards[currentMove]);
11823             }
11824             if (whosays == GE_ICS) {
11825                 switch (result) {
11826                 case WhiteWins:
11827                     if (gameMode == IcsPlayingWhite)
11828                         PlayIcsWinSound();
11829                     else if(gameMode == IcsPlayingBlack)
11830                         PlayIcsLossSound();
11831                     break;
11832                 case BlackWins:
11833                     if (gameMode == IcsPlayingBlack)
11834                         PlayIcsWinSound();
11835                     else if(gameMode == IcsPlayingWhite)
11836                         PlayIcsLossSound();
11837                     break;
11838                 case GameIsDrawn:
11839                     PlayIcsDrawSound();
11840                     break;
11841                 default:
11842                     PlayIcsUnfinishedSound();
11843                 }
11844             }
11845             if(appData.quitNext) { ExitEvent(0); return; }
11846         } else if (gameMode == EditGame ||
11847                    gameMode == PlayFromGameFile ||
11848                    gameMode == AnalyzeMode ||
11849                    gameMode == AnalyzeFile) {
11850             nextGameMode = gameMode;
11851         } else {
11852             nextGameMode = EndOfGame;
11853         }
11854         pausing = FALSE;
11855         ModeHighlight();
11856     } else {
11857         nextGameMode = gameMode;
11858     }
11859
11860     if (appData.noChessProgram) {
11861         gameMode = nextGameMode;
11862         ModeHighlight();
11863         endingGame = 0; /* [HGM] crash */
11864         return;
11865     }
11866
11867     if (first.reuse) {
11868         /* Put first chess program into idle state */
11869         if (first.pr != NoProc &&
11870             (gameMode == MachinePlaysWhite ||
11871              gameMode == MachinePlaysBlack ||
11872              gameMode == TwoMachinesPlay ||
11873              gameMode == IcsPlayingWhite ||
11874              gameMode == IcsPlayingBlack ||
11875              gameMode == BeginningOfGame)) {
11876             SendToProgram("force\n", &first);
11877             if (first.usePing) {
11878               char buf[MSG_SIZ];
11879               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11880               SendToProgram(buf, &first);
11881             }
11882         }
11883     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11884         /* Kill off first chess program */
11885         if (first.isr != NULL)
11886           RemoveInputSource(first.isr);
11887         first.isr = NULL;
11888
11889         if (first.pr != NoProc) {
11890             ExitAnalyzeMode();
11891             DoSleep( appData.delayBeforeQuit );
11892             SendToProgram("quit\n", &first);
11893             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11894             first.reload = TRUE;
11895         }
11896         first.pr = NoProc;
11897     }
11898     if (second.reuse) {
11899         /* Put second chess program into idle state */
11900         if (second.pr != NoProc &&
11901             gameMode == TwoMachinesPlay) {
11902             SendToProgram("force\n", &second);
11903             if (second.usePing) {
11904               char buf[MSG_SIZ];
11905               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11906               SendToProgram(buf, &second);
11907             }
11908         }
11909     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11910         /* Kill off second chess program */
11911         if (second.isr != NULL)
11912           RemoveInputSource(second.isr);
11913         second.isr = NULL;
11914
11915         if (second.pr != NoProc) {
11916             DoSleep( appData.delayBeforeQuit );
11917             SendToProgram("quit\n", &second);
11918             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11919             second.reload = TRUE;
11920         }
11921         second.pr = NoProc;
11922     }
11923
11924     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11925         char resChar = '=';
11926         switch (result) {
11927         case WhiteWins:
11928           resChar = '+';
11929           if (first.twoMachinesColor[0] == 'w') {
11930             first.matchWins++;
11931           } else {
11932             second.matchWins++;
11933           }
11934           break;
11935         case BlackWins:
11936           resChar = '-';
11937           if (first.twoMachinesColor[0] == 'b') {
11938             first.matchWins++;
11939           } else {
11940             second.matchWins++;
11941           }
11942           break;
11943         case GameUnfinished:
11944           resChar = ' ';
11945         default:
11946           break;
11947         }
11948
11949         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11950         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11951             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11952             ReserveGame(nextGame, resChar); // sets nextGame
11953             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11954             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11955         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11956
11957         if (nextGame <= appData.matchGames && !abortMatch) {
11958             gameMode = nextGameMode;
11959             matchGame = nextGame; // this will be overruled in tourney mode!
11960             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11961             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11962             endingGame = 0; /* [HGM] crash */
11963             return;
11964         } else {
11965             gameMode = nextGameMode;
11966             if(appData.epd) {
11967                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11968                 OutputKibitz(2, buf);
11969                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11970                 OutputKibitz(2, buf);
11971                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11972                 if(second.matchWins) OutputKibitz(2, buf);
11973                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11974                 OutputKibitz(2, buf);
11975             }
11976             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11977                      first.tidy, second.tidy,
11978                      first.matchWins, second.matchWins,
11979                      appData.matchGames - (first.matchWins + second.matchWins));
11980             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11981             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11982             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11983             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11984                 first.twoMachinesColor = "black\n";
11985                 second.twoMachinesColor = "white\n";
11986             } else {
11987                 first.twoMachinesColor = "white\n";
11988                 second.twoMachinesColor = "black\n";
11989             }
11990         }
11991     }
11992     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11993         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11994       ExitAnalyzeMode();
11995     gameMode = nextGameMode;
11996     ModeHighlight();
11997     endingGame = 0;  /* [HGM] crash */
11998     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11999         if(matchMode == TRUE) { // match through command line: exit with or without popup
12000             if(ranking) {
12001                 ToNrEvent(forwardMostMove);
12002                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12003                 else ExitEvent(0);
12004             } else DisplayFatalError(buf, 0, 0);
12005         } else { // match through menu; just stop, with or without popup
12006             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12007             ModeHighlight();
12008             if(ranking){
12009                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12010             } else DisplayNote(buf);
12011       }
12012       if(ranking) free(ranking);
12013     }
12014 }
12015
12016 /* Assumes program was just initialized (initString sent).
12017    Leaves program in force mode. */
12018 void
12019 FeedMovesToProgram (ChessProgramState *cps, int upto)
12020 {
12021     int i;
12022
12023     if (appData.debugMode)
12024       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12025               startedFromSetupPosition ? "position and " : "",
12026               backwardMostMove, upto, cps->which);
12027     if(currentlyInitializedVariant != gameInfo.variant) {
12028       char buf[MSG_SIZ];
12029         // [HGM] variantswitch: make engine aware of new variant
12030         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12031                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12032                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12033         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12034         SendToProgram(buf, cps);
12035         currentlyInitializedVariant = gameInfo.variant;
12036     }
12037     SendToProgram("force\n", cps);
12038     if (startedFromSetupPosition) {
12039         SendBoard(cps, backwardMostMove);
12040     if (appData.debugMode) {
12041         fprintf(debugFP, "feedMoves\n");
12042     }
12043     }
12044     for (i = backwardMostMove; i < upto; i++) {
12045         SendMoveToProgram(i, cps);
12046     }
12047 }
12048
12049
12050 int
12051 ResurrectChessProgram ()
12052 {
12053      /* The chess program may have exited.
12054         If so, restart it and feed it all the moves made so far. */
12055     static int doInit = 0;
12056
12057     if (appData.noChessProgram) return 1;
12058
12059     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12060         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12061         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12062         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12063     } else {
12064         if (first.pr != NoProc) return 1;
12065         StartChessProgram(&first);
12066     }
12067     InitChessProgram(&first, FALSE);
12068     FeedMovesToProgram(&first, currentMove);
12069
12070     if (!first.sendTime) {
12071         /* can't tell gnuchess what its clock should read,
12072            so we bow to its notion. */
12073         ResetClocks();
12074         timeRemaining[0][currentMove] = whiteTimeRemaining;
12075         timeRemaining[1][currentMove] = blackTimeRemaining;
12076     }
12077
12078     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12079                 appData.icsEngineAnalyze) && first.analysisSupport) {
12080       SendToProgram("analyze\n", &first);
12081       first.analyzing = TRUE;
12082     }
12083     return 1;
12084 }
12085
12086 /*
12087  * Button procedures
12088  */
12089 void
12090 Reset (int redraw, int init)
12091 {
12092     int i;
12093
12094     if (appData.debugMode) {
12095         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12096                 redraw, init, gameMode);
12097     }
12098     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12099     deadRanks = 0; // assume entire board is used
12100     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12101     CleanupTail(); // [HGM] vari: delete any stored variations
12102     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12103     pausing = pauseExamInvalid = FALSE;
12104     startedFromSetupPosition = blackPlaysFirst = FALSE;
12105     firstMove = TRUE;
12106     whiteFlag = blackFlag = FALSE;
12107     userOfferedDraw = FALSE;
12108     hintRequested = bookRequested = FALSE;
12109     first.maybeThinking = FALSE;
12110     second.maybeThinking = FALSE;
12111     first.bookSuspend = FALSE; // [HGM] book
12112     second.bookSuspend = FALSE;
12113     thinkOutput[0] = NULLCHAR;
12114     lastHint[0] = NULLCHAR;
12115     ClearGameInfo(&gameInfo);
12116     gameInfo.variant = StringToVariant(appData.variant);
12117     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12118         gameInfo.variant = VariantUnknown;
12119         strncpy(engineVariant, appData.variant, MSG_SIZ);
12120     }
12121     ics_user_moved = ics_clock_paused = FALSE;
12122     ics_getting_history = H_FALSE;
12123     ics_gamenum = -1;
12124     white_holding[0] = black_holding[0] = NULLCHAR;
12125     ClearProgramStats();
12126     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12127
12128     ResetFrontEnd();
12129     ClearHighlights();
12130     flipView = appData.flipView;
12131     ClearPremoveHighlights();
12132     gotPremove = FALSE;
12133     alarmSounded = FALSE;
12134     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12135
12136     GameEnds(EndOfFile, NULL, GE_PLAYER);
12137     if(appData.serverMovesName != NULL) {
12138         /* [HGM] prepare to make moves file for broadcasting */
12139         clock_t t = clock();
12140         if(serverMoves != NULL) fclose(serverMoves);
12141         serverMoves = fopen(appData.serverMovesName, "r");
12142         if(serverMoves != NULL) {
12143             fclose(serverMoves);
12144             /* delay 15 sec before overwriting, so all clients can see end */
12145             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12146         }
12147         serverMoves = fopen(appData.serverMovesName, "w");
12148     }
12149
12150     ExitAnalyzeMode();
12151     gameMode = BeginningOfGame;
12152     ModeHighlight();
12153     if(appData.icsActive) gameInfo.variant = VariantNormal;
12154     currentMove = forwardMostMove = backwardMostMove = 0;
12155     MarkTargetSquares(1);
12156     InitPosition(redraw);
12157     for (i = 0; i < MAX_MOVES; i++) {
12158         if (commentList[i] != NULL) {
12159             free(commentList[i]);
12160             commentList[i] = NULL;
12161         }
12162     }
12163     ResetClocks();
12164     timeRemaining[0][0] = whiteTimeRemaining;
12165     timeRemaining[1][0] = blackTimeRemaining;
12166
12167     if (first.pr == NoProc) {
12168         StartChessProgram(&first);
12169     }
12170     if (init) {
12171             InitChessProgram(&first, startedFromSetupPosition);
12172     }
12173     DisplayTitle("");
12174     DisplayMessage("", "");
12175     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12176     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12177     ClearMap();        // [HGM] exclude: invalidate map
12178 }
12179
12180 void
12181 AutoPlayGameLoop ()
12182 {
12183     for (;;) {
12184         if (!AutoPlayOneMove())
12185           return;
12186         if (matchMode || appData.timeDelay == 0)
12187           continue;
12188         if (appData.timeDelay < 0)
12189           return;
12190         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12191         break;
12192     }
12193 }
12194
12195 void
12196 AnalyzeNextGame()
12197 {
12198     ReloadGame(1); // next game
12199 }
12200
12201 int
12202 AutoPlayOneMove ()
12203 {
12204     int fromX, fromY, toX, toY;
12205
12206     if (appData.debugMode) {
12207       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12208     }
12209
12210     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12211       return FALSE;
12212
12213     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12214       pvInfoList[currentMove].depth = programStats.depth;
12215       pvInfoList[currentMove].score = programStats.score;
12216       pvInfoList[currentMove].time  = 0;
12217       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12218       else { // append analysis of final position as comment
12219         char buf[MSG_SIZ];
12220         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12221         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12222       }
12223       programStats.depth = 0;
12224     }
12225
12226     if (currentMove >= forwardMostMove) {
12227       if(gameMode == AnalyzeFile) {
12228           if(appData.loadGameIndex == -1) {
12229             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12230           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12231           } else {
12232           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12233         }
12234       }
12235 //      gameMode = EndOfGame;
12236 //      ModeHighlight();
12237
12238       /* [AS] Clear current move marker at the end of a game */
12239       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12240
12241       return FALSE;
12242     }
12243
12244     toX = moveList[currentMove][2] - AAA;
12245     toY = moveList[currentMove][3] - ONE;
12246
12247     if (moveList[currentMove][1] == '@') {
12248         if (appData.highlightLastMove) {
12249             SetHighlights(-1, -1, toX, toY);
12250         }
12251     } else {
12252         fromX = moveList[currentMove][0] - AAA;
12253         fromY = moveList[currentMove][1] - ONE;
12254
12255         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12256
12257         if(moveList[currentMove][4] == ';') { // multi-leg
12258             killX = moveList[currentMove][5] - AAA;
12259             killY = moveList[currentMove][6] - ONE;
12260         }
12261         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12262         killX = killY = -1;
12263
12264         if (appData.highlightLastMove) {
12265             SetHighlights(fromX, fromY, toX, toY);
12266         }
12267     }
12268     DisplayMove(currentMove);
12269     SendMoveToProgram(currentMove++, &first);
12270     DisplayBothClocks();
12271     DrawPosition(FALSE, boards[currentMove]);
12272     // [HGM] PV info: always display, routine tests if empty
12273     DisplayComment(currentMove - 1, commentList[currentMove]);
12274     return TRUE;
12275 }
12276
12277
12278 int
12279 LoadGameOneMove (ChessMove readAhead)
12280 {
12281     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12282     char promoChar = NULLCHAR;
12283     ChessMove moveType;
12284     char move[MSG_SIZ];
12285     char *p, *q;
12286
12287     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12288         gameMode != AnalyzeMode && gameMode != Training) {
12289         gameFileFP = NULL;
12290         return FALSE;
12291     }
12292
12293     yyboardindex = forwardMostMove;
12294     if (readAhead != EndOfFile) {
12295       moveType = readAhead;
12296     } else {
12297       if (gameFileFP == NULL)
12298           return FALSE;
12299       moveType = (ChessMove) Myylex();
12300     }
12301
12302     done = FALSE;
12303     switch (moveType) {
12304       case Comment:
12305         if (appData.debugMode)
12306           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12307         p = yy_text;
12308
12309         /* append the comment but don't display it */
12310         AppendComment(currentMove, p, FALSE);
12311         return TRUE;
12312
12313       case WhiteCapturesEnPassant:
12314       case BlackCapturesEnPassant:
12315       case WhitePromotion:
12316       case BlackPromotion:
12317       case WhiteNonPromotion:
12318       case BlackNonPromotion:
12319       case NormalMove:
12320       case FirstLeg:
12321       case WhiteKingSideCastle:
12322       case WhiteQueenSideCastle:
12323       case BlackKingSideCastle:
12324       case BlackQueenSideCastle:
12325       case WhiteKingSideCastleWild:
12326       case WhiteQueenSideCastleWild:
12327       case BlackKingSideCastleWild:
12328       case BlackQueenSideCastleWild:
12329       /* PUSH Fabien */
12330       case WhiteHSideCastleFR:
12331       case WhiteASideCastleFR:
12332       case BlackHSideCastleFR:
12333       case BlackASideCastleFR:
12334       /* POP Fabien */
12335         if (appData.debugMode)
12336           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12337         fromX = currentMoveString[0] - AAA;
12338         fromY = currentMoveString[1] - ONE;
12339         toX = currentMoveString[2] - AAA;
12340         toY = currentMoveString[3] - ONE;
12341         promoChar = currentMoveString[4];
12342         if(promoChar == ';') promoChar = currentMoveString[7];
12343         break;
12344
12345       case WhiteDrop:
12346       case BlackDrop:
12347         if (appData.debugMode)
12348           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12349         fromX = moveType == WhiteDrop ?
12350           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12351         (int) CharToPiece(ToLower(currentMoveString[0]));
12352         fromY = DROP_RANK;
12353         toX = currentMoveString[2] - AAA;
12354         toY = currentMoveString[3] - ONE;
12355         break;
12356
12357       case WhiteWins:
12358       case BlackWins:
12359       case GameIsDrawn:
12360       case GameUnfinished:
12361         if (appData.debugMode)
12362           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12363         p = strchr(yy_text, '{');
12364         if (p == NULL) p = strchr(yy_text, '(');
12365         if (p == NULL) {
12366             p = yy_text;
12367             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12368         } else {
12369             q = strchr(p, *p == '{' ? '}' : ')');
12370             if (q != NULL) *q = NULLCHAR;
12371             p++;
12372         }
12373         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12374         GameEnds(moveType, p, GE_FILE);
12375         done = TRUE;
12376         if (cmailMsgLoaded) {
12377             ClearHighlights();
12378             flipView = WhiteOnMove(currentMove);
12379             if (moveType == GameUnfinished) flipView = !flipView;
12380             if (appData.debugMode)
12381               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12382         }
12383         break;
12384
12385       case EndOfFile:
12386         if (appData.debugMode)
12387           fprintf(debugFP, "Parser hit end of file\n");
12388         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12389           case MT_NONE:
12390           case MT_CHECK:
12391             break;
12392           case MT_CHECKMATE:
12393           case MT_STAINMATE:
12394             if (WhiteOnMove(currentMove)) {
12395                 GameEnds(BlackWins, "Black mates", GE_FILE);
12396             } else {
12397                 GameEnds(WhiteWins, "White mates", GE_FILE);
12398             }
12399             break;
12400           case MT_STALEMATE:
12401             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12402             break;
12403         }
12404         done = TRUE;
12405         break;
12406
12407       case MoveNumberOne:
12408         if (lastLoadGameStart == GNUChessGame) {
12409             /* GNUChessGames have numbers, but they aren't move numbers */
12410             if (appData.debugMode)
12411               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12412                       yy_text, (int) moveType);
12413             return LoadGameOneMove(EndOfFile); /* tail recursion */
12414         }
12415         /* else fall thru */
12416
12417       case XBoardGame:
12418       case GNUChessGame:
12419       case PGNTag:
12420         /* Reached start of next game in file */
12421         if (appData.debugMode)
12422           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12423         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12424           case MT_NONE:
12425           case MT_CHECK:
12426             break;
12427           case MT_CHECKMATE:
12428           case MT_STAINMATE:
12429             if (WhiteOnMove(currentMove)) {
12430                 GameEnds(BlackWins, "Black mates", GE_FILE);
12431             } else {
12432                 GameEnds(WhiteWins, "White mates", GE_FILE);
12433             }
12434             break;
12435           case MT_STALEMATE:
12436             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12437             break;
12438         }
12439         done = TRUE;
12440         break;
12441
12442       case PositionDiagram:     /* should not happen; ignore */
12443       case ElapsedTime:         /* ignore */
12444       case NAG:                 /* ignore */
12445         if (appData.debugMode)
12446           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12447                   yy_text, (int) moveType);
12448         return LoadGameOneMove(EndOfFile); /* tail recursion */
12449
12450       case IllegalMove:
12451         if (appData.testLegality) {
12452             if (appData.debugMode)
12453               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12454             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12455                     (forwardMostMove / 2) + 1,
12456                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12457             DisplayError(move, 0);
12458             done = TRUE;
12459         } else {
12460             if (appData.debugMode)
12461               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12462                       yy_text, currentMoveString);
12463             if(currentMoveString[1] == '@') {
12464                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12465                 fromY = DROP_RANK;
12466             } else {
12467                 fromX = currentMoveString[0] - AAA;
12468                 fromY = currentMoveString[1] - ONE;
12469             }
12470             toX = currentMoveString[2] - AAA;
12471             toY = currentMoveString[3] - ONE;
12472             promoChar = currentMoveString[4];
12473         }
12474         break;
12475
12476       case AmbiguousMove:
12477         if (appData.debugMode)
12478           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12479         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12480                 (forwardMostMove / 2) + 1,
12481                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12482         DisplayError(move, 0);
12483         done = TRUE;
12484         break;
12485
12486       default:
12487       case ImpossibleMove:
12488         if (appData.debugMode)
12489           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12490         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12491                 (forwardMostMove / 2) + 1,
12492                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12493         DisplayError(move, 0);
12494         done = TRUE;
12495         break;
12496     }
12497
12498     if (done) {
12499         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12500             DrawPosition(FALSE, boards[currentMove]);
12501             DisplayBothClocks();
12502             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12503               DisplayComment(currentMove - 1, commentList[currentMove]);
12504         }
12505         (void) StopLoadGameTimer();
12506         gameFileFP = NULL;
12507         cmailOldMove = forwardMostMove;
12508         return FALSE;
12509     } else {
12510         /* currentMoveString is set as a side-effect of yylex */
12511
12512         thinkOutput[0] = NULLCHAR;
12513         MakeMove(fromX, fromY, toX, toY, promoChar);
12514         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12515         currentMove = forwardMostMove;
12516         return TRUE;
12517     }
12518 }
12519
12520 /* Load the nth game from the given file */
12521 int
12522 LoadGameFromFile (char *filename, int n, char *title, int useList)
12523 {
12524     FILE *f;
12525     char buf[MSG_SIZ];
12526
12527     if (strcmp(filename, "-") == 0) {
12528         f = stdin;
12529         title = "stdin";
12530     } else {
12531         f = fopen(filename, "rb");
12532         if (f == NULL) {
12533           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12534             DisplayError(buf, errno);
12535             return FALSE;
12536         }
12537     }
12538     if (fseek(f, 0, 0) == -1) {
12539         /* f is not seekable; probably a pipe */
12540         useList = FALSE;
12541     }
12542     if (useList && n == 0) {
12543         int error = GameListBuild(f);
12544         if (error) {
12545             DisplayError(_("Cannot build game list"), error);
12546         } else if (!ListEmpty(&gameList) &&
12547                    ((ListGame *) gameList.tailPred)->number > 1) {
12548             GameListPopUp(f, title);
12549             return TRUE;
12550         }
12551         GameListDestroy();
12552         n = 1;
12553     }
12554     if (n == 0) n = 1;
12555     return LoadGame(f, n, title, FALSE);
12556 }
12557
12558
12559 void
12560 MakeRegisteredMove ()
12561 {
12562     int fromX, fromY, toX, toY;
12563     char promoChar;
12564     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12565         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12566           case CMAIL_MOVE:
12567           case CMAIL_DRAW:
12568             if (appData.debugMode)
12569               fprintf(debugFP, "Restoring %s for game %d\n",
12570                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12571
12572             thinkOutput[0] = NULLCHAR;
12573             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12574             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12575             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12576             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12577             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12578             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12579             MakeMove(fromX, fromY, toX, toY, promoChar);
12580             ShowMove(fromX, fromY, toX, toY);
12581
12582             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12583               case MT_NONE:
12584               case MT_CHECK:
12585                 break;
12586
12587               case MT_CHECKMATE:
12588               case MT_STAINMATE:
12589                 if (WhiteOnMove(currentMove)) {
12590                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12591                 } else {
12592                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12593                 }
12594                 break;
12595
12596               case MT_STALEMATE:
12597                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12598                 break;
12599             }
12600
12601             break;
12602
12603           case CMAIL_RESIGN:
12604             if (WhiteOnMove(currentMove)) {
12605                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12606             } else {
12607                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12608             }
12609             break;
12610
12611           case CMAIL_ACCEPT:
12612             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12613             break;
12614
12615           default:
12616             break;
12617         }
12618     }
12619
12620     return;
12621 }
12622
12623 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12624 int
12625 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12626 {
12627     int retVal;
12628
12629     if (gameNumber > nCmailGames) {
12630         DisplayError(_("No more games in this message"), 0);
12631         return FALSE;
12632     }
12633     if (f == lastLoadGameFP) {
12634         int offset = gameNumber - lastLoadGameNumber;
12635         if (offset == 0) {
12636             cmailMsg[0] = NULLCHAR;
12637             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12638                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12639                 nCmailMovesRegistered--;
12640             }
12641             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12642             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12643                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12644             }
12645         } else {
12646             if (! RegisterMove()) return FALSE;
12647         }
12648     }
12649
12650     retVal = LoadGame(f, gameNumber, title, useList);
12651
12652     /* Make move registered during previous look at this game, if any */
12653     MakeRegisteredMove();
12654
12655     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12656         commentList[currentMove]
12657           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12658         DisplayComment(currentMove - 1, commentList[currentMove]);
12659     }
12660
12661     return retVal;
12662 }
12663
12664 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12665 int
12666 ReloadGame (int offset)
12667 {
12668     int gameNumber = lastLoadGameNumber + offset;
12669     if (lastLoadGameFP == NULL) {
12670         DisplayError(_("No game has been loaded yet"), 0);
12671         return FALSE;
12672     }
12673     if (gameNumber <= 0) {
12674         DisplayError(_("Can't back up any further"), 0);
12675         return FALSE;
12676     }
12677     if (cmailMsgLoaded) {
12678         return CmailLoadGame(lastLoadGameFP, gameNumber,
12679                              lastLoadGameTitle, lastLoadGameUseList);
12680     } else {
12681         return LoadGame(lastLoadGameFP, gameNumber,
12682                         lastLoadGameTitle, lastLoadGameUseList);
12683     }
12684 }
12685
12686 int keys[EmptySquare+1];
12687
12688 int
12689 PositionMatches (Board b1, Board b2)
12690 {
12691     int r, f, sum=0;
12692     switch(appData.searchMode) {
12693         case 1: return CompareWithRights(b1, b2);
12694         case 2:
12695             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12696                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12697             }
12698             return TRUE;
12699         case 3:
12700             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12701               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12702                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12703             }
12704             return sum==0;
12705         case 4:
12706             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12707                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12708             }
12709             return sum==0;
12710     }
12711     return TRUE;
12712 }
12713
12714 #define Q_PROMO  4
12715 #define Q_EP     3
12716 #define Q_BCASTL 2
12717 #define Q_WCASTL 1
12718
12719 int pieceList[256], quickBoard[256];
12720 ChessSquare pieceType[256] = { EmptySquare };
12721 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12722 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12723 int soughtTotal, turn;
12724 Boolean epOK, flipSearch;
12725
12726 typedef struct {
12727     unsigned char piece, to;
12728 } Move;
12729
12730 #define DSIZE (250000)
12731
12732 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12733 Move *moveDatabase = initialSpace;
12734 unsigned int movePtr, dataSize = DSIZE;
12735
12736 int
12737 MakePieceList (Board board, int *counts)
12738 {
12739     int r, f, n=Q_PROMO, total=0;
12740     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12741     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12742         int sq = f + (r<<4);
12743         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12744             quickBoard[sq] = ++n;
12745             pieceList[n] = sq;
12746             pieceType[n] = board[r][f];
12747             counts[board[r][f]]++;
12748             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12749             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12750             total++;
12751         }
12752     }
12753     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12754     return total;
12755 }
12756
12757 void
12758 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12759 {
12760     int sq = fromX + (fromY<<4);
12761     int piece = quickBoard[sq], rook;
12762     quickBoard[sq] = 0;
12763     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12764     if(piece == pieceList[1] && fromY == toY) {
12765       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12766         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12767         moveDatabase[movePtr++].piece = Q_WCASTL;
12768         quickBoard[sq] = piece;
12769         piece = quickBoard[from]; quickBoard[from] = 0;
12770         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12771       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12772         quickBoard[sq] = 0; // remove Rook
12773         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12774         moveDatabase[movePtr++].piece = Q_WCASTL;
12775         quickBoard[sq] = pieceList[1]; // put King
12776         piece = rook;
12777         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12778       }
12779     } else
12780     if(piece == pieceList[2] && fromY == toY) {
12781       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12782         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12783         moveDatabase[movePtr++].piece = Q_BCASTL;
12784         quickBoard[sq] = piece;
12785         piece = quickBoard[from]; quickBoard[from] = 0;
12786         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12787       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12788         quickBoard[sq] = 0; // remove Rook
12789         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12790         moveDatabase[movePtr++].piece = Q_BCASTL;
12791         quickBoard[sq] = pieceList[2]; // put King
12792         piece = rook;
12793         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12794       }
12795     } else
12796     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12797         quickBoard[(fromY<<4)+toX] = 0;
12798         moveDatabase[movePtr].piece = Q_EP;
12799         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12800         moveDatabase[movePtr].to = sq;
12801     } else
12802     if(promoPiece != pieceType[piece]) {
12803         moveDatabase[movePtr++].piece = Q_PROMO;
12804         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12805     }
12806     moveDatabase[movePtr].piece = piece;
12807     quickBoard[sq] = piece;
12808     movePtr++;
12809 }
12810
12811 int
12812 PackGame (Board board)
12813 {
12814     Move *newSpace = NULL;
12815     moveDatabase[movePtr].piece = 0; // terminate previous game
12816     if(movePtr > dataSize) {
12817         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12818         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12819         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12820         if(newSpace) {
12821             int i;
12822             Move *p = moveDatabase, *q = newSpace;
12823             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12824             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12825             moveDatabase = newSpace;
12826         } else { // calloc failed, we must be out of memory. Too bad...
12827             dataSize = 0; // prevent calloc events for all subsequent games
12828             return 0;     // and signal this one isn't cached
12829         }
12830     }
12831     movePtr++;
12832     MakePieceList(board, counts);
12833     return movePtr;
12834 }
12835
12836 int
12837 QuickCompare (Board board, int *minCounts, int *maxCounts)
12838 {   // compare according to search mode
12839     int r, f;
12840     switch(appData.searchMode)
12841     {
12842       case 1: // exact position match
12843         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12844         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12845             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12846         }
12847         break;
12848       case 2: // can have extra material on empty squares
12849         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12850             if(board[r][f] == EmptySquare) continue;
12851             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12852         }
12853         break;
12854       case 3: // material with exact Pawn structure
12855         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12856             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12857             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12858         } // fall through to material comparison
12859       case 4: // exact material
12860         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12861         break;
12862       case 6: // material range with given imbalance
12863         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12864         // fall through to range comparison
12865       case 5: // material range
12866         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12867     }
12868     return TRUE;
12869 }
12870
12871 int
12872 QuickScan (Board board, Move *move)
12873 {   // reconstruct game,and compare all positions in it
12874     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12875     do {
12876         int piece = move->piece;
12877         int to = move->to, from = pieceList[piece];
12878         if(found < 0) { // if already found just scan to game end for final piece count
12879           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12880            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12881            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12882                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12883             ) {
12884             static int lastCounts[EmptySquare+1];
12885             int i;
12886             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12887             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12888           } else stretch = 0;
12889           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12890           if(found >= 0 && !appData.minPieces) return found;
12891         }
12892         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12893           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12894           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12895             piece = (++move)->piece;
12896             from = pieceList[piece];
12897             counts[pieceType[piece]]--;
12898             pieceType[piece] = (ChessSquare) move->to;
12899             counts[move->to]++;
12900           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12901             counts[pieceType[quickBoard[to]]]--;
12902             quickBoard[to] = 0; total--;
12903             move++;
12904             continue;
12905           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12906             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12907             from  = pieceList[piece]; // so this must be King
12908             quickBoard[from] = 0;
12909             pieceList[piece] = to;
12910             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12911             quickBoard[from] = 0; // rook
12912             quickBoard[to] = piece;
12913             to = move->to; piece = move->piece;
12914             goto aftercastle;
12915           }
12916         }
12917         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12918         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12919         quickBoard[from] = 0;
12920       aftercastle:
12921         quickBoard[to] = piece;
12922         pieceList[piece] = to;
12923         cnt++; turn ^= 3;
12924         move++;
12925     } while(1);
12926 }
12927
12928 void
12929 InitSearch ()
12930 {
12931     int r, f;
12932     flipSearch = FALSE;
12933     CopyBoard(soughtBoard, boards[currentMove]);
12934     soughtTotal = MakePieceList(soughtBoard, maxSought);
12935     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12936     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12937     CopyBoard(reverseBoard, boards[currentMove]);
12938     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12939         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12940         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12941         reverseBoard[r][f] = piece;
12942     }
12943     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12944     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12945     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12946                  || (boards[currentMove][CASTLING][2] == NoRights ||
12947                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12948                  && (boards[currentMove][CASTLING][5] == NoRights ||
12949                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12950       ) {
12951         flipSearch = TRUE;
12952         CopyBoard(flipBoard, soughtBoard);
12953         CopyBoard(rotateBoard, reverseBoard);
12954         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12955             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12956             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12957         }
12958     }
12959     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12960     if(appData.searchMode >= 5) {
12961         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12962         MakePieceList(soughtBoard, minSought);
12963         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12964     }
12965     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12966         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12967 }
12968
12969 GameInfo dummyInfo;
12970 static int creatingBook;
12971
12972 int
12973 GameContainsPosition (FILE *f, ListGame *lg)
12974 {
12975     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12976     int fromX, fromY, toX, toY;
12977     char promoChar;
12978     static int initDone=FALSE;
12979
12980     // weed out games based on numerical tag comparison
12981     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12982     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12983     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12984     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12985     if(!initDone) {
12986         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12987         initDone = TRUE;
12988     }
12989     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12990     else CopyBoard(boards[scratch], initialPosition); // default start position
12991     if(lg->moves) {
12992         turn = btm + 1;
12993         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12994         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12995     }
12996     if(btm) plyNr++;
12997     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12998     fseek(f, lg->offset, 0);
12999     yynewfile(f);
13000     while(1) {
13001         yyboardindex = scratch;
13002         quickFlag = plyNr+1;
13003         next = Myylex();
13004         quickFlag = 0;
13005         switch(next) {
13006             case PGNTag:
13007                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13008             default:
13009                 continue;
13010
13011             case XBoardGame:
13012             case GNUChessGame:
13013                 if(plyNr) return -1; // after we have seen moves, this is for new game
13014               continue;
13015
13016             case AmbiguousMove: // we cannot reconstruct the game beyond these two
13017             case ImpossibleMove:
13018             case WhiteWins: // game ends here with these four
13019             case BlackWins:
13020             case GameIsDrawn:
13021             case GameUnfinished:
13022                 return -1;
13023
13024             case IllegalMove:
13025                 if(appData.testLegality) return -1;
13026             case WhiteCapturesEnPassant:
13027             case BlackCapturesEnPassant:
13028             case WhitePromotion:
13029             case BlackPromotion:
13030             case WhiteNonPromotion:
13031             case BlackNonPromotion:
13032             case NormalMove:
13033             case FirstLeg:
13034             case WhiteKingSideCastle:
13035             case WhiteQueenSideCastle:
13036             case BlackKingSideCastle:
13037             case BlackQueenSideCastle:
13038             case WhiteKingSideCastleWild:
13039             case WhiteQueenSideCastleWild:
13040             case BlackKingSideCastleWild:
13041             case BlackQueenSideCastleWild:
13042             case WhiteHSideCastleFR:
13043             case WhiteASideCastleFR:
13044             case BlackHSideCastleFR:
13045             case BlackASideCastleFR:
13046                 fromX = currentMoveString[0] - AAA;
13047                 fromY = currentMoveString[1] - ONE;
13048                 toX = currentMoveString[2] - AAA;
13049                 toY = currentMoveString[3] - ONE;
13050                 promoChar = currentMoveString[4];
13051                 break;
13052             case WhiteDrop:
13053             case BlackDrop:
13054                 fromX = next == WhiteDrop ?
13055                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
13056                   (int) CharToPiece(ToLower(currentMoveString[0]));
13057                 fromY = DROP_RANK;
13058                 toX = currentMoveString[2] - AAA;
13059                 toY = currentMoveString[3] - ONE;
13060                 promoChar = 0;
13061                 break;
13062         }
13063         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13064         plyNr++;
13065         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13066         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13067         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13068         if(appData.findMirror) {
13069             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13070             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13071         }
13072     }
13073 }
13074
13075 /* Load the nth game from open file f */
13076 int
13077 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13078 {
13079     ChessMove cm;
13080     char buf[MSG_SIZ];
13081     int gn = gameNumber;
13082     ListGame *lg = NULL;
13083     int numPGNTags = 0, i;
13084     int err, pos = -1;
13085     GameMode oldGameMode;
13086     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13087     char oldName[MSG_SIZ];
13088
13089     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13090
13091     if (appData.debugMode)
13092         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13093
13094     if (gameMode == Training )
13095         SetTrainingModeOff();
13096
13097     oldGameMode = gameMode;
13098     if (gameMode != BeginningOfGame) {
13099       Reset(FALSE, TRUE);
13100     }
13101     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13102
13103     gameFileFP = f;
13104     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13105         fclose(lastLoadGameFP);
13106     }
13107
13108     if (useList) {
13109         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13110
13111         if (lg) {
13112             fseek(f, lg->offset, 0);
13113             GameListHighlight(gameNumber);
13114             pos = lg->position;
13115             gn = 1;
13116         }
13117         else {
13118             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13119               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13120             else
13121             DisplayError(_("Game number out of range"), 0);
13122             return FALSE;
13123         }
13124     } else {
13125         GameListDestroy();
13126         if (fseek(f, 0, 0) == -1) {
13127             if (f == lastLoadGameFP ?
13128                 gameNumber == lastLoadGameNumber + 1 :
13129                 gameNumber == 1) {
13130                 gn = 1;
13131             } else {
13132                 DisplayError(_("Can't seek on game file"), 0);
13133                 return FALSE;
13134             }
13135         }
13136     }
13137     lastLoadGameFP = f;
13138     lastLoadGameNumber = gameNumber;
13139     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13140     lastLoadGameUseList = useList;
13141
13142     yynewfile(f);
13143
13144     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13145       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13146                 lg->gameInfo.black);
13147             DisplayTitle(buf);
13148     } else if (*title != NULLCHAR) {
13149         if (gameNumber > 1) {
13150           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13151             DisplayTitle(buf);
13152         } else {
13153             DisplayTitle(title);
13154         }
13155     }
13156
13157     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13158         gameMode = PlayFromGameFile;
13159         ModeHighlight();
13160     }
13161
13162     currentMove = forwardMostMove = backwardMostMove = 0;
13163     CopyBoard(boards[0], initialPosition);
13164     StopClocks();
13165
13166     /*
13167      * Skip the first gn-1 games in the file.
13168      * Also skip over anything that precedes an identifiable
13169      * start of game marker, to avoid being confused by
13170      * garbage at the start of the file.  Currently
13171      * recognized start of game markers are the move number "1",
13172      * the pattern "gnuchess .* game", the pattern
13173      * "^[#;%] [^ ]* game file", and a PGN tag block.
13174      * A game that starts with one of the latter two patterns
13175      * will also have a move number 1, possibly
13176      * following a position diagram.
13177      * 5-4-02: Let's try being more lenient and allowing a game to
13178      * start with an unnumbered move.  Does that break anything?
13179      */
13180     cm = lastLoadGameStart = EndOfFile;
13181     while (gn > 0) {
13182         yyboardindex = forwardMostMove;
13183         cm = (ChessMove) Myylex();
13184         switch (cm) {
13185           case EndOfFile:
13186             if (cmailMsgLoaded) {
13187                 nCmailGames = CMAIL_MAX_GAMES - gn;
13188             } else {
13189                 Reset(TRUE, TRUE);
13190                 DisplayError(_("Game not found in file"), 0);
13191             }
13192             return FALSE;
13193
13194           case GNUChessGame:
13195           case XBoardGame:
13196             gn--;
13197             lastLoadGameStart = cm;
13198             break;
13199
13200           case MoveNumberOne:
13201             switch (lastLoadGameStart) {
13202               case GNUChessGame:
13203               case XBoardGame:
13204               case PGNTag:
13205                 break;
13206               case MoveNumberOne:
13207               case EndOfFile:
13208                 gn--;           /* count this game */
13209                 lastLoadGameStart = cm;
13210                 break;
13211               default:
13212                 /* impossible */
13213                 break;
13214             }
13215             break;
13216
13217           case PGNTag:
13218             switch (lastLoadGameStart) {
13219               case GNUChessGame:
13220               case PGNTag:
13221               case MoveNumberOne:
13222               case EndOfFile:
13223                 gn--;           /* count this game */
13224                 lastLoadGameStart = cm;
13225                 break;
13226               case XBoardGame:
13227                 lastLoadGameStart = cm; /* game counted already */
13228                 break;
13229               default:
13230                 /* impossible */
13231                 break;
13232             }
13233             if (gn > 0) {
13234                 do {
13235                     yyboardindex = forwardMostMove;
13236                     cm = (ChessMove) Myylex();
13237                 } while (cm == PGNTag || cm == Comment);
13238             }
13239             break;
13240
13241           case WhiteWins:
13242           case BlackWins:
13243           case GameIsDrawn:
13244             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13245                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13246                     != CMAIL_OLD_RESULT) {
13247                     nCmailResults ++ ;
13248                     cmailResult[  CMAIL_MAX_GAMES
13249                                 - gn - 1] = CMAIL_OLD_RESULT;
13250                 }
13251             }
13252             break;
13253
13254           case NormalMove:
13255           case FirstLeg:
13256             /* Only a NormalMove can be at the start of a game
13257              * without a position diagram. */
13258             if (lastLoadGameStart == EndOfFile ) {
13259               gn--;
13260               lastLoadGameStart = MoveNumberOne;
13261             }
13262             break;
13263
13264           default:
13265             break;
13266         }
13267     }
13268
13269     if (appData.debugMode)
13270       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13271
13272     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13273
13274     if (cm == XBoardGame) {
13275         /* Skip any header junk before position diagram and/or move 1 */
13276         for (;;) {
13277             yyboardindex = forwardMostMove;
13278             cm = (ChessMove) Myylex();
13279
13280             if (cm == EndOfFile ||
13281                 cm == GNUChessGame || cm == XBoardGame) {
13282                 /* Empty game; pretend end-of-file and handle later */
13283                 cm = EndOfFile;
13284                 break;
13285             }
13286
13287             if (cm == MoveNumberOne || cm == PositionDiagram ||
13288                 cm == PGNTag || cm == Comment)
13289               break;
13290         }
13291     } else if (cm == GNUChessGame) {
13292         if (gameInfo.event != NULL) {
13293             free(gameInfo.event);
13294         }
13295         gameInfo.event = StrSave(yy_text);
13296     }
13297
13298     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13299     while (cm == PGNTag) {
13300         if (appData.debugMode)
13301           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13302         err = ParsePGNTag(yy_text, &gameInfo);
13303         if (!err) numPGNTags++;
13304
13305         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13306         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13307             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13308             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13309             InitPosition(TRUE);
13310             oldVariant = gameInfo.variant;
13311             if (appData.debugMode)
13312               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13313         }
13314
13315
13316         if (gameInfo.fen != NULL) {
13317           Board initial_position;
13318           startedFromSetupPosition = TRUE;
13319           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13320             Reset(TRUE, TRUE);
13321             DisplayError(_("Bad FEN position in file"), 0);
13322             return FALSE;
13323           }
13324           CopyBoard(boards[0], initial_position);
13325           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13326             CopyBoard(initialPosition, initial_position);
13327           if (blackPlaysFirst) {
13328             currentMove = forwardMostMove = backwardMostMove = 1;
13329             CopyBoard(boards[1], initial_position);
13330             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13331             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13332             timeRemaining[0][1] = whiteTimeRemaining;
13333             timeRemaining[1][1] = blackTimeRemaining;
13334             if (commentList[0] != NULL) {
13335               commentList[1] = commentList[0];
13336               commentList[0] = NULL;
13337             }
13338           } else {
13339             currentMove = forwardMostMove = backwardMostMove = 0;
13340           }
13341           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13342           {   int i;
13343               initialRulePlies = FENrulePlies;
13344               for( i=0; i< nrCastlingRights; i++ )
13345                   initialRights[i] = initial_position[CASTLING][i];
13346           }
13347           yyboardindex = forwardMostMove;
13348           free(gameInfo.fen);
13349           gameInfo.fen = NULL;
13350         }
13351
13352         yyboardindex = forwardMostMove;
13353         cm = (ChessMove) Myylex();
13354
13355         /* Handle comments interspersed among the tags */
13356         while (cm == Comment) {
13357             char *p;
13358             if (appData.debugMode)
13359               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13360             p = yy_text;
13361             AppendComment(currentMove, p, FALSE);
13362             yyboardindex = forwardMostMove;
13363             cm = (ChessMove) Myylex();
13364         }
13365     }
13366
13367     /* don't rely on existence of Event tag since if game was
13368      * pasted from clipboard the Event tag may not exist
13369      */
13370     if (numPGNTags > 0){
13371         char *tags;
13372         if (gameInfo.variant == VariantNormal) {
13373           VariantClass v = StringToVariant(gameInfo.event);
13374           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13375           if(v < VariantShogi) gameInfo.variant = v;
13376         }
13377         if (!matchMode) {
13378           if( appData.autoDisplayTags ) {
13379             tags = PGNTags(&gameInfo);
13380             TagsPopUp(tags, CmailMsg());
13381             free(tags);
13382           }
13383         }
13384     } else {
13385         /* Make something up, but don't display it now */
13386         SetGameInfo();
13387         TagsPopDown();
13388     }
13389
13390     if (cm == PositionDiagram) {
13391         int i, j;
13392         char *p;
13393         Board initial_position;
13394
13395         if (appData.debugMode)
13396           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13397
13398         if (!startedFromSetupPosition) {
13399             p = yy_text;
13400             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13401               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13402                 switch (*p) {
13403                   case '{':
13404                   case '[':
13405                   case '-':
13406                   case ' ':
13407                   case '\t':
13408                   case '\n':
13409                   case '\r':
13410                     break;
13411                   default:
13412                     initial_position[i][j++] = CharToPiece(*p);
13413                     break;
13414                 }
13415             while (*p == ' ' || *p == '\t' ||
13416                    *p == '\n' || *p == '\r') p++;
13417
13418             if (strncmp(p, "black", strlen("black"))==0)
13419               blackPlaysFirst = TRUE;
13420             else
13421               blackPlaysFirst = FALSE;
13422             startedFromSetupPosition = TRUE;
13423
13424             CopyBoard(boards[0], initial_position);
13425             if (blackPlaysFirst) {
13426                 currentMove = forwardMostMove = backwardMostMove = 1;
13427                 CopyBoard(boards[1], initial_position);
13428                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13429                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13430                 timeRemaining[0][1] = whiteTimeRemaining;
13431                 timeRemaining[1][1] = blackTimeRemaining;
13432                 if (commentList[0] != NULL) {
13433                     commentList[1] = commentList[0];
13434                     commentList[0] = NULL;
13435                 }
13436             } else {
13437                 currentMove = forwardMostMove = backwardMostMove = 0;
13438             }
13439         }
13440         yyboardindex = forwardMostMove;
13441         cm = (ChessMove) Myylex();
13442     }
13443
13444   if(!creatingBook) {
13445     if (first.pr == NoProc) {
13446         StartChessProgram(&first);
13447     }
13448     InitChessProgram(&first, FALSE);
13449     if(gameInfo.variant == VariantUnknown && *oldName) {
13450         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13451         gameInfo.variant = v;
13452     }
13453     SendToProgram("force\n", &first);
13454     if (startedFromSetupPosition) {
13455         SendBoard(&first, forwardMostMove);
13456     if (appData.debugMode) {
13457         fprintf(debugFP, "Load Game\n");
13458     }
13459         DisplayBothClocks();
13460     }
13461   }
13462
13463     /* [HGM] server: flag to write setup moves in broadcast file as one */
13464     loadFlag = appData.suppressLoadMoves;
13465
13466     while (cm == Comment) {
13467         char *p;
13468         if (appData.debugMode)
13469           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13470         p = yy_text;
13471         AppendComment(currentMove, p, FALSE);
13472         yyboardindex = forwardMostMove;
13473         cm = (ChessMove) Myylex();
13474     }
13475
13476     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13477         cm == WhiteWins || cm == BlackWins ||
13478         cm == GameIsDrawn || cm == GameUnfinished) {
13479         DisplayMessage("", _("No moves in game"));
13480         if (cmailMsgLoaded) {
13481             if (appData.debugMode)
13482               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13483             ClearHighlights();
13484             flipView = FALSE;
13485         }
13486         DrawPosition(FALSE, boards[currentMove]);
13487         DisplayBothClocks();
13488         gameMode = EditGame;
13489         ModeHighlight();
13490         gameFileFP = NULL;
13491         cmailOldMove = 0;
13492         return TRUE;
13493     }
13494
13495     // [HGM] PV info: routine tests if comment empty
13496     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13497         DisplayComment(currentMove - 1, commentList[currentMove]);
13498     }
13499     if (!matchMode && appData.timeDelay != 0)
13500       DrawPosition(FALSE, boards[currentMove]);
13501
13502     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13503       programStats.ok_to_send = 1;
13504     }
13505
13506     /* if the first token after the PGN tags is a move
13507      * and not move number 1, retrieve it from the parser
13508      */
13509     if (cm != MoveNumberOne)
13510         LoadGameOneMove(cm);
13511
13512     /* load the remaining moves from the file */
13513     while (LoadGameOneMove(EndOfFile)) {
13514       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13515       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13516     }
13517
13518     /* rewind to the start of the game */
13519     currentMove = backwardMostMove;
13520
13521     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13522
13523     if (oldGameMode == AnalyzeFile) {
13524       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13525       AnalyzeFileEvent();
13526     } else
13527     if (oldGameMode == AnalyzeMode) {
13528       AnalyzeFileEvent();
13529     }
13530
13531     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13532         long int w, b; // [HGM] adjourn: restore saved clock times
13533         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13534         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13535             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13536             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13537         }
13538     }
13539
13540     if(creatingBook) return TRUE;
13541     if (!matchMode && pos > 0) {
13542         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13543     } else
13544     if (matchMode || appData.timeDelay == 0) {
13545       ToEndEvent();
13546     } else if (appData.timeDelay > 0) {
13547       AutoPlayGameLoop();
13548     }
13549
13550     if (appData.debugMode)
13551         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13552
13553     loadFlag = 0; /* [HGM] true game starts */
13554     return TRUE;
13555 }
13556
13557 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13558 int
13559 ReloadPosition (int offset)
13560 {
13561     int positionNumber = lastLoadPositionNumber + offset;
13562     if (lastLoadPositionFP == NULL) {
13563         DisplayError(_("No position has been loaded yet"), 0);
13564         return FALSE;
13565     }
13566     if (positionNumber <= 0) {
13567         DisplayError(_("Can't back up any further"), 0);
13568         return FALSE;
13569     }
13570     return LoadPosition(lastLoadPositionFP, positionNumber,
13571                         lastLoadPositionTitle);
13572 }
13573
13574 /* Load the nth position from the given file */
13575 int
13576 LoadPositionFromFile (char *filename, int n, char *title)
13577 {
13578     FILE *f;
13579     char buf[MSG_SIZ];
13580
13581     if (strcmp(filename, "-") == 0) {
13582         return LoadPosition(stdin, n, "stdin");
13583     } else {
13584         f = fopen(filename, "rb");
13585         if (f == NULL) {
13586             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13587             DisplayError(buf, errno);
13588             return FALSE;
13589         } else {
13590             return LoadPosition(f, n, title);
13591         }
13592     }
13593 }
13594
13595 /* Load the nth position from the given open file, and close it */
13596 int
13597 LoadPosition (FILE *f, int positionNumber, char *title)
13598 {
13599     char *p, line[MSG_SIZ];
13600     Board initial_position;
13601     int i, j, fenMode, pn;
13602
13603     if (gameMode == Training )
13604         SetTrainingModeOff();
13605
13606     if (gameMode != BeginningOfGame) {
13607         Reset(FALSE, TRUE);
13608     }
13609     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13610         fclose(lastLoadPositionFP);
13611     }
13612     if (positionNumber == 0) positionNumber = 1;
13613     lastLoadPositionFP = f;
13614     lastLoadPositionNumber = positionNumber;
13615     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13616     if (first.pr == NoProc && !appData.noChessProgram) {
13617       StartChessProgram(&first);
13618       InitChessProgram(&first, FALSE);
13619     }
13620     pn = positionNumber;
13621     if (positionNumber < 0) {
13622         /* Negative position number means to seek to that byte offset */
13623         if (fseek(f, -positionNumber, 0) == -1) {
13624             DisplayError(_("Can't seek on position file"), 0);
13625             return FALSE;
13626         };
13627         pn = 1;
13628     } else {
13629         if (fseek(f, 0, 0) == -1) {
13630             if (f == lastLoadPositionFP ?
13631                 positionNumber == lastLoadPositionNumber + 1 :
13632                 positionNumber == 1) {
13633                 pn = 1;
13634             } else {
13635                 DisplayError(_("Can't seek on position file"), 0);
13636                 return FALSE;
13637             }
13638         }
13639     }
13640     /* See if this file is FEN or old-style xboard */
13641     if (fgets(line, MSG_SIZ, f) == NULL) {
13642         DisplayError(_("Position not found in file"), 0);
13643         return FALSE;
13644     }
13645     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13646     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13647
13648     if (pn >= 2) {
13649         if (fenMode || line[0] == '#') pn--;
13650         while (pn > 0) {
13651             /* skip positions before number pn */
13652             if (fgets(line, MSG_SIZ, f) == NULL) {
13653                 Reset(TRUE, TRUE);
13654                 DisplayError(_("Position not found in file"), 0);
13655                 return FALSE;
13656             }
13657             if (fenMode || line[0] == '#') pn--;
13658         }
13659     }
13660
13661     if (fenMode) {
13662         char *p;
13663         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13664             DisplayError(_("Bad FEN position in file"), 0);
13665             return FALSE;
13666         }
13667         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13668             sscanf(p+4, "%[^;]", bestMove);
13669         } else *bestMove = NULLCHAR;
13670         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13671             sscanf(p+4, "%[^;]", avoidMove);
13672         } else *avoidMove = NULLCHAR;
13673     } else {
13674         (void) fgets(line, MSG_SIZ, f);
13675         (void) fgets(line, MSG_SIZ, f);
13676
13677         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13678             (void) fgets(line, MSG_SIZ, f);
13679             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13680                 if (*p == ' ')
13681                   continue;
13682                 initial_position[i][j++] = CharToPiece(*p);
13683             }
13684         }
13685
13686         blackPlaysFirst = FALSE;
13687         if (!feof(f)) {
13688             (void) fgets(line, MSG_SIZ, f);
13689             if (strncmp(line, "black", strlen("black"))==0)
13690               blackPlaysFirst = TRUE;
13691         }
13692     }
13693     startedFromSetupPosition = TRUE;
13694
13695     CopyBoard(boards[0], initial_position);
13696     if (blackPlaysFirst) {
13697         currentMove = forwardMostMove = backwardMostMove = 1;
13698         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13699         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13700         CopyBoard(boards[1], initial_position);
13701         DisplayMessage("", _("Black to play"));
13702     } else {
13703         currentMove = forwardMostMove = backwardMostMove = 0;
13704         DisplayMessage("", _("White to play"));
13705     }
13706     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13707     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13708         SendToProgram("force\n", &first);
13709         SendBoard(&first, forwardMostMove);
13710     }
13711     if (appData.debugMode) {
13712 int i, j;
13713   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13714   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13715         fprintf(debugFP, "Load Position\n");
13716     }
13717
13718     if (positionNumber > 1) {
13719       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13720         DisplayTitle(line);
13721     } else {
13722         DisplayTitle(title);
13723     }
13724     gameMode = EditGame;
13725     ModeHighlight();
13726     ResetClocks();
13727     timeRemaining[0][1] = whiteTimeRemaining;
13728     timeRemaining[1][1] = blackTimeRemaining;
13729     DrawPosition(FALSE, boards[currentMove]);
13730
13731     return TRUE;
13732 }
13733
13734
13735 void
13736 CopyPlayerNameIntoFileName (char **dest, char *src)
13737 {
13738     while (*src != NULLCHAR && *src != ',') {
13739         if (*src == ' ') {
13740             *(*dest)++ = '_';
13741             src++;
13742         } else {
13743             *(*dest)++ = *src++;
13744         }
13745     }
13746 }
13747
13748 char *
13749 DefaultFileName (char *ext)
13750 {
13751     static char def[MSG_SIZ];
13752     char *p;
13753
13754     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13755         p = def;
13756         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13757         *p++ = '-';
13758         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13759         *p++ = '.';
13760         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13761     } else {
13762         def[0] = NULLCHAR;
13763     }
13764     return def;
13765 }
13766
13767 /* Save the current game to the given file */
13768 int
13769 SaveGameToFile (char *filename, int append)
13770 {
13771     FILE *f;
13772     char buf[MSG_SIZ];
13773     int result, i, t,tot=0;
13774
13775     if (strcmp(filename, "-") == 0) {
13776         return SaveGame(stdout, 0, NULL);
13777     } else {
13778         for(i=0; i<10; i++) { // upto 10 tries
13779              f = fopen(filename, append ? "a" : "w");
13780              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13781              if(f || errno != 13) break;
13782              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13783              tot += t;
13784         }
13785         if (f == NULL) {
13786             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13787             DisplayError(buf, errno);
13788             return FALSE;
13789         } else {
13790             safeStrCpy(buf, lastMsg, MSG_SIZ);
13791             DisplayMessage(_("Waiting for access to save file"), "");
13792             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13793             DisplayMessage(_("Saving game"), "");
13794             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13795             result = SaveGame(f, 0, NULL);
13796             DisplayMessage(buf, "");
13797             return result;
13798         }
13799     }
13800 }
13801
13802 char *
13803 SavePart (char *str)
13804 {
13805     static char buf[MSG_SIZ];
13806     char *p;
13807
13808     p = strchr(str, ' ');
13809     if (p == NULL) return str;
13810     strncpy(buf, str, p - str);
13811     buf[p - str] = NULLCHAR;
13812     return buf;
13813 }
13814
13815 #define PGN_MAX_LINE 75
13816
13817 #define PGN_SIDE_WHITE  0
13818 #define PGN_SIDE_BLACK  1
13819
13820 static int
13821 FindFirstMoveOutOfBook (int side)
13822 {
13823     int result = -1;
13824
13825     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13826         int index = backwardMostMove;
13827         int has_book_hit = 0;
13828
13829         if( (index % 2) != side ) {
13830             index++;
13831         }
13832
13833         while( index < forwardMostMove ) {
13834             /* Check to see if engine is in book */
13835             int depth = pvInfoList[index].depth;
13836             int score = pvInfoList[index].score;
13837             int in_book = 0;
13838
13839             if( depth <= 2 ) {
13840                 in_book = 1;
13841             }
13842             else if( score == 0 && depth == 63 ) {
13843                 in_book = 1; /* Zappa */
13844             }
13845             else if( score == 2 && depth == 99 ) {
13846                 in_book = 1; /* Abrok */
13847             }
13848
13849             has_book_hit += in_book;
13850
13851             if( ! in_book ) {
13852                 result = index;
13853
13854                 break;
13855             }
13856
13857             index += 2;
13858         }
13859     }
13860
13861     return result;
13862 }
13863
13864 void
13865 GetOutOfBookInfo (char * buf)
13866 {
13867     int oob[2];
13868     int i;
13869     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13870
13871     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13872     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13873
13874     *buf = '\0';
13875
13876     if( oob[0] >= 0 || oob[1] >= 0 ) {
13877         for( i=0; i<2; i++ ) {
13878             int idx = oob[i];
13879
13880             if( idx >= 0 ) {
13881                 if( i > 0 && oob[0] >= 0 ) {
13882                     strcat( buf, "   " );
13883                 }
13884
13885                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13886                 sprintf( buf+strlen(buf), "%s%.2f",
13887                     pvInfoList[idx].score >= 0 ? "+" : "",
13888                     pvInfoList[idx].score / 100.0 );
13889             }
13890         }
13891     }
13892 }
13893
13894 /* Save game in PGN style */
13895 static void
13896 SaveGamePGN2 (FILE *f)
13897 {
13898     int i, offset, linelen, newblock;
13899 //    char *movetext;
13900     char numtext[32];
13901     int movelen, numlen, blank;
13902     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13903
13904     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13905
13906     PrintPGNTags(f, &gameInfo);
13907
13908     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13909
13910     if (backwardMostMove > 0 || startedFromSetupPosition) {
13911         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13912         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13913         fprintf(f, "\n{--------------\n");
13914         PrintPosition(f, backwardMostMove);
13915         fprintf(f, "--------------}\n");
13916         free(fen);
13917     }
13918     else {
13919         /* [AS] Out of book annotation */
13920         if( appData.saveOutOfBookInfo ) {
13921             char buf[64];
13922
13923             GetOutOfBookInfo( buf );
13924
13925             if( buf[0] != '\0' ) {
13926                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13927             }
13928         }
13929
13930         fprintf(f, "\n");
13931     }
13932
13933     i = backwardMostMove;
13934     linelen = 0;
13935     newblock = TRUE;
13936
13937     while (i < forwardMostMove) {
13938         /* Print comments preceding this move */
13939         if (commentList[i] != NULL) {
13940             if (linelen > 0) fprintf(f, "\n");
13941             fprintf(f, "%s", commentList[i]);
13942             linelen = 0;
13943             newblock = TRUE;
13944         }
13945
13946         /* Format move number */
13947         if ((i % 2) == 0)
13948           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13949         else
13950           if (newblock)
13951             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13952           else
13953             numtext[0] = NULLCHAR;
13954
13955         numlen = strlen(numtext);
13956         newblock = FALSE;
13957
13958         /* Print move number */
13959         blank = linelen > 0 && numlen > 0;
13960         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13961             fprintf(f, "\n");
13962             linelen = 0;
13963             blank = 0;
13964         }
13965         if (blank) {
13966             fprintf(f, " ");
13967             linelen++;
13968         }
13969         fprintf(f, "%s", numtext);
13970         linelen += numlen;
13971
13972         /* Get move */
13973         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13974         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13975
13976         /* Print move */
13977         blank = linelen > 0 && movelen > 0;
13978         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13979             fprintf(f, "\n");
13980             linelen = 0;
13981             blank = 0;
13982         }
13983         if (blank) {
13984             fprintf(f, " ");
13985             linelen++;
13986         }
13987         fprintf(f, "%s", move_buffer);
13988         linelen += movelen;
13989
13990         /* [AS] Add PV info if present */
13991         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13992             /* [HGM] add time */
13993             char buf[MSG_SIZ]; int seconds;
13994
13995             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13996
13997             if( seconds <= 0)
13998               buf[0] = 0;
13999             else
14000               if( seconds < 30 )
14001                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14002               else
14003                 {
14004                   seconds = (seconds + 4)/10; // round to full seconds
14005                   if( seconds < 60 )
14006                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14007                   else
14008                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14009                 }
14010
14011             if(appData.cumulativeTimePGN) {
14012                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14013             }
14014
14015             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14016                       pvInfoList[i].score >= 0 ? "+" : "",
14017                       pvInfoList[i].score / 100.0,
14018                       pvInfoList[i].depth,
14019                       buf );
14020
14021             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14022
14023             /* Print score/depth */
14024             blank = linelen > 0 && movelen > 0;
14025             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14026                 fprintf(f, "\n");
14027                 linelen = 0;
14028                 blank = 0;
14029             }
14030             if (blank) {
14031                 fprintf(f, " ");
14032                 linelen++;
14033             }
14034             fprintf(f, "%s", move_buffer);
14035             linelen += movelen;
14036         }
14037
14038         i++;
14039     }
14040
14041     /* Start a new line */
14042     if (linelen > 0) fprintf(f, "\n");
14043
14044     /* Print comments after last move */
14045     if (commentList[i] != NULL) {
14046         fprintf(f, "%s\n", commentList[i]);
14047     }
14048
14049     /* Print result */
14050     if (gameInfo.resultDetails != NULL &&
14051         gameInfo.resultDetails[0] != NULLCHAR) {
14052         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14053         if(gameInfo.result == GameUnfinished && appData.clockMode &&
14054            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14055             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14056         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14057     } else {
14058         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14059     }
14060 }
14061
14062 /* Save game in PGN style and close the file */
14063 int
14064 SaveGamePGN (FILE *f)
14065 {
14066     SaveGamePGN2(f);
14067     fclose(f);
14068     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14069     return TRUE;
14070 }
14071
14072 /* Save game in old style and close the file */
14073 int
14074 SaveGameOldStyle (FILE *f)
14075 {
14076     int i, offset;
14077     time_t tm;
14078
14079     tm = time((time_t *) NULL);
14080
14081     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14082     PrintOpponents(f);
14083
14084     if (backwardMostMove > 0 || startedFromSetupPosition) {
14085         fprintf(f, "\n[--------------\n");
14086         PrintPosition(f, backwardMostMove);
14087         fprintf(f, "--------------]\n");
14088     } else {
14089         fprintf(f, "\n");
14090     }
14091
14092     i = backwardMostMove;
14093     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14094
14095     while (i < forwardMostMove) {
14096         if (commentList[i] != NULL) {
14097             fprintf(f, "[%s]\n", commentList[i]);
14098         }
14099
14100         if ((i % 2) == 1) {
14101             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14102             i++;
14103         } else {
14104             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14105             i++;
14106             if (commentList[i] != NULL) {
14107                 fprintf(f, "\n");
14108                 continue;
14109             }
14110             if (i >= forwardMostMove) {
14111                 fprintf(f, "\n");
14112                 break;
14113             }
14114             fprintf(f, "%s\n", parseList[i]);
14115             i++;
14116         }
14117     }
14118
14119     if (commentList[i] != NULL) {
14120         fprintf(f, "[%s]\n", commentList[i]);
14121     }
14122
14123     /* This isn't really the old style, but it's close enough */
14124     if (gameInfo.resultDetails != NULL &&
14125         gameInfo.resultDetails[0] != NULLCHAR) {
14126         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14127                 gameInfo.resultDetails);
14128     } else {
14129         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14130     }
14131
14132     fclose(f);
14133     return TRUE;
14134 }
14135
14136 /* Save the current game to open file f and close the file */
14137 int
14138 SaveGame (FILE *f, int dummy, char *dummy2)
14139 {
14140     if (gameMode == EditPosition) EditPositionDone(TRUE);
14141     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14142     if (appData.oldSaveStyle)
14143       return SaveGameOldStyle(f);
14144     else
14145       return SaveGamePGN(f);
14146 }
14147
14148 /* Save the current position to the given file */
14149 int
14150 SavePositionToFile (char *filename)
14151 {
14152     FILE *f;
14153     char buf[MSG_SIZ];
14154
14155     if (strcmp(filename, "-") == 0) {
14156         return SavePosition(stdout, 0, NULL);
14157     } else {
14158         f = fopen(filename, "a");
14159         if (f == NULL) {
14160             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14161             DisplayError(buf, errno);
14162             return FALSE;
14163         } else {
14164             safeStrCpy(buf, lastMsg, MSG_SIZ);
14165             DisplayMessage(_("Waiting for access to save file"), "");
14166             flock(fileno(f), LOCK_EX); // [HGM] lock
14167             DisplayMessage(_("Saving position"), "");
14168             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14169             SavePosition(f, 0, NULL);
14170             DisplayMessage(buf, "");
14171             return TRUE;
14172         }
14173     }
14174 }
14175
14176 /* Save the current position to the given open file and close the file */
14177 int
14178 SavePosition (FILE *f, int dummy, char *dummy2)
14179 {
14180     time_t tm;
14181     char *fen;
14182
14183     if (gameMode == EditPosition) EditPositionDone(TRUE);
14184     if (appData.oldSaveStyle) {
14185         tm = time((time_t *) NULL);
14186
14187         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14188         PrintOpponents(f);
14189         fprintf(f, "[--------------\n");
14190         PrintPosition(f, currentMove);
14191         fprintf(f, "--------------]\n");
14192     } else {
14193         fen = PositionToFEN(currentMove, NULL, 1);
14194         fprintf(f, "%s\n", fen);
14195         free(fen);
14196     }
14197     fclose(f);
14198     return TRUE;
14199 }
14200
14201 void
14202 ReloadCmailMsgEvent (int unregister)
14203 {
14204 #if !WIN32
14205     static char *inFilename = NULL;
14206     static char *outFilename;
14207     int i;
14208     struct stat inbuf, outbuf;
14209     int status;
14210
14211     /* Any registered moves are unregistered if unregister is set, */
14212     /* i.e. invoked by the signal handler */
14213     if (unregister) {
14214         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14215             cmailMoveRegistered[i] = FALSE;
14216             if (cmailCommentList[i] != NULL) {
14217                 free(cmailCommentList[i]);
14218                 cmailCommentList[i] = NULL;
14219             }
14220         }
14221         nCmailMovesRegistered = 0;
14222     }
14223
14224     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14225         cmailResult[i] = CMAIL_NOT_RESULT;
14226     }
14227     nCmailResults = 0;
14228
14229     if (inFilename == NULL) {
14230         /* Because the filenames are static they only get malloced once  */
14231         /* and they never get freed                                      */
14232         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14233         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14234
14235         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14236         sprintf(outFilename, "%s.out", appData.cmailGameName);
14237     }
14238
14239     status = stat(outFilename, &outbuf);
14240     if (status < 0) {
14241         cmailMailedMove = FALSE;
14242     } else {
14243         status = stat(inFilename, &inbuf);
14244         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14245     }
14246
14247     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14248        counts the games, notes how each one terminated, etc.
14249
14250        It would be nice to remove this kludge and instead gather all
14251        the information while building the game list.  (And to keep it
14252        in the game list nodes instead of having a bunch of fixed-size
14253        parallel arrays.)  Note this will require getting each game's
14254        termination from the PGN tags, as the game list builder does
14255        not process the game moves.  --mann
14256        */
14257     cmailMsgLoaded = TRUE;
14258     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14259
14260     /* Load first game in the file or popup game menu */
14261     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14262
14263 #endif /* !WIN32 */
14264     return;
14265 }
14266
14267 int
14268 RegisterMove ()
14269 {
14270     FILE *f;
14271     char string[MSG_SIZ];
14272
14273     if (   cmailMailedMove
14274         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14275         return TRUE;            /* Allow free viewing  */
14276     }
14277
14278     /* Unregister move to ensure that we don't leave RegisterMove        */
14279     /* with the move registered when the conditions for registering no   */
14280     /* longer hold                                                       */
14281     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14282         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14283         nCmailMovesRegistered --;
14284
14285         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14286           {
14287               free(cmailCommentList[lastLoadGameNumber - 1]);
14288               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14289           }
14290     }
14291
14292     if (cmailOldMove == -1) {
14293         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14294         return FALSE;
14295     }
14296
14297     if (currentMove > cmailOldMove + 1) {
14298         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14299         return FALSE;
14300     }
14301
14302     if (currentMove < cmailOldMove) {
14303         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14304         return FALSE;
14305     }
14306
14307     if (forwardMostMove > currentMove) {
14308         /* Silently truncate extra moves */
14309         TruncateGame();
14310     }
14311
14312     if (   (currentMove == cmailOldMove + 1)
14313         || (   (currentMove == cmailOldMove)
14314             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14315                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14316         if (gameInfo.result != GameUnfinished) {
14317             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14318         }
14319
14320         if (commentList[currentMove] != NULL) {
14321             cmailCommentList[lastLoadGameNumber - 1]
14322               = StrSave(commentList[currentMove]);
14323         }
14324         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14325
14326         if (appData.debugMode)
14327           fprintf(debugFP, "Saving %s for game %d\n",
14328                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14329
14330         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14331
14332         f = fopen(string, "w");
14333         if (appData.oldSaveStyle) {
14334             SaveGameOldStyle(f); /* also closes the file */
14335
14336             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14337             f = fopen(string, "w");
14338             SavePosition(f, 0, NULL); /* also closes the file */
14339         } else {
14340             fprintf(f, "{--------------\n");
14341             PrintPosition(f, currentMove);
14342             fprintf(f, "--------------}\n\n");
14343
14344             SaveGame(f, 0, NULL); /* also closes the file*/
14345         }
14346
14347         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14348         nCmailMovesRegistered ++;
14349     } else if (nCmailGames == 1) {
14350         DisplayError(_("You have not made a move yet"), 0);
14351         return FALSE;
14352     }
14353
14354     return TRUE;
14355 }
14356
14357 void
14358 MailMoveEvent ()
14359 {
14360 #if !WIN32
14361     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14362     FILE *commandOutput;
14363     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14364     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14365     int nBuffers;
14366     int i;
14367     int archived;
14368     char *arcDir;
14369
14370     if (! cmailMsgLoaded) {
14371         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14372         return;
14373     }
14374
14375     if (nCmailGames == nCmailResults) {
14376         DisplayError(_("No unfinished games"), 0);
14377         return;
14378     }
14379
14380 #if CMAIL_PROHIBIT_REMAIL
14381     if (cmailMailedMove) {
14382       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);
14383         DisplayError(msg, 0);
14384         return;
14385     }
14386 #endif
14387
14388     if (! (cmailMailedMove || RegisterMove())) return;
14389
14390     if (   cmailMailedMove
14391         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14392       snprintf(string, MSG_SIZ, partCommandString,
14393                appData.debugMode ? " -v" : "", appData.cmailGameName);
14394         commandOutput = popen(string, "r");
14395
14396         if (commandOutput == NULL) {
14397             DisplayError(_("Failed to invoke cmail"), 0);
14398         } else {
14399             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14400                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14401             }
14402             if (nBuffers > 1) {
14403                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14404                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14405                 nBytes = MSG_SIZ - 1;
14406             } else {
14407                 (void) memcpy(msg, buffer, nBytes);
14408             }
14409             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14410
14411             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14412                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14413
14414                 archived = TRUE;
14415                 for (i = 0; i < nCmailGames; i ++) {
14416                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14417                         archived = FALSE;
14418                     }
14419                 }
14420                 if (   archived
14421                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14422                         != NULL)) {
14423                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14424                            arcDir,
14425                            appData.cmailGameName,
14426                            gameInfo.date);
14427                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14428                     cmailMsgLoaded = FALSE;
14429                 }
14430             }
14431
14432             DisplayInformation(msg);
14433             pclose(commandOutput);
14434         }
14435     } else {
14436         if ((*cmailMsg) != '\0') {
14437             DisplayInformation(cmailMsg);
14438         }
14439     }
14440
14441     return;
14442 #endif /* !WIN32 */
14443 }
14444
14445 char *
14446 CmailMsg ()
14447 {
14448 #if WIN32
14449     return NULL;
14450 #else
14451     int  prependComma = 0;
14452     char number[5];
14453     char string[MSG_SIZ];       /* Space for game-list */
14454     int  i;
14455
14456     if (!cmailMsgLoaded) return "";
14457
14458     if (cmailMailedMove) {
14459       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14460     } else {
14461         /* Create a list of games left */
14462       snprintf(string, MSG_SIZ, "[");
14463         for (i = 0; i < nCmailGames; i ++) {
14464             if (! (   cmailMoveRegistered[i]
14465                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14466                 if (prependComma) {
14467                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14468                 } else {
14469                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14470                     prependComma = 1;
14471                 }
14472
14473                 strcat(string, number);
14474             }
14475         }
14476         strcat(string, "]");
14477
14478         if (nCmailMovesRegistered + nCmailResults == 0) {
14479             switch (nCmailGames) {
14480               case 1:
14481                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14482                 break;
14483
14484               case 2:
14485                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14486                 break;
14487
14488               default:
14489                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14490                          nCmailGames);
14491                 break;
14492             }
14493         } else {
14494             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14495               case 1:
14496                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14497                          string);
14498                 break;
14499
14500               case 0:
14501                 if (nCmailResults == nCmailGames) {
14502                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14503                 } else {
14504                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14505                 }
14506                 break;
14507
14508               default:
14509                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14510                          string);
14511             }
14512         }
14513     }
14514     return cmailMsg;
14515 #endif /* WIN32 */
14516 }
14517
14518 void
14519 ResetGameEvent ()
14520 {
14521     if (gameMode == Training)
14522       SetTrainingModeOff();
14523
14524     Reset(TRUE, TRUE);
14525     cmailMsgLoaded = FALSE;
14526     if (appData.icsActive) {
14527       SendToICS(ics_prefix);
14528       SendToICS("refresh\n");
14529     }
14530 }
14531
14532 void
14533 ExitEvent (int status)
14534 {
14535     exiting++;
14536     if (exiting > 2) {
14537       /* Give up on clean exit */
14538       exit(status);
14539     }
14540     if (exiting > 1) {
14541       /* Keep trying for clean exit */
14542       return;
14543     }
14544
14545     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14546     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14547
14548     if (telnetISR != NULL) {
14549       RemoveInputSource(telnetISR);
14550     }
14551     if (icsPR != NoProc) {
14552       DestroyChildProcess(icsPR, TRUE);
14553     }
14554
14555     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14556     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14557
14558     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14559     /* make sure this other one finishes before killing it!                  */
14560     if(endingGame) { int count = 0;
14561         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14562         while(endingGame && count++ < 10) DoSleep(1);
14563         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14564     }
14565
14566     /* Kill off chess programs */
14567     if (first.pr != NoProc) {
14568         ExitAnalyzeMode();
14569
14570         DoSleep( appData.delayBeforeQuit );
14571         SendToProgram("quit\n", &first);
14572         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14573     }
14574     if (second.pr != NoProc) {
14575         DoSleep( appData.delayBeforeQuit );
14576         SendToProgram("quit\n", &second);
14577         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14578     }
14579     if (first.isr != NULL) {
14580         RemoveInputSource(first.isr);
14581     }
14582     if (second.isr != NULL) {
14583         RemoveInputSource(second.isr);
14584     }
14585
14586     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14587     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14588
14589     ShutDownFrontEnd();
14590     exit(status);
14591 }
14592
14593 void
14594 PauseEngine (ChessProgramState *cps)
14595 {
14596     SendToProgram("pause\n", cps);
14597     cps->pause = 2;
14598 }
14599
14600 void
14601 UnPauseEngine (ChessProgramState *cps)
14602 {
14603     SendToProgram("resume\n", cps);
14604     cps->pause = 1;
14605 }
14606
14607 void
14608 PauseEvent ()
14609 {
14610     if (appData.debugMode)
14611         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14612     if (pausing) {
14613         pausing = FALSE;
14614         ModeHighlight();
14615         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14616             StartClocks();
14617             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14618                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14619                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14620             }
14621             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14622             HandleMachineMove(stashedInputMove, stalledEngine);
14623             stalledEngine = NULL;
14624             return;
14625         }
14626         if (gameMode == MachinePlaysWhite ||
14627             gameMode == TwoMachinesPlay   ||
14628             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14629             if(first.pause)  UnPauseEngine(&first);
14630             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14631             if(second.pause) UnPauseEngine(&second);
14632             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14633             StartClocks();
14634         } else {
14635             DisplayBothClocks();
14636         }
14637         if (gameMode == PlayFromGameFile) {
14638             if (appData.timeDelay >= 0)
14639                 AutoPlayGameLoop();
14640         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14641             Reset(FALSE, TRUE);
14642             SendToICS(ics_prefix);
14643             SendToICS("refresh\n");
14644         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14645             ForwardInner(forwardMostMove);
14646         }
14647         pauseExamInvalid = FALSE;
14648     } else {
14649         switch (gameMode) {
14650           default:
14651             return;
14652           case IcsExamining:
14653             pauseExamForwardMostMove = forwardMostMove;
14654             pauseExamInvalid = FALSE;
14655             /* fall through */
14656           case IcsObserving:
14657           case IcsPlayingWhite:
14658           case IcsPlayingBlack:
14659             pausing = TRUE;
14660             ModeHighlight();
14661             return;
14662           case PlayFromGameFile:
14663             (void) StopLoadGameTimer();
14664             pausing = TRUE;
14665             ModeHighlight();
14666             break;
14667           case BeginningOfGame:
14668             if (appData.icsActive) return;
14669             /* else fall through */
14670           case MachinePlaysWhite:
14671           case MachinePlaysBlack:
14672           case TwoMachinesPlay:
14673             if (forwardMostMove == 0)
14674               return;           /* don't pause if no one has moved */
14675             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14676                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14677                 if(onMove->pause) {           // thinking engine can be paused
14678                     PauseEngine(onMove);      // do it
14679                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14680                         PauseEngine(onMove->other);
14681                     else
14682                         SendToProgram("easy\n", onMove->other);
14683                     StopClocks();
14684                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14685             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14686                 if(first.pause) {
14687                     PauseEngine(&first);
14688                     StopClocks();
14689                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14690             } else { // human on move, pause pondering by either method
14691                 if(first.pause)
14692                     PauseEngine(&first);
14693                 else if(appData.ponderNextMove)
14694                     SendToProgram("easy\n", &first);
14695                 StopClocks();
14696             }
14697             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14698           case AnalyzeMode:
14699             pausing = TRUE;
14700             ModeHighlight();
14701             break;
14702         }
14703     }
14704 }
14705
14706 void
14707 EditCommentEvent ()
14708 {
14709     char title[MSG_SIZ];
14710
14711     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14712       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14713     } else {
14714       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14715                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14716                parseList[currentMove - 1]);
14717     }
14718
14719     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14720 }
14721
14722
14723 void
14724 EditTagsEvent ()
14725 {
14726     char *tags = PGNTags(&gameInfo);
14727     bookUp = FALSE;
14728     EditTagsPopUp(tags, NULL);
14729     free(tags);
14730 }
14731
14732 void
14733 ToggleSecond ()
14734 {
14735   if(second.analyzing) {
14736     SendToProgram("exit\n", &second);
14737     second.analyzing = FALSE;
14738   } else {
14739     if (second.pr == NoProc) StartChessProgram(&second);
14740     InitChessProgram(&second, FALSE);
14741     FeedMovesToProgram(&second, currentMove);
14742
14743     SendToProgram("analyze\n", &second);
14744     second.analyzing = TRUE;
14745   }
14746 }
14747
14748 /* Toggle ShowThinking */
14749 void
14750 ToggleShowThinking()
14751 {
14752   appData.showThinking = !appData.showThinking;
14753   ShowThinkingEvent();
14754 }
14755
14756 int
14757 AnalyzeModeEvent ()
14758 {
14759     char buf[MSG_SIZ];
14760
14761     if (!first.analysisSupport) {
14762       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14763       DisplayError(buf, 0);
14764       return 0;
14765     }
14766     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14767     if (appData.icsActive) {
14768         if (gameMode != IcsObserving) {
14769           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14770             DisplayError(buf, 0);
14771             /* secure check */
14772             if (appData.icsEngineAnalyze) {
14773                 if (appData.debugMode)
14774                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14775                 ExitAnalyzeMode();
14776                 ModeHighlight();
14777             }
14778             return 0;
14779         }
14780         /* if enable, user wants to disable icsEngineAnalyze */
14781         if (appData.icsEngineAnalyze) {
14782                 ExitAnalyzeMode();
14783                 ModeHighlight();
14784                 return 0;
14785         }
14786         appData.icsEngineAnalyze = TRUE;
14787         if (appData.debugMode)
14788             fprintf(debugFP, "ICS engine analyze starting... \n");
14789     }
14790
14791     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14792     if (appData.noChessProgram || gameMode == AnalyzeMode)
14793       return 0;
14794
14795     if (gameMode != AnalyzeFile) {
14796         if (!appData.icsEngineAnalyze) {
14797                EditGameEvent();
14798                if (gameMode != EditGame) return 0;
14799         }
14800         if (!appData.showThinking) ToggleShowThinking();
14801         ResurrectChessProgram();
14802         SendToProgram("analyze\n", &first);
14803         first.analyzing = TRUE;
14804         /*first.maybeThinking = TRUE;*/
14805         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14806         EngineOutputPopUp();
14807     }
14808     if (!appData.icsEngineAnalyze) {
14809         gameMode = AnalyzeMode;
14810         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14811     }
14812     pausing = FALSE;
14813     ModeHighlight();
14814     SetGameInfo();
14815
14816     StartAnalysisClock();
14817     GetTimeMark(&lastNodeCountTime);
14818     lastNodeCount = 0;
14819     return 1;
14820 }
14821
14822 void
14823 AnalyzeFileEvent ()
14824 {
14825     if (appData.noChessProgram || gameMode == AnalyzeFile)
14826       return;
14827
14828     if (!first.analysisSupport) {
14829       char buf[MSG_SIZ];
14830       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14831       DisplayError(buf, 0);
14832       return;
14833     }
14834
14835     if (gameMode != AnalyzeMode) {
14836         keepInfo = 1; // mere annotating should not alter PGN tags
14837         EditGameEvent();
14838         keepInfo = 0;
14839         if (gameMode != EditGame) return;
14840         if (!appData.showThinking) ToggleShowThinking();
14841         ResurrectChessProgram();
14842         SendToProgram("analyze\n", &first);
14843         first.analyzing = TRUE;
14844         /*first.maybeThinking = TRUE;*/
14845         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14846         EngineOutputPopUp();
14847     }
14848     gameMode = AnalyzeFile;
14849     pausing = FALSE;
14850     ModeHighlight();
14851
14852     StartAnalysisClock();
14853     GetTimeMark(&lastNodeCountTime);
14854     lastNodeCount = 0;
14855     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14856     AnalysisPeriodicEvent(1);
14857 }
14858
14859 void
14860 MachineWhiteEvent ()
14861 {
14862     char buf[MSG_SIZ];
14863     char *bookHit = NULL;
14864
14865     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14866       return;
14867
14868
14869     if (gameMode == PlayFromGameFile ||
14870         gameMode == TwoMachinesPlay  ||
14871         gameMode == Training         ||
14872         gameMode == AnalyzeMode      ||
14873         gameMode == EndOfGame)
14874         EditGameEvent();
14875
14876     if (gameMode == EditPosition)
14877         EditPositionDone(TRUE);
14878
14879     if (!WhiteOnMove(currentMove)) {
14880         DisplayError(_("It is not White's turn"), 0);
14881         return;
14882     }
14883
14884     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14885       ExitAnalyzeMode();
14886
14887     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14888         gameMode == AnalyzeFile)
14889         TruncateGame();
14890
14891     ResurrectChessProgram();    /* in case it isn't running */
14892     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14893         gameMode = MachinePlaysWhite;
14894         ResetClocks();
14895     } else
14896     gameMode = MachinePlaysWhite;
14897     pausing = FALSE;
14898     ModeHighlight();
14899     SetGameInfo();
14900     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14901     DisplayTitle(buf);
14902     if (first.sendName) {
14903       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14904       SendToProgram(buf, &first);
14905     }
14906     if (first.sendTime) {
14907       if (first.useColors) {
14908         SendToProgram("black\n", &first); /*gnu kludge*/
14909       }
14910       SendTimeRemaining(&first, TRUE);
14911     }
14912     if (first.useColors) {
14913       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14914     }
14915     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14916     SetMachineThinkingEnables();
14917     first.maybeThinking = TRUE;
14918     StartClocks();
14919     firstMove = FALSE;
14920
14921     if (appData.autoFlipView && !flipView) {
14922       flipView = !flipView;
14923       DrawPosition(FALSE, NULL);
14924       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14925     }
14926
14927     if(bookHit) { // [HGM] book: simulate book reply
14928         static char bookMove[MSG_SIZ]; // a bit generous?
14929
14930         programStats.nodes = programStats.depth = programStats.time =
14931         programStats.score = programStats.got_only_move = 0;
14932         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14933
14934         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14935         strcat(bookMove, bookHit);
14936         savedMessage = bookMove; // args for deferred call
14937         savedState = &first;
14938         ScheduleDelayedEvent(DeferredBookMove, 1);
14939     }
14940 }
14941
14942 void
14943 MachineBlackEvent ()
14944 {
14945   char buf[MSG_SIZ];
14946   char *bookHit = NULL;
14947
14948     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14949         return;
14950
14951
14952     if (gameMode == PlayFromGameFile ||
14953         gameMode == TwoMachinesPlay  ||
14954         gameMode == Training         ||
14955         gameMode == AnalyzeMode      ||
14956         gameMode == EndOfGame)
14957         EditGameEvent();
14958
14959     if (gameMode == EditPosition)
14960         EditPositionDone(TRUE);
14961
14962     if (WhiteOnMove(currentMove)) {
14963         DisplayError(_("It is not Black's turn"), 0);
14964         return;
14965     }
14966
14967     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14968       ExitAnalyzeMode();
14969
14970     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14971         gameMode == AnalyzeFile)
14972         TruncateGame();
14973
14974     ResurrectChessProgram();    /* in case it isn't running */
14975     gameMode = MachinePlaysBlack;
14976     pausing = FALSE;
14977     ModeHighlight();
14978     SetGameInfo();
14979     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14980     DisplayTitle(buf);
14981     if (first.sendName) {
14982       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14983       SendToProgram(buf, &first);
14984     }
14985     if (first.sendTime) {
14986       if (first.useColors) {
14987         SendToProgram("white\n", &first); /*gnu kludge*/
14988       }
14989       SendTimeRemaining(&first, FALSE);
14990     }
14991     if (first.useColors) {
14992       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14993     }
14994     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14995     SetMachineThinkingEnables();
14996     first.maybeThinking = TRUE;
14997     StartClocks();
14998
14999     if (appData.autoFlipView && flipView) {
15000       flipView = !flipView;
15001       DrawPosition(FALSE, NULL);
15002       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15003     }
15004     if(bookHit) { // [HGM] book: simulate book reply
15005         static char bookMove[MSG_SIZ]; // a bit generous?
15006
15007         programStats.nodes = programStats.depth = programStats.time =
15008         programStats.score = programStats.got_only_move = 0;
15009         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15010
15011         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15012         strcat(bookMove, bookHit);
15013         savedMessage = bookMove; // args for deferred call
15014         savedState = &first;
15015         ScheduleDelayedEvent(DeferredBookMove, 1);
15016     }
15017 }
15018
15019
15020 void
15021 DisplayTwoMachinesTitle ()
15022 {
15023     char buf[MSG_SIZ];
15024     if (appData.matchGames > 0) {
15025         if(appData.tourneyFile[0]) {
15026           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15027                    gameInfo.white, _("vs."), gameInfo.black,
15028                    nextGame+1, appData.matchGames+1,
15029                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15030         } else
15031         if (first.twoMachinesColor[0] == 'w') {
15032           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15033                    gameInfo.white, _("vs."),  gameInfo.black,
15034                    first.matchWins, second.matchWins,
15035                    matchGame - 1 - (first.matchWins + second.matchWins));
15036         } else {
15037           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15038                    gameInfo.white, _("vs."), gameInfo.black,
15039                    second.matchWins, first.matchWins,
15040                    matchGame - 1 - (first.matchWins + second.matchWins));
15041         }
15042     } else {
15043       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15044     }
15045     DisplayTitle(buf);
15046 }
15047
15048 void
15049 SettingsMenuIfReady ()
15050 {
15051   if (second.lastPing != second.lastPong) {
15052     DisplayMessage("", _("Waiting for second chess program"));
15053     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15054     return;
15055   }
15056   ThawUI();
15057   DisplayMessage("", "");
15058   SettingsPopUp(&second);
15059 }
15060
15061 int
15062 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15063 {
15064     char buf[MSG_SIZ];
15065     if (cps->pr == NoProc) {
15066         StartChessProgram(cps);
15067         if (cps->protocolVersion == 1) {
15068           retry();
15069           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15070         } else {
15071           /* kludge: allow timeout for initial "feature" command */
15072           if(retry != TwoMachinesEventIfReady) FreezeUI();
15073           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15074           DisplayMessage("", buf);
15075           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15076         }
15077         return 1;
15078     }
15079     return 0;
15080 }
15081
15082 void
15083 TwoMachinesEvent P((void))
15084 {
15085     int i, move = forwardMostMove;
15086     char buf[MSG_SIZ];
15087     ChessProgramState *onmove;
15088     char *bookHit = NULL;
15089     static int stalling = 0;
15090     TimeMark now;
15091     long wait;
15092
15093     if (appData.noChessProgram) return;
15094
15095     switch (gameMode) {
15096       case TwoMachinesPlay:
15097         return;
15098       case MachinePlaysWhite:
15099       case MachinePlaysBlack:
15100         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15101             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15102             return;
15103         }
15104         /* fall through */
15105       case BeginningOfGame:
15106       case PlayFromGameFile:
15107       case EndOfGame:
15108         EditGameEvent();
15109         if (gameMode != EditGame) return;
15110         break;
15111       case EditPosition:
15112         EditPositionDone(TRUE);
15113         break;
15114       case AnalyzeMode:
15115       case AnalyzeFile:
15116         ExitAnalyzeMode();
15117         break;
15118       case EditGame:
15119       default:
15120         break;
15121     }
15122
15123 //    forwardMostMove = currentMove;
15124     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15125     startingEngine = TRUE;
15126
15127     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15128
15129     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15130     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15131       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15132       return;
15133     }
15134   if(!appData.epd) {
15135     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15136
15137     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15138                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15139         startingEngine = matchMode = FALSE;
15140         DisplayError("second engine does not play this", 0);
15141         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15142         EditGameEvent(); // switch back to EditGame mode
15143         return;
15144     }
15145
15146     if(!stalling) {
15147       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15148       SendToProgram("force\n", &second);
15149       stalling = 1;
15150       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15151       return;
15152     }
15153   }
15154     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15155     if(appData.matchPause>10000 || appData.matchPause<10)
15156                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15157     wait = SubtractTimeMarks(&now, &pauseStart);
15158     if(wait < appData.matchPause) {
15159         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15160         return;
15161     }
15162     // we are now committed to starting the game
15163     stalling = 0;
15164     DisplayMessage("", "");
15165   if(!appData.epd) {
15166     if (startedFromSetupPosition) {
15167         SendBoard(&second, backwardMostMove);
15168     if (appData.debugMode) {
15169         fprintf(debugFP, "Two Machines\n");
15170     }
15171     }
15172     for (i = backwardMostMove; i < forwardMostMove; i++) {
15173         SendMoveToProgram(i, &second);
15174     }
15175   }
15176
15177     gameMode = TwoMachinesPlay;
15178     pausing = startingEngine = FALSE;
15179     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15180     SetGameInfo();
15181     DisplayTwoMachinesTitle();
15182     firstMove = TRUE;
15183     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15184         onmove = &first;
15185     } else {
15186         onmove = &second;
15187     }
15188     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15189     SendToProgram(first.computerString, &first);
15190     if (first.sendName) {
15191       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15192       SendToProgram(buf, &first);
15193     }
15194   if(!appData.epd) {
15195     SendToProgram(second.computerString, &second);
15196     if (second.sendName) {
15197       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15198       SendToProgram(buf, &second);
15199     }
15200   }
15201
15202     if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15203         ResetClocks();
15204         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15205         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15206     }
15207     if (onmove->sendTime) {
15208       if (onmove->useColors) {
15209         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15210       }
15211       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15212     }
15213     if (onmove->useColors) {
15214       SendToProgram(onmove->twoMachinesColor, onmove);
15215     }
15216     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15217 //    SendToProgram("go\n", onmove);
15218     onmove->maybeThinking = TRUE;
15219     SetMachineThinkingEnables();
15220
15221     StartClocks();
15222
15223     if(bookHit) { // [HGM] book: simulate book reply
15224         static char bookMove[MSG_SIZ]; // a bit generous?
15225
15226         programStats.nodes = programStats.depth = programStats.time =
15227         programStats.score = programStats.got_only_move = 0;
15228         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15229
15230         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15231         strcat(bookMove, bookHit);
15232         savedMessage = bookMove; // args for deferred call
15233         savedState = onmove;
15234         ScheduleDelayedEvent(DeferredBookMove, 1);
15235     }
15236 }
15237
15238 void
15239 TrainingEvent ()
15240 {
15241     if (gameMode == Training) {
15242       SetTrainingModeOff();
15243       gameMode = PlayFromGameFile;
15244       DisplayMessage("", _("Training mode off"));
15245     } else {
15246       gameMode = Training;
15247       animateTraining = appData.animate;
15248
15249       /* make sure we are not already at the end of the game */
15250       if (currentMove < forwardMostMove) {
15251         SetTrainingModeOn();
15252         DisplayMessage("", _("Training mode on"));
15253       } else {
15254         gameMode = PlayFromGameFile;
15255         DisplayError(_("Already at end of game"), 0);
15256       }
15257     }
15258     ModeHighlight();
15259 }
15260
15261 void
15262 IcsClientEvent ()
15263 {
15264     if (!appData.icsActive) return;
15265     switch (gameMode) {
15266       case IcsPlayingWhite:
15267       case IcsPlayingBlack:
15268       case IcsObserving:
15269       case IcsIdle:
15270       case BeginningOfGame:
15271       case IcsExamining:
15272         return;
15273
15274       case EditGame:
15275         break;
15276
15277       case EditPosition:
15278         EditPositionDone(TRUE);
15279         break;
15280
15281       case AnalyzeMode:
15282       case AnalyzeFile:
15283         ExitAnalyzeMode();
15284         break;
15285
15286       default:
15287         EditGameEvent();
15288         break;
15289     }
15290
15291     gameMode = IcsIdle;
15292     ModeHighlight();
15293     return;
15294 }
15295
15296 void
15297 EditGameEvent ()
15298 {
15299     int i;
15300
15301     switch (gameMode) {
15302       case Training:
15303         SetTrainingModeOff();
15304         break;
15305       case MachinePlaysWhite:
15306       case MachinePlaysBlack:
15307       case BeginningOfGame:
15308         SendToProgram("force\n", &first);
15309         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15310             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15311                 char buf[MSG_SIZ];
15312                 abortEngineThink = TRUE;
15313                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15314                 SendToProgram(buf, &first);
15315                 DisplayMessage("Aborting engine think", "");
15316                 FreezeUI();
15317             }
15318         }
15319         SetUserThinkingEnables();
15320         break;
15321       case PlayFromGameFile:
15322         (void) StopLoadGameTimer();
15323         if (gameFileFP != NULL) {
15324             gameFileFP = NULL;
15325         }
15326         break;
15327       case EditPosition:
15328         EditPositionDone(TRUE);
15329         break;
15330       case AnalyzeMode:
15331       case AnalyzeFile:
15332         ExitAnalyzeMode();
15333         SendToProgram("force\n", &first);
15334         break;
15335       case TwoMachinesPlay:
15336         GameEnds(EndOfFile, NULL, GE_PLAYER);
15337         ResurrectChessProgram();
15338         SetUserThinkingEnables();
15339         break;
15340       case EndOfGame:
15341         ResurrectChessProgram();
15342         break;
15343       case IcsPlayingBlack:
15344       case IcsPlayingWhite:
15345         DisplayError(_("Warning: You are still playing a game"), 0);
15346         break;
15347       case IcsObserving:
15348         DisplayError(_("Warning: You are still observing a game"), 0);
15349         break;
15350       case IcsExamining:
15351         DisplayError(_("Warning: You are still examining a game"), 0);
15352         break;
15353       case IcsIdle:
15354         break;
15355       case EditGame:
15356       default:
15357         return;
15358     }
15359
15360     pausing = FALSE;
15361     StopClocks();
15362     first.offeredDraw = second.offeredDraw = 0;
15363
15364     if (gameMode == PlayFromGameFile) {
15365         whiteTimeRemaining = timeRemaining[0][currentMove];
15366         blackTimeRemaining = timeRemaining[1][currentMove];
15367         DisplayTitle("");
15368     }
15369
15370     if (gameMode == MachinePlaysWhite ||
15371         gameMode == MachinePlaysBlack ||
15372         gameMode == TwoMachinesPlay ||
15373         gameMode == EndOfGame) {
15374         i = forwardMostMove;
15375         while (i > currentMove) {
15376             SendToProgram("undo\n", &first);
15377             i--;
15378         }
15379         if(!adjustedClock) {
15380         whiteTimeRemaining = timeRemaining[0][currentMove];
15381         blackTimeRemaining = timeRemaining[1][currentMove];
15382         DisplayBothClocks();
15383         }
15384         if (whiteFlag || blackFlag) {
15385             whiteFlag = blackFlag = 0;
15386         }
15387         DisplayTitle("");
15388     }
15389
15390     gameMode = EditGame;
15391     ModeHighlight();
15392     SetGameInfo();
15393 }
15394
15395 void
15396 EditPositionEvent ()
15397 {
15398     int i;
15399     if (gameMode == EditPosition) {
15400         EditGameEvent();
15401         return;
15402     }
15403
15404     EditGameEvent();
15405     if (gameMode != EditGame) return;
15406
15407     gameMode = EditPosition;
15408     ModeHighlight();
15409     SetGameInfo();
15410     CopyBoard(rightsBoard, nullBoard);
15411     if (currentMove > 0)
15412       CopyBoard(boards[0], boards[currentMove]);
15413     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15414       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15415
15416     blackPlaysFirst = !WhiteOnMove(currentMove);
15417     ResetClocks();
15418     currentMove = forwardMostMove = backwardMostMove = 0;
15419     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15420     DisplayMove(-1);
15421     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15422 }
15423
15424 void
15425 ExitAnalyzeMode ()
15426 {
15427     /* [DM] icsEngineAnalyze - possible call from other functions */
15428     if (appData.icsEngineAnalyze) {
15429         appData.icsEngineAnalyze = FALSE;
15430
15431         DisplayMessage("",_("Close ICS engine analyze..."));
15432     }
15433     if (first.analysisSupport && first.analyzing) {
15434       SendToBoth("exit\n");
15435       first.analyzing = second.analyzing = FALSE;
15436     }
15437     thinkOutput[0] = NULLCHAR;
15438 }
15439
15440 void
15441 EditPositionDone (Boolean fakeRights)
15442 {
15443     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15444
15445     startedFromSetupPosition = TRUE;
15446     InitChessProgram(&first, FALSE);
15447     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15448       int r, f;
15449       boards[0][EP_STATUS] = EP_NONE;
15450       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15451       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15452         if(rightsBoard[r][f]) {
15453           ChessSquare p = boards[0][r][f];
15454           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15455           else if(p == king) boards[0][CASTLING][2] = f;
15456           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15457           else rightsBoard[r][f] = 2; // mark for second pass
15458         }
15459       }
15460       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15461         if(rightsBoard[r][f] == 2) {
15462           ChessSquare p = boards[0][r][f];
15463           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15464           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15465         }
15466       }
15467     }
15468     SendToProgram("force\n", &first);
15469     if (blackPlaysFirst) {
15470         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15471         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15472         currentMove = forwardMostMove = backwardMostMove = 1;
15473         CopyBoard(boards[1], boards[0]);
15474     } else {
15475         currentMove = forwardMostMove = backwardMostMove = 0;
15476     }
15477     SendBoard(&first, forwardMostMove);
15478     if (appData.debugMode) {
15479         fprintf(debugFP, "EditPosDone\n");
15480     }
15481     DisplayTitle("");
15482     DisplayMessage("", "");
15483     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15484     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15485     gameMode = EditGame;
15486     ModeHighlight();
15487     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15488     ClearHighlights(); /* [AS] */
15489 }
15490
15491 /* Pause for `ms' milliseconds */
15492 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15493 void
15494 TimeDelay (long ms)
15495 {
15496     TimeMark m1, m2;
15497
15498     GetTimeMark(&m1);
15499     do {
15500         GetTimeMark(&m2);
15501     } while (SubtractTimeMarks(&m2, &m1) < ms);
15502 }
15503
15504 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15505 void
15506 SendMultiLineToICS (char *buf)
15507 {
15508     char temp[MSG_SIZ+1], *p;
15509     int len;
15510
15511     len = strlen(buf);
15512     if (len > MSG_SIZ)
15513       len = MSG_SIZ;
15514
15515     strncpy(temp, buf, len);
15516     temp[len] = 0;
15517
15518     p = temp;
15519     while (*p) {
15520         if (*p == '\n' || *p == '\r')
15521           *p = ' ';
15522         ++p;
15523     }
15524
15525     strcat(temp, "\n");
15526     SendToICS(temp);
15527     SendToPlayer(temp, strlen(temp));
15528 }
15529
15530 void
15531 SetWhiteToPlayEvent ()
15532 {
15533     if (gameMode == EditPosition) {
15534         blackPlaysFirst = FALSE;
15535         DisplayBothClocks();    /* works because currentMove is 0 */
15536     } else if (gameMode == IcsExamining) {
15537         SendToICS(ics_prefix);
15538         SendToICS("tomove white\n");
15539     }
15540 }
15541
15542 void
15543 SetBlackToPlayEvent ()
15544 {
15545     if (gameMode == EditPosition) {
15546         blackPlaysFirst = TRUE;
15547         currentMove = 1;        /* kludge */
15548         DisplayBothClocks();
15549         currentMove = 0;
15550     } else if (gameMode == IcsExamining) {
15551         SendToICS(ics_prefix);
15552         SendToICS("tomove black\n");
15553     }
15554 }
15555
15556 void
15557 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15558 {
15559     char buf[MSG_SIZ];
15560     ChessSquare piece = boards[0][y][x];
15561     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15562     static int lastVariant;
15563     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15564
15565     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15566
15567     switch (selection) {
15568       case ClearBoard:
15569         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15570         MarkTargetSquares(1);
15571         CopyBoard(currentBoard, boards[0]);
15572         CopyBoard(menuBoard, initialPosition);
15573         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15574             SendToICS(ics_prefix);
15575             SendToICS("bsetup clear\n");
15576         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15577             SendToICS(ics_prefix);
15578             SendToICS("clearboard\n");
15579         } else {
15580             int nonEmpty = 0;
15581             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15582                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15583                 for (y = 0; y < BOARD_HEIGHT; y++) {
15584                     if (gameMode == IcsExamining) {
15585                         if (boards[currentMove][y][x] != EmptySquare) {
15586                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15587                                     AAA + x, ONE + y);
15588                             SendToICS(buf);
15589                         }
15590                     } else if(boards[0][y][x] != DarkSquare) {
15591                         if(boards[0][y][x] != p) nonEmpty++;
15592                         boards[0][y][x] = p;
15593                     }
15594                 }
15595             }
15596             CopyBoard(rightsBoard, nullBoard);
15597             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15598                 int r, i;
15599                 for(r = 0; r < BOARD_HEIGHT; r++) {
15600                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15601                     ChessSquare p = menuBoard[r][x];
15602                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15603                   }
15604                 }
15605                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15606                 DisplayMessage("Clicking clock again restores position", "");
15607                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15608                 if(!nonEmpty) { // asked to clear an empty board
15609                     CopyBoard(boards[0], menuBoard);
15610                 } else
15611                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15612                     CopyBoard(boards[0], initialPosition);
15613                 } else
15614                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15615                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15616                     CopyBoard(boards[0], erasedBoard);
15617                 } else
15618                     CopyBoard(erasedBoard, currentBoard);
15619
15620                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15621                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15622             }
15623         }
15624         if (gameMode == EditPosition) {
15625             DrawPosition(FALSE, boards[0]);
15626         }
15627         break;
15628
15629       case WhitePlay:
15630         SetWhiteToPlayEvent();
15631         break;
15632
15633       case BlackPlay:
15634         SetBlackToPlayEvent();
15635         break;
15636
15637       case EmptySquare:
15638         if (gameMode == IcsExamining) {
15639             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15640             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15641             SendToICS(buf);
15642         } else {
15643             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15644                 if(x == BOARD_LEFT-2) {
15645                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15646                     boards[0][y][1] = 0;
15647                 } else
15648                 if(x == BOARD_RGHT+1) {
15649                     if(y >= gameInfo.holdingsSize) break;
15650                     boards[0][y][BOARD_WIDTH-2] = 0;
15651                 } else break;
15652             }
15653             boards[0][y][x] = EmptySquare;
15654             DrawPosition(FALSE, boards[0]);
15655         }
15656         break;
15657
15658       case PromotePiece:
15659         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15660            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15661             selection = (ChessSquare) (PROMOTED(piece));
15662         } else if(piece == EmptySquare) selection = WhiteSilver;
15663         else selection = (ChessSquare)((int)piece - 1);
15664         goto defaultlabel;
15665
15666       case DemotePiece:
15667         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15668            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15669             selection = (ChessSquare) (DEMOTED(piece));
15670         } else if(piece == EmptySquare) selection = BlackSilver;
15671         else selection = (ChessSquare)((int)piece + 1);
15672         goto defaultlabel;
15673
15674       case WhiteQueen:
15675       case BlackQueen:
15676         if(gameInfo.variant == VariantShatranj ||
15677            gameInfo.variant == VariantXiangqi  ||
15678            gameInfo.variant == VariantCourier  ||
15679            gameInfo.variant == VariantASEAN    ||
15680            gameInfo.variant == VariantMakruk     )
15681             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15682         goto defaultlabel;
15683
15684       case WhiteRook:
15685         baseRank = 0;
15686       case BlackRook:
15687         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15688         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15689         goto defaultlabel;
15690
15691       case WhiteKing:
15692         baseRank = 0;
15693       case BlackKing:
15694         if(gameInfo.variant == VariantXiangqi)
15695             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15696         if(gameInfo.variant == VariantKnightmate)
15697             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15698         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15699       default:
15700         defaultlabel:
15701         if (gameMode == IcsExamining) {
15702             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15703             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15704                      PieceToChar(selection), AAA + x, ONE + y);
15705             SendToICS(buf);
15706         } else {
15707             rightsBoard[y][x] = hasRights;
15708             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15709                 int n;
15710                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15711                     n = PieceToNumber(selection - BlackPawn);
15712                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15713                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15714                     boards[0][BOARD_HEIGHT-1-n][1]++;
15715                 } else
15716                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15717                     n = PieceToNumber(selection);
15718                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15719                     boards[0][n][BOARD_WIDTH-1] = selection;
15720                     boards[0][n][BOARD_WIDTH-2]++;
15721                 }
15722             } else
15723             boards[0][y][x] = selection;
15724             DrawPosition(TRUE, boards[0]);
15725             ClearHighlights();
15726             fromX = fromY = -1;
15727         }
15728         break;
15729     }
15730 }
15731
15732
15733 void
15734 DropMenuEvent (ChessSquare selection, int x, int y)
15735 {
15736     ChessMove moveType;
15737
15738     switch (gameMode) {
15739       case IcsPlayingWhite:
15740       case MachinePlaysBlack:
15741         if (!WhiteOnMove(currentMove)) {
15742             DisplayMoveError(_("It is Black's turn"));
15743             return;
15744         }
15745         moveType = WhiteDrop;
15746         break;
15747       case IcsPlayingBlack:
15748       case MachinePlaysWhite:
15749         if (WhiteOnMove(currentMove)) {
15750             DisplayMoveError(_("It is White's turn"));
15751             return;
15752         }
15753         moveType = BlackDrop;
15754         break;
15755       case EditGame:
15756         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15757         break;
15758       default:
15759         return;
15760     }
15761
15762     if (moveType == BlackDrop && selection < BlackPawn) {
15763       selection = (ChessSquare) ((int) selection
15764                                  + (int) BlackPawn - (int) WhitePawn);
15765     }
15766     if (boards[currentMove][y][x] != EmptySquare) {
15767         DisplayMoveError(_("That square is occupied"));
15768         return;
15769     }
15770
15771     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15772 }
15773
15774 void
15775 AcceptEvent ()
15776 {
15777     /* Accept a pending offer of any kind from opponent */
15778
15779     if (appData.icsActive) {
15780         SendToICS(ics_prefix);
15781         SendToICS("accept\n");
15782     } else if (cmailMsgLoaded) {
15783         if (currentMove == cmailOldMove &&
15784             commentList[cmailOldMove] != NULL &&
15785             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15786                    "Black offers a draw" : "White offers a draw")) {
15787             TruncateGame();
15788             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15789             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15790         } else {
15791             DisplayError(_("There is no pending offer on this move"), 0);
15792             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15793         }
15794     } else {
15795         /* Not used for offers from chess program */
15796     }
15797 }
15798
15799 void
15800 DeclineEvent ()
15801 {
15802     /* Decline a pending offer of any kind from opponent */
15803
15804     if (appData.icsActive) {
15805         SendToICS(ics_prefix);
15806         SendToICS("decline\n");
15807     } else if (cmailMsgLoaded) {
15808         if (currentMove == cmailOldMove &&
15809             commentList[cmailOldMove] != NULL &&
15810             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15811                    "Black offers a draw" : "White offers a draw")) {
15812 #ifdef NOTDEF
15813             AppendComment(cmailOldMove, "Draw declined", TRUE);
15814             DisplayComment(cmailOldMove - 1, "Draw declined");
15815 #endif /*NOTDEF*/
15816         } else {
15817             DisplayError(_("There is no pending offer on this move"), 0);
15818         }
15819     } else {
15820         /* Not used for offers from chess program */
15821     }
15822 }
15823
15824 void
15825 RematchEvent ()
15826 {
15827     /* Issue ICS rematch command */
15828     if (appData.icsActive) {
15829         SendToICS(ics_prefix);
15830         SendToICS("rematch\n");
15831     }
15832 }
15833
15834 void
15835 CallFlagEvent ()
15836 {
15837     /* Call your opponent's flag (claim a win on time) */
15838     if (appData.icsActive) {
15839         SendToICS(ics_prefix);
15840         SendToICS("flag\n");
15841     } else {
15842         switch (gameMode) {
15843           default:
15844             return;
15845           case MachinePlaysWhite:
15846             if (whiteFlag) {
15847                 if (blackFlag)
15848                   GameEnds(GameIsDrawn, "Both players ran out of time",
15849                            GE_PLAYER);
15850                 else
15851                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15852             } else {
15853                 DisplayError(_("Your opponent is not out of time"), 0);
15854             }
15855             break;
15856           case MachinePlaysBlack:
15857             if (blackFlag) {
15858                 if (whiteFlag)
15859                   GameEnds(GameIsDrawn, "Both players ran out of time",
15860                            GE_PLAYER);
15861                 else
15862                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15863             } else {
15864                 DisplayError(_("Your opponent is not out of time"), 0);
15865             }
15866             break;
15867         }
15868     }
15869 }
15870
15871 void
15872 ClockClick (int which)
15873 {       // [HGM] code moved to back-end from winboard.c
15874         if(which) { // black clock
15875           if (gameMode == EditPosition || gameMode == IcsExamining) {
15876             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15877             SetBlackToPlayEvent();
15878           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15879                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15880           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15881           } else if (shiftKey) {
15882             AdjustClock(which, -1);
15883           } else if (gameMode == IcsPlayingWhite ||
15884                      gameMode == MachinePlaysBlack) {
15885             CallFlagEvent();
15886           }
15887         } else { // white clock
15888           if (gameMode == EditPosition || gameMode == IcsExamining) {
15889             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15890             SetWhiteToPlayEvent();
15891           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15892                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15893           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15894           } else if (shiftKey) {
15895             AdjustClock(which, -1);
15896           } else if (gameMode == IcsPlayingBlack ||
15897                    gameMode == MachinePlaysWhite) {
15898             CallFlagEvent();
15899           }
15900         }
15901 }
15902
15903 void
15904 DrawEvent ()
15905 {
15906     /* Offer draw or accept pending draw offer from opponent */
15907
15908     if (appData.icsActive) {
15909         /* Note: tournament rules require draw offers to be
15910            made after you make your move but before you punch
15911            your clock.  Currently ICS doesn't let you do that;
15912            instead, you immediately punch your clock after making
15913            a move, but you can offer a draw at any time. */
15914
15915         SendToICS(ics_prefix);
15916         SendToICS("draw\n");
15917         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15918     } else if (cmailMsgLoaded) {
15919         if (currentMove == cmailOldMove &&
15920             commentList[cmailOldMove] != NULL &&
15921             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15922                    "Black offers a draw" : "White offers a draw")) {
15923             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15924             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15925         } else if (currentMove == cmailOldMove + 1) {
15926             char *offer = WhiteOnMove(cmailOldMove) ?
15927               "White offers a draw" : "Black offers a draw";
15928             AppendComment(currentMove, offer, TRUE);
15929             DisplayComment(currentMove - 1, offer);
15930             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15931         } else {
15932             DisplayError(_("You must make your move before offering a draw"), 0);
15933             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15934         }
15935     } else if (first.offeredDraw) {
15936         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15937     } else {
15938         if (first.sendDrawOffers) {
15939             SendToProgram("draw\n", &first);
15940             userOfferedDraw = TRUE;
15941         }
15942     }
15943 }
15944
15945 void
15946 AdjournEvent ()
15947 {
15948     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15949
15950     if (appData.icsActive) {
15951         SendToICS(ics_prefix);
15952         SendToICS("adjourn\n");
15953     } else {
15954         /* Currently GNU Chess doesn't offer or accept Adjourns */
15955     }
15956 }
15957
15958
15959 void
15960 AbortEvent ()
15961 {
15962     /* Offer Abort or accept pending Abort offer from opponent */
15963
15964     if (appData.icsActive) {
15965         SendToICS(ics_prefix);
15966         SendToICS("abort\n");
15967     } else {
15968         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15969     }
15970 }
15971
15972 void
15973 ResignEvent ()
15974 {
15975     /* Resign.  You can do this even if it's not your turn. */
15976
15977     if (appData.icsActive) {
15978         SendToICS(ics_prefix);
15979         SendToICS("resign\n");
15980     } else {
15981         switch (gameMode) {
15982           case MachinePlaysWhite:
15983             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15984             break;
15985           case MachinePlaysBlack:
15986             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15987             break;
15988           case EditGame:
15989             if (cmailMsgLoaded) {
15990                 TruncateGame();
15991                 if (WhiteOnMove(cmailOldMove)) {
15992                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15993                 } else {
15994                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15995                 }
15996                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15997             }
15998             break;
15999           default:
16000             break;
16001         }
16002     }
16003 }
16004
16005
16006 void
16007 StopObservingEvent ()
16008 {
16009     /* Stop observing current games */
16010     SendToICS(ics_prefix);
16011     SendToICS("unobserve\n");
16012 }
16013
16014 void
16015 StopExaminingEvent ()
16016 {
16017     /* Stop observing current game */
16018     SendToICS(ics_prefix);
16019     SendToICS("unexamine\n");
16020 }
16021
16022 void
16023 ForwardInner (int target)
16024 {
16025     int limit; int oldSeekGraphUp = seekGraphUp;
16026
16027     if (appData.debugMode)
16028         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16029                 target, currentMove, forwardMostMove);
16030
16031     if (gameMode == EditPosition)
16032       return;
16033
16034     seekGraphUp = FALSE;
16035     MarkTargetSquares(1);
16036     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16037
16038     if (gameMode == PlayFromGameFile && !pausing)
16039       PauseEvent();
16040
16041     if (gameMode == IcsExamining && pausing)
16042       limit = pauseExamForwardMostMove;
16043     else
16044       limit = forwardMostMove;
16045
16046     if (target > limit) target = limit;
16047
16048     if (target > 0 && moveList[target - 1][0]) {
16049         int fromX, fromY, toX, toY;
16050         toX = moveList[target - 1][2] - AAA;
16051         toY = moveList[target - 1][3] - ONE;
16052         if (moveList[target - 1][1] == '@') {
16053             if (appData.highlightLastMove) {
16054                 SetHighlights(-1, -1, toX, toY);
16055             }
16056         } else {
16057             fromX = moveList[target - 1][0] - AAA;
16058             fromY = moveList[target - 1][1] - ONE;
16059             if (target == currentMove + 1) {
16060                 if(moveList[target - 1][4] == ';') { // multi-leg
16061                     killX = moveList[target - 1][5] - AAA;
16062                     killY = moveList[target - 1][6] - ONE;
16063                 }
16064                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16065                 killX = killY = -1;
16066             }
16067             if (appData.highlightLastMove) {
16068                 SetHighlights(fromX, fromY, toX, toY);
16069             }
16070         }
16071     }
16072     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16073         gameMode == Training || gameMode == PlayFromGameFile ||
16074         gameMode == AnalyzeFile) {
16075         while (currentMove < target) {
16076             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16077             SendMoveToProgram(currentMove++, &first);
16078         }
16079     } else {
16080         currentMove = target;
16081     }
16082
16083     if (gameMode == EditGame || gameMode == EndOfGame) {
16084         whiteTimeRemaining = timeRemaining[0][currentMove];
16085         blackTimeRemaining = timeRemaining[1][currentMove];
16086     }
16087     DisplayBothClocks();
16088     DisplayMove(currentMove - 1);
16089     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16090     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16091     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16092         DisplayComment(currentMove - 1, commentList[currentMove]);
16093     }
16094     ClearMap(); // [HGM] exclude: invalidate map
16095 }
16096
16097
16098 void
16099 ForwardEvent ()
16100 {
16101     if (gameMode == IcsExamining && !pausing) {
16102         SendToICS(ics_prefix);
16103         SendToICS("forward\n");
16104     } else {
16105         ForwardInner(currentMove + 1);
16106     }
16107 }
16108
16109 void
16110 ToEndEvent ()
16111 {
16112     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16113         /* to optimze, we temporarily turn off analysis mode while we feed
16114          * the remaining moves to the engine. Otherwise we get analysis output
16115          * after each move.
16116          */
16117         if (first.analysisSupport) {
16118           SendToProgram("exit\nforce\n", &first);
16119           first.analyzing = FALSE;
16120         }
16121     }
16122
16123     if (gameMode == IcsExamining && !pausing) {
16124         SendToICS(ics_prefix);
16125         SendToICS("forward 999999\n");
16126     } else {
16127         ForwardInner(forwardMostMove);
16128     }
16129
16130     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16131         /* we have fed all the moves, so reactivate analysis mode */
16132         SendToProgram("analyze\n", &first);
16133         first.analyzing = TRUE;
16134         /*first.maybeThinking = TRUE;*/
16135         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16136     }
16137 }
16138
16139 void
16140 BackwardInner (int target)
16141 {
16142     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16143
16144     if (appData.debugMode)
16145         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16146                 target, currentMove, forwardMostMove);
16147
16148     if (gameMode == EditPosition) return;
16149     seekGraphUp = FALSE;
16150     MarkTargetSquares(1);
16151     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16152     if (currentMove <= backwardMostMove) {
16153         ClearHighlights();
16154         DrawPosition(full_redraw, boards[currentMove]);
16155         return;
16156     }
16157     if (gameMode == PlayFromGameFile && !pausing)
16158       PauseEvent();
16159
16160     if (moveList[target][0]) {
16161         int fromX, fromY, toX, toY;
16162         toX = moveList[target][2] - AAA;
16163         toY = moveList[target][3] - ONE;
16164         if (moveList[target][1] == '@') {
16165             if (appData.highlightLastMove) {
16166                 SetHighlights(-1, -1, toX, toY);
16167             }
16168         } else {
16169             fromX = moveList[target][0] - AAA;
16170             fromY = moveList[target][1] - ONE;
16171             if (target == currentMove - 1) {
16172                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16173             }
16174             if (appData.highlightLastMove) {
16175                 SetHighlights(fromX, fromY, toX, toY);
16176             }
16177         }
16178     }
16179     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16180         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16181         while (currentMove > target) {
16182             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16183                 // null move cannot be undone. Reload program with move history before it.
16184                 int i;
16185                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16186                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16187                 }
16188                 SendBoard(&first, i);
16189               if(second.analyzing) SendBoard(&second, i);
16190                 for(currentMove=i; currentMove<target; currentMove++) {
16191                     SendMoveToProgram(currentMove, &first);
16192                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16193                 }
16194                 break;
16195             }
16196             SendToBoth("undo\n");
16197             currentMove--;
16198         }
16199     } else {
16200         currentMove = target;
16201     }
16202
16203     if (gameMode == EditGame || gameMode == EndOfGame) {
16204         whiteTimeRemaining = timeRemaining[0][currentMove];
16205         blackTimeRemaining = timeRemaining[1][currentMove];
16206     }
16207     DisplayBothClocks();
16208     DisplayMove(currentMove - 1);
16209     DrawPosition(full_redraw, boards[currentMove]);
16210     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16211     // [HGM] PV info: routine tests if comment empty
16212     DisplayComment(currentMove - 1, commentList[currentMove]);
16213     ClearMap(); // [HGM] exclude: invalidate map
16214 }
16215
16216 void
16217 BackwardEvent ()
16218 {
16219     if (gameMode == IcsExamining && !pausing) {
16220         SendToICS(ics_prefix);
16221         SendToICS("backward\n");
16222     } else {
16223         BackwardInner(currentMove - 1);
16224     }
16225 }
16226
16227 void
16228 ToStartEvent ()
16229 {
16230     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16231         /* to optimize, we temporarily turn off analysis mode while we undo
16232          * all the moves. Otherwise we get analysis output after each undo.
16233          */
16234         if (first.analysisSupport) {
16235           SendToProgram("exit\nforce\n", &first);
16236           first.analyzing = FALSE;
16237         }
16238     }
16239
16240     if (gameMode == IcsExamining && !pausing) {
16241         SendToICS(ics_prefix);
16242         SendToICS("backward 999999\n");
16243     } else {
16244         BackwardInner(backwardMostMove);
16245     }
16246
16247     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16248         /* we have fed all the moves, so reactivate analysis mode */
16249         SendToProgram("analyze\n", &first);
16250         first.analyzing = TRUE;
16251         /*first.maybeThinking = TRUE;*/
16252         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16253     }
16254 }
16255
16256 void
16257 ToNrEvent (int to)
16258 {
16259   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16260   if (to >= forwardMostMove) to = forwardMostMove;
16261   if (to <= backwardMostMove) to = backwardMostMove;
16262   if (to < currentMove) {
16263     BackwardInner(to);
16264   } else {
16265     ForwardInner(to);
16266   }
16267 }
16268
16269 void
16270 RevertEvent (Boolean annotate)
16271 {
16272     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16273         return;
16274     }
16275     if (gameMode != IcsExamining) {
16276         DisplayError(_("You are not examining a game"), 0);
16277         return;
16278     }
16279     if (pausing) {
16280         DisplayError(_("You can't revert while pausing"), 0);
16281         return;
16282     }
16283     SendToICS(ics_prefix);
16284     SendToICS("revert\n");
16285 }
16286
16287 void
16288 RetractMoveEvent ()
16289 {
16290     switch (gameMode) {
16291       case MachinePlaysWhite:
16292       case MachinePlaysBlack:
16293         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16294             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16295             return;
16296         }
16297         if (forwardMostMove < 2) return;
16298         currentMove = forwardMostMove = forwardMostMove - 2;
16299         whiteTimeRemaining = timeRemaining[0][currentMove];
16300         blackTimeRemaining = timeRemaining[1][currentMove];
16301         DisplayBothClocks();
16302         DisplayMove(currentMove - 1);
16303         ClearHighlights();/*!! could figure this out*/
16304         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16305         SendToProgram("remove\n", &first);
16306         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16307         break;
16308
16309       case BeginningOfGame:
16310       default:
16311         break;
16312
16313       case IcsPlayingWhite:
16314       case IcsPlayingBlack:
16315         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16316             SendToICS(ics_prefix);
16317             SendToICS("takeback 2\n");
16318         } else {
16319             SendToICS(ics_prefix);
16320             SendToICS("takeback 1\n");
16321         }
16322         break;
16323     }
16324 }
16325
16326 void
16327 MoveNowEvent ()
16328 {
16329     ChessProgramState *cps;
16330
16331     switch (gameMode) {
16332       case MachinePlaysWhite:
16333         if (!WhiteOnMove(forwardMostMove)) {
16334             DisplayError(_("It is your turn"), 0);
16335             return;
16336         }
16337         cps = &first;
16338         break;
16339       case MachinePlaysBlack:
16340         if (WhiteOnMove(forwardMostMove)) {
16341             DisplayError(_("It is your turn"), 0);
16342             return;
16343         }
16344         cps = &first;
16345         break;
16346       case TwoMachinesPlay:
16347         if (WhiteOnMove(forwardMostMove) ==
16348             (first.twoMachinesColor[0] == 'w')) {
16349             cps = &first;
16350         } else {
16351             cps = &second;
16352         }
16353         break;
16354       case BeginningOfGame:
16355       default:
16356         return;
16357     }
16358     SendToProgram("?\n", cps);
16359 }
16360
16361 void
16362 TruncateGameEvent ()
16363 {
16364     EditGameEvent();
16365     if (gameMode != EditGame) return;
16366     TruncateGame();
16367 }
16368
16369 void
16370 TruncateGame ()
16371 {
16372     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16373     if (forwardMostMove > currentMove) {
16374         if (gameInfo.resultDetails != NULL) {
16375             free(gameInfo.resultDetails);
16376             gameInfo.resultDetails = NULL;
16377             gameInfo.result = GameUnfinished;
16378         }
16379         forwardMostMove = currentMove;
16380         HistorySet(parseList, backwardMostMove, forwardMostMove,
16381                    currentMove-1);
16382     }
16383 }
16384
16385 void
16386 HintEvent ()
16387 {
16388     if (appData.noChessProgram) return;
16389     switch (gameMode) {
16390       case MachinePlaysWhite:
16391         if (WhiteOnMove(forwardMostMove)) {
16392             DisplayError(_("Wait until your turn."), 0);
16393             return;
16394         }
16395         break;
16396       case BeginningOfGame:
16397       case MachinePlaysBlack:
16398         if (!WhiteOnMove(forwardMostMove)) {
16399             DisplayError(_("Wait until your turn."), 0);
16400             return;
16401         }
16402         break;
16403       default:
16404         DisplayError(_("No hint available"), 0);
16405         return;
16406     }
16407     SendToProgram("hint\n", &first);
16408     hintRequested = TRUE;
16409 }
16410
16411 int
16412 SaveSelected (FILE *g, int dummy, char *dummy2)
16413 {
16414     ListGame * lg = (ListGame *) gameList.head;
16415     int nItem, cnt=0;
16416     FILE *f;
16417
16418     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16419         DisplayError(_("Game list not loaded or empty"), 0);
16420         return 0;
16421     }
16422
16423     creatingBook = TRUE; // suppresses stuff during load game
16424
16425     /* Get list size */
16426     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16427         if(lg->position >= 0) { // selected?
16428             LoadGame(f, nItem, "", TRUE);
16429             SaveGamePGN2(g); // leaves g open
16430             cnt++; DoEvents();
16431         }
16432         lg = (ListGame *) lg->node.succ;
16433     }
16434
16435     fclose(g);
16436     creatingBook = FALSE;
16437
16438     return cnt;
16439 }
16440
16441 void
16442 CreateBookEvent ()
16443 {
16444     ListGame * lg = (ListGame *) gameList.head;
16445     FILE *f, *g;
16446     int nItem;
16447     static int secondTime = FALSE;
16448
16449     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16450         DisplayError(_("Game list not loaded or empty"), 0);
16451         return;
16452     }
16453
16454     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16455         fclose(g);
16456         secondTime++;
16457         DisplayNote(_("Book file exists! Try again for overwrite."));
16458         return;
16459     }
16460
16461     creatingBook = TRUE;
16462     secondTime = FALSE;
16463
16464     /* Get list size */
16465     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16466         if(lg->position >= 0) {
16467             LoadGame(f, nItem, "", TRUE);
16468             AddGameToBook(TRUE);
16469             DoEvents();
16470         }
16471         lg = (ListGame *) lg->node.succ;
16472     }
16473
16474     creatingBook = FALSE;
16475     FlushBook();
16476 }
16477
16478 void
16479 BookEvent ()
16480 {
16481     if (appData.noChessProgram) return;
16482     switch (gameMode) {
16483       case MachinePlaysWhite:
16484         if (WhiteOnMove(forwardMostMove)) {
16485             DisplayError(_("Wait until your turn."), 0);
16486             return;
16487         }
16488         break;
16489       case BeginningOfGame:
16490       case MachinePlaysBlack:
16491         if (!WhiteOnMove(forwardMostMove)) {
16492             DisplayError(_("Wait until your turn."), 0);
16493             return;
16494         }
16495         break;
16496       case EditPosition:
16497         EditPositionDone(TRUE);
16498         break;
16499       case TwoMachinesPlay:
16500         return;
16501       default:
16502         break;
16503     }
16504     SendToProgram("bk\n", &first);
16505     bookOutput[0] = NULLCHAR;
16506     bookRequested = TRUE;
16507 }
16508
16509 void
16510 AboutGameEvent ()
16511 {
16512     char *tags = PGNTags(&gameInfo);
16513     TagsPopUp(tags, CmailMsg());
16514     free(tags);
16515 }
16516
16517 /* end button procedures */
16518
16519 void
16520 PrintPosition (FILE *fp, int move)
16521 {
16522     int i, j;
16523
16524     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16525         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16526             char c = PieceToChar(boards[move][i][j]);
16527             fputc(c == '?' ? '.' : c, fp);
16528             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16529         }
16530     }
16531     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16532       fprintf(fp, "white to play\n");
16533     else
16534       fprintf(fp, "black to play\n");
16535 }
16536
16537 void
16538 PrintOpponents (FILE *fp)
16539 {
16540     if (gameInfo.white != NULL) {
16541         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16542     } else {
16543         fprintf(fp, "\n");
16544     }
16545 }
16546
16547 /* Find last component of program's own name, using some heuristics */
16548 void
16549 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16550 {
16551     char *p, *q, c;
16552     int local = (strcmp(host, "localhost") == 0);
16553     while (!local && (p = strchr(prog, ';')) != NULL) {
16554         p++;
16555         while (*p == ' ') p++;
16556         prog = p;
16557     }
16558     if (*prog == '"' || *prog == '\'') {
16559         q = strchr(prog + 1, *prog);
16560     } else {
16561         q = strchr(prog, ' ');
16562     }
16563     if (q == NULL) q = prog + strlen(prog);
16564     p = q;
16565     while (p >= prog && *p != '/' && *p != '\\') p--;
16566     p++;
16567     if(p == prog && *p == '"') p++;
16568     c = *q; *q = 0;
16569     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16570     memcpy(buf, p, q - p);
16571     buf[q - p] = NULLCHAR;
16572     if (!local) {
16573         strcat(buf, "@");
16574         strcat(buf, host);
16575     }
16576 }
16577
16578 char *
16579 TimeControlTagValue ()
16580 {
16581     char buf[MSG_SIZ];
16582     if (!appData.clockMode) {
16583       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16584     } else if (movesPerSession > 0) {
16585       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16586     } else if (timeIncrement == 0) {
16587       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16588     } else {
16589       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16590     }
16591     return StrSave(buf);
16592 }
16593
16594 void
16595 SetGameInfo ()
16596 {
16597     /* This routine is used only for certain modes */
16598     VariantClass v = gameInfo.variant;
16599     ChessMove r = GameUnfinished;
16600     char *p = NULL;
16601
16602     if(keepInfo) return;
16603
16604     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16605         r = gameInfo.result;
16606         p = gameInfo.resultDetails;
16607         gameInfo.resultDetails = NULL;
16608     }
16609     ClearGameInfo(&gameInfo);
16610     gameInfo.variant = v;
16611
16612     switch (gameMode) {
16613       case MachinePlaysWhite:
16614         gameInfo.event = StrSave( appData.pgnEventHeader );
16615         gameInfo.site = StrSave(HostName());
16616         gameInfo.date = PGNDate();
16617         gameInfo.round = StrSave("-");
16618         gameInfo.white = StrSave(first.tidy);
16619         gameInfo.black = StrSave(UserName());
16620         gameInfo.timeControl = TimeControlTagValue();
16621         break;
16622
16623       case MachinePlaysBlack:
16624         gameInfo.event = StrSave( appData.pgnEventHeader );
16625         gameInfo.site = StrSave(HostName());
16626         gameInfo.date = PGNDate();
16627         gameInfo.round = StrSave("-");
16628         gameInfo.white = StrSave(UserName());
16629         gameInfo.black = StrSave(first.tidy);
16630         gameInfo.timeControl = TimeControlTagValue();
16631         break;
16632
16633       case TwoMachinesPlay:
16634         gameInfo.event = StrSave( appData.pgnEventHeader );
16635         gameInfo.site = StrSave(HostName());
16636         gameInfo.date = PGNDate();
16637         if (roundNr > 0) {
16638             char buf[MSG_SIZ];
16639             snprintf(buf, MSG_SIZ, "%d", roundNr);
16640             gameInfo.round = StrSave(buf);
16641         } else {
16642             gameInfo.round = StrSave("-");
16643         }
16644         if (first.twoMachinesColor[0] == 'w') {
16645             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16646             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16647         } else {
16648             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16649             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16650         }
16651         gameInfo.timeControl = TimeControlTagValue();
16652         break;
16653
16654       case EditGame:
16655         gameInfo.event = StrSave("Edited game");
16656         gameInfo.site = StrSave(HostName());
16657         gameInfo.date = PGNDate();
16658         gameInfo.round = StrSave("-");
16659         gameInfo.white = StrSave("-");
16660         gameInfo.black = StrSave("-");
16661         gameInfo.result = r;
16662         gameInfo.resultDetails = p;
16663         break;
16664
16665       case EditPosition:
16666         gameInfo.event = StrSave("Edited position");
16667         gameInfo.site = StrSave(HostName());
16668         gameInfo.date = PGNDate();
16669         gameInfo.round = StrSave("-");
16670         gameInfo.white = StrSave("-");
16671         gameInfo.black = StrSave("-");
16672         break;
16673
16674       case IcsPlayingWhite:
16675       case IcsPlayingBlack:
16676       case IcsObserving:
16677       case IcsExamining:
16678         break;
16679
16680       case PlayFromGameFile:
16681         gameInfo.event = StrSave("Game from non-PGN file");
16682         gameInfo.site = StrSave(HostName());
16683         gameInfo.date = PGNDate();
16684         gameInfo.round = StrSave("-");
16685         gameInfo.white = StrSave("?");
16686         gameInfo.black = StrSave("?");
16687         break;
16688
16689       default:
16690         break;
16691     }
16692 }
16693
16694 void
16695 ReplaceComment (int index, char *text)
16696 {
16697     int len;
16698     char *p;
16699     float score;
16700
16701     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16702        pvInfoList[index-1].depth == len &&
16703        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16704        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16705     while (*text == '\n') text++;
16706     len = strlen(text);
16707     while (len > 0 && text[len - 1] == '\n') len--;
16708
16709     if (commentList[index] != NULL)
16710       free(commentList[index]);
16711
16712     if (len == 0) {
16713         commentList[index] = NULL;
16714         return;
16715     }
16716   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16717       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16718       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16719     commentList[index] = (char *) malloc(len + 2);
16720     strncpy(commentList[index], text, len);
16721     commentList[index][len] = '\n';
16722     commentList[index][len + 1] = NULLCHAR;
16723   } else {
16724     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16725     char *p;
16726     commentList[index] = (char *) malloc(len + 7);
16727     safeStrCpy(commentList[index], "{\n", 3);
16728     safeStrCpy(commentList[index]+2, text, len+1);
16729     commentList[index][len+2] = NULLCHAR;
16730     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16731     strcat(commentList[index], "\n}\n");
16732   }
16733 }
16734
16735 void
16736 CrushCRs (char *text)
16737 {
16738   char *p = text;
16739   char *q = text;
16740   char ch;
16741
16742   do {
16743     ch = *p++;
16744     if (ch == '\r') continue;
16745     *q++ = ch;
16746   } while (ch != '\0');
16747 }
16748
16749 void
16750 AppendComment (int index, char *text, Boolean addBraces)
16751 /* addBraces  tells if we should add {} */
16752 {
16753     int oldlen, len;
16754     char *old;
16755
16756 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16757     if(addBraces == 3) addBraces = 0; else // force appending literally
16758     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16759
16760     CrushCRs(text);
16761     while (*text == '\n') text++;
16762     len = strlen(text);
16763     while (len > 0 && text[len - 1] == '\n') len--;
16764     text[len] = NULLCHAR;
16765
16766     if (len == 0) return;
16767
16768     if (commentList[index] != NULL) {
16769       Boolean addClosingBrace = addBraces;
16770         old = commentList[index];
16771         oldlen = strlen(old);
16772         while(commentList[index][oldlen-1] ==  '\n')
16773           commentList[index][--oldlen] = NULLCHAR;
16774         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16775         safeStrCpy(commentList[index], old, oldlen + len + 6);
16776         free(old);
16777         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16778         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16779           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16780           while (*text == '\n') { text++; len--; }
16781           commentList[index][--oldlen] = NULLCHAR;
16782       }
16783         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16784         else          strcat(commentList[index], "\n");
16785         strcat(commentList[index], text);
16786         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16787         else          strcat(commentList[index], "\n");
16788     } else {
16789         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16790         if(addBraces)
16791           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16792         else commentList[index][0] = NULLCHAR;
16793         strcat(commentList[index], text);
16794         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16795         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16796     }
16797 }
16798
16799 static char *
16800 FindStr (char * text, char * sub_text)
16801 {
16802     char * result = strstr( text, sub_text );
16803
16804     if( result != NULL ) {
16805         result += strlen( sub_text );
16806     }
16807
16808     return result;
16809 }
16810
16811 /* [AS] Try to extract PV info from PGN comment */
16812 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16813 char *
16814 GetInfoFromComment (int index, char * text)
16815 {
16816     char * sep = text, *p;
16817
16818     if( text != NULL && index > 0 ) {
16819         int score = 0;
16820         int depth = 0;
16821         int time = -1, sec = 0, deci;
16822         char * s_eval = FindStr( text, "[%eval " );
16823         char * s_emt = FindStr( text, "[%emt " );
16824 #if 0
16825         if( s_eval != NULL || s_emt != NULL ) {
16826 #else
16827         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16828 #endif
16829             /* New style */
16830             char delim;
16831
16832             if( s_eval != NULL ) {
16833                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16834                     return text;
16835                 }
16836
16837                 if( delim != ']' ) {
16838                     return text;
16839                 }
16840             }
16841
16842             if( s_emt != NULL ) {
16843             }
16844                 return text;
16845         }
16846         else {
16847             /* We expect something like: [+|-]nnn.nn/dd */
16848             int score_lo = 0;
16849
16850             if(*text != '{') return text; // [HGM] braces: must be normal comment
16851
16852             sep = strchr( text, '/' );
16853             if( sep == NULL || sep < (text+4) ) {
16854                 return text;
16855             }
16856
16857             p = text;
16858             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16859             if(p[1] == '(') { // comment starts with PV
16860                p = strchr(p, ')'); // locate end of PV
16861                if(p == NULL || sep < p+5) return text;
16862                // at this point we have something like "{(.*) +0.23/6 ..."
16863                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16864                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16865                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16866             }
16867             time = -1; sec = -1; deci = -1;
16868             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16869                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16870                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16871                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16872                 return text;
16873             }
16874
16875             if( score_lo < 0 || score_lo >= 100 ) {
16876                 return text;
16877             }
16878
16879             if(sec >= 0) time = 600*time + 10*sec; else
16880             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16881
16882             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16883
16884             /* [HGM] PV time: now locate end of PV info */
16885             while( *++sep >= '0' && *sep <= '9'); // strip depth
16886             if(time >= 0)
16887             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16888             if(sec >= 0)
16889             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16890             if(deci >= 0)
16891             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16892             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16893         }
16894
16895         if( depth <= 0 ) {
16896             return text;
16897         }
16898
16899         if( time < 0 ) {
16900             time = -1;
16901         }
16902
16903         pvInfoList[index-1].depth = depth;
16904         pvInfoList[index-1].score = score;
16905         pvInfoList[index-1].time  = 10*time; // centi-sec
16906         if(*sep == '}') *sep = 0; else *--sep = '{';
16907         if(p != text) {
16908             while(*p++ = *sep++)
16909                                 ;
16910             sep = text;
16911         } // squeeze out space between PV and comment, and return both
16912     }
16913     return sep;
16914 }
16915
16916 void
16917 SendToProgram (char *message, ChessProgramState *cps)
16918 {
16919     int count, outCount, error;
16920     char buf[MSG_SIZ];
16921
16922     if (cps->pr == NoProc) return;
16923     Attention(cps);
16924
16925     if (appData.debugMode) {
16926         TimeMark now;
16927         GetTimeMark(&now);
16928         fprintf(debugFP, "%ld >%-6s: %s",
16929                 SubtractTimeMarks(&now, &programStartTime),
16930                 cps->which, message);
16931         if(serverFP)
16932             fprintf(serverFP, "%ld >%-6s: %s",
16933                 SubtractTimeMarks(&now, &programStartTime),
16934                 cps->which, message), fflush(serverFP);
16935     }
16936
16937     count = strlen(message);
16938     outCount = OutputToProcess(cps->pr, message, count, &error);
16939     if (outCount < count && !exiting
16940                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16941       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16942       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16943         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16944             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16945                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16946                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16947                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16948             } else {
16949                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16950                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16951                 gameInfo.result = res;
16952             }
16953             gameInfo.resultDetails = StrSave(buf);
16954         }
16955         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16956         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16957     }
16958 }
16959
16960 void
16961 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16962 {
16963     char *end_str;
16964     char buf[MSG_SIZ];
16965     ChessProgramState *cps = (ChessProgramState *)closure;
16966
16967     if (isr != cps->isr) return; /* Killed intentionally */
16968     if (count <= 0) {
16969         if (count == 0) {
16970             RemoveInputSource(cps->isr);
16971             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16972                     _(cps->which), cps->program);
16973             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16974             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16975                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16976                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16977                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16978                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16979                 } else {
16980                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16981                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16982                     gameInfo.result = res;
16983                 }
16984                 gameInfo.resultDetails = StrSave(buf);
16985             }
16986             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16987             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16988         } else {
16989             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16990                     _(cps->which), cps->program);
16991             RemoveInputSource(cps->isr);
16992
16993             /* [AS] Program is misbehaving badly... kill it */
16994             if( count == -2 ) {
16995                 DestroyChildProcess( cps->pr, 9 );
16996                 cps->pr = NoProc;
16997             }
16998
16999             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17000         }
17001         return;
17002     }
17003
17004     if ((end_str = strchr(message, '\r')) != NULL)
17005       *end_str = NULLCHAR;
17006     if ((end_str = strchr(message, '\n')) != NULL)
17007       *end_str = NULLCHAR;
17008
17009     if (appData.debugMode) {
17010         TimeMark now; int print = 1;
17011         char *quote = ""; char c; int i;
17012
17013         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17014                 char start = message[0];
17015                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17016                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17017                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
17018                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17019                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17020                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
17021                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17022                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
17023                    sscanf(message, "hint: %c", &c)!=1 &&
17024                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
17025                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17026                     print = (appData.engineComments >= 2);
17027                 }
17028                 message[0] = start; // restore original message
17029         }
17030         if(print) {
17031                 GetTimeMark(&now);
17032                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17033                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17034                         quote,
17035                         message);
17036                 if(serverFP)
17037                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
17038                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17039                         quote,
17040                         message), fflush(serverFP);
17041         }
17042     }
17043
17044     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17045     if (appData.icsEngineAnalyze) {
17046         if (strstr(message, "whisper") != NULL ||
17047              strstr(message, "kibitz") != NULL ||
17048             strstr(message, "tellics") != NULL) return;
17049     }
17050
17051     HandleMachineMove(message, cps);
17052 }
17053
17054
17055 void
17056 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17057 {
17058     char buf[MSG_SIZ];
17059     int seconds;
17060
17061     if( timeControl_2 > 0 ) {
17062         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17063             tc = timeControl_2;
17064         }
17065     }
17066     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17067     inc /= cps->timeOdds;
17068     st  /= cps->timeOdds;
17069
17070     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17071
17072     if (st > 0) {
17073       /* Set exact time per move, normally using st command */
17074       if (cps->stKludge) {
17075         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17076         seconds = st % 60;
17077         if (seconds == 0) {
17078           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17079         } else {
17080           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17081         }
17082       } else {
17083         snprintf(buf, MSG_SIZ, "st %d\n", st);
17084       }
17085     } else {
17086       /* Set conventional or incremental time control, using level command */
17087       if (seconds == 0) {
17088         /* Note old gnuchess bug -- minutes:seconds used to not work.
17089            Fixed in later versions, but still avoid :seconds
17090            when seconds is 0. */
17091         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17092       } else {
17093         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17094                  seconds, inc/1000.);
17095       }
17096     }
17097     SendToProgram(buf, cps);
17098
17099     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17100     /* Orthogonally, limit search to given depth */
17101     if (sd > 0) {
17102       if (cps->sdKludge) {
17103         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17104       } else {
17105         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17106       }
17107       SendToProgram(buf, cps);
17108     }
17109
17110     if(cps->nps >= 0) { /* [HGM] nps */
17111         if(cps->supportsNPS == FALSE)
17112           cps->nps = -1; // don't use if engine explicitly says not supported!
17113         else {
17114           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17115           SendToProgram(buf, cps);
17116         }
17117     }
17118 }
17119
17120 ChessProgramState *
17121 WhitePlayer ()
17122 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17123 {
17124     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17125        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17126         return &second;
17127     return &first;
17128 }
17129
17130 void
17131 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17132 {
17133     char message[MSG_SIZ];
17134     long time, otime;
17135
17136     /* Note: this routine must be called when the clocks are stopped
17137        or when they have *just* been set or switched; otherwise
17138        it will be off by the time since the current tick started.
17139     */
17140     if (machineWhite) {
17141         time = whiteTimeRemaining / 10;
17142         otime = blackTimeRemaining / 10;
17143     } else {
17144         time = blackTimeRemaining / 10;
17145         otime = whiteTimeRemaining / 10;
17146     }
17147     /* [HGM] translate opponent's time by time-odds factor */
17148     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17149
17150     if (time <= 0) time = 1;
17151     if (otime <= 0) otime = 1;
17152
17153     snprintf(message, MSG_SIZ, "time %ld\n", time);
17154     SendToProgram(message, cps);
17155
17156     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17157     SendToProgram(message, cps);
17158 }
17159
17160 char *
17161 EngineDefinedVariant (ChessProgramState *cps, int n)
17162 {   // return name of n-th unknown variant that engine supports
17163     static char buf[MSG_SIZ];
17164     char *p, *s = cps->variants;
17165     if(!s) return NULL;
17166     do { // parse string from variants feature
17167       VariantClass v;
17168         p = strchr(s, ',');
17169         if(p) *p = NULLCHAR;
17170       v = StringToVariant(s);
17171       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17172         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17173             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17174                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17175                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17176                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17177             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17178         }
17179         if(p) *p++ = ',';
17180         if(n < 0) return buf;
17181     } while(s = p);
17182     return NULL;
17183 }
17184
17185 int
17186 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17187 {
17188   char buf[MSG_SIZ];
17189   int len = strlen(name);
17190   int val;
17191
17192   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17193     (*p) += len + 1;
17194     sscanf(*p, "%d", &val);
17195     *loc = (val != 0);
17196     while (**p && **p != ' ')
17197       (*p)++;
17198     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17199     SendToProgram(buf, cps);
17200     return TRUE;
17201   }
17202   return FALSE;
17203 }
17204
17205 int
17206 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17207 {
17208   char buf[MSG_SIZ];
17209   int len = strlen(name);
17210   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17211     (*p) += len + 1;
17212     sscanf(*p, "%d", loc);
17213     while (**p && **p != ' ') (*p)++;
17214     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17215     SendToProgram(buf, cps);
17216     return TRUE;
17217   }
17218   return FALSE;
17219 }
17220
17221 int
17222 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17223 {
17224   char buf[MSG_SIZ];
17225   int len = strlen(name);
17226   if (strncmp((*p), name, len) == 0
17227       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17228     (*p) += len + 2;
17229     len = strlen(*p) + 1; if(len < MSG_SIZ && !strcmp(name, "option")) len = MSG_SIZ; // make sure string options have enough space to change their value
17230     FREE(*loc); *loc = malloc(len);
17231     strncpy(*loc, *p, len);
17232     sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17233     while (**p && **p != '\"') (*p)++;
17234     if (**p == '\"') (*p)++;
17235     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17236     SendToProgram(buf, cps);
17237     return TRUE;
17238   }
17239   return FALSE;
17240 }
17241
17242 int
17243 ParseOption (Option *opt, ChessProgramState *cps)
17244 // [HGM] options: process the string that defines an engine option, and determine
17245 // name, type, default value, and allowed value range
17246 {
17247         char *p, *q, buf[MSG_SIZ];
17248         int n, min = (-1)<<31, max = 1<<31, def;
17249
17250         opt->target = &opt->value;   // OK for spin/slider and checkbox
17251         if(p = strstr(opt->name, " -spin ")) {
17252             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17253             if(max < min) max = min; // enforce consistency
17254             if(def < min) def = min;
17255             if(def > max) def = max;
17256             opt->value = def;
17257             opt->min = min;
17258             opt->max = max;
17259             opt->type = Spin;
17260         } else if((p = strstr(opt->name, " -slider "))) {
17261             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17262             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17263             if(max < min) max = min; // enforce consistency
17264             if(def < min) def = min;
17265             if(def > max) def = max;
17266             opt->value = def;
17267             opt->min = min;
17268             opt->max = max;
17269             opt->type = Spin; // Slider;
17270         } else if((p = strstr(opt->name, " -string "))) {
17271             opt->textValue = p+9;
17272             opt->type = TextBox;
17273             opt->target = &opt->textValue;
17274         } else if((p = strstr(opt->name, " -file "))) {
17275             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17276             opt->target = opt->textValue = p+7;
17277             opt->type = FileName; // FileName;
17278             opt->target = &opt->textValue;
17279         } else if((p = strstr(opt->name, " -path "))) {
17280             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17281             opt->target = opt->textValue = p+7;
17282             opt->type = PathName; // PathName;
17283             opt->target = &opt->textValue;
17284         } else if(p = strstr(opt->name, " -check ")) {
17285             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17286             opt->value = (def != 0);
17287             opt->type = CheckBox;
17288         } else if(p = strstr(opt->name, " -combo ")) {
17289             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17290             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17291             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17292             opt->value = n = 0;
17293             while(q = StrStr(q, " /// ")) {
17294                 n++; *q = 0;    // count choices, and null-terminate each of them
17295                 q += 5;
17296                 if(*q == '*') { // remember default, which is marked with * prefix
17297                     q++;
17298                     opt->value = n;
17299                 }
17300                 cps->comboList[cps->comboCnt++] = q;
17301             }
17302             cps->comboList[cps->comboCnt++] = NULL;
17303             opt->max = n + 1;
17304             opt->type = ComboBox;
17305         } else if(p = strstr(opt->name, " -button")) {
17306             opt->type = Button;
17307         } else if(p = strstr(opt->name, " -save")) {
17308             opt->type = SaveButton;
17309         } else return FALSE;
17310         *p = 0; // terminate option name
17311         // now look if the command-line options define a setting for this engine option.
17312         if(cps->optionSettings && cps->optionSettings[0])
17313             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17314         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17315           snprintf(buf, MSG_SIZ, "option %s", p);
17316                 if(p = strstr(buf, ",")) *p = 0;
17317                 if(q = strchr(buf, '=')) switch(opt->type) {
17318                     case ComboBox:
17319                         for(n=0; n<opt->max; n++)
17320                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17321                         break;
17322                     case TextBox:
17323                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17324                         break;
17325                     case Spin:
17326                     case CheckBox:
17327                         opt->value = atoi(q+1);
17328                     default:
17329                         break;
17330                 }
17331                 strcat(buf, "\n");
17332                 SendToProgram(buf, cps);
17333         }
17334         *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17335         if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17336         return TRUE;
17337 }
17338
17339 void
17340 FeatureDone (ChessProgramState *cps, int val)
17341 {
17342   DelayedEventCallback cb = GetDelayedEvent();
17343   if ((cb == InitBackEnd3 && cps == &first) ||
17344       (cb == SettingsMenuIfReady && cps == &second) ||
17345       (cb == LoadEngine) ||
17346       (cb == TwoMachinesEventIfReady)) {
17347     CancelDelayedEvent();
17348     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17349   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17350   cps->initDone = val;
17351   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17352 }
17353
17354 /* Parse feature command from engine */
17355 void
17356 ParseFeatures (char *args, ChessProgramState *cps)
17357 {
17358   char *p = args;
17359   char *q = NULL;
17360   int val;
17361   char buf[MSG_SIZ];
17362
17363   for (;;) {
17364     while (*p == ' ') p++;
17365     if (*p == NULLCHAR) return;
17366
17367     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17368     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17369     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17370     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17371     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17372     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17373     if (BoolFeature(&p, "reuse", &val, cps)) {
17374       /* Engine can disable reuse, but can't enable it if user said no */
17375       if (!val) cps->reuse = FALSE;
17376       continue;
17377     }
17378     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17379     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17380       if (gameMode == TwoMachinesPlay) {
17381         DisplayTwoMachinesTitle();
17382       } else {
17383         DisplayTitle("");
17384       }
17385       continue;
17386     }
17387     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17388     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17389     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17390     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17391     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17392     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17393     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17394     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17395     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17396     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17397     if (IntFeature(&p, "done", &val, cps)) {
17398       FeatureDone(cps, val);
17399       continue;
17400     }
17401     /* Added by Tord: */
17402     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17403     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17404     /* End of additions by Tord */
17405
17406     /* [HGM] added features: */
17407     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17408     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17409     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17410     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17411     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17412     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17413     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17414     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17415         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17416         if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17417         FREE(cps->option[cps->nrOptions].name);
17418         cps->option[cps->nrOptions].name = q; q = NULL;
17419         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17420           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17421             SendToProgram(buf, cps);
17422             continue;
17423         }
17424         if(cps->nrOptions >= MAX_OPTIONS) {
17425             cps->nrOptions--;
17426             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17427             DisplayError(buf, 0);
17428         }
17429         continue;
17430     }
17431     /* End of additions by HGM */
17432
17433     /* unknown feature: complain and skip */
17434     q = p;
17435     while (*q && *q != '=') q++;
17436     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17437     SendToProgram(buf, cps);
17438     p = q;
17439     if (*p == '=') {
17440       p++;
17441       if (*p == '\"') {
17442         p++;
17443         while (*p && *p != '\"') p++;
17444         if (*p == '\"') p++;
17445       } else {
17446         while (*p && *p != ' ') p++;
17447       }
17448     }
17449   }
17450
17451 }
17452
17453 void
17454 PeriodicUpdatesEvent (int newState)
17455 {
17456     if (newState == appData.periodicUpdates)
17457       return;
17458
17459     appData.periodicUpdates=newState;
17460
17461     /* Display type changes, so update it now */
17462 //    DisplayAnalysis();
17463
17464     /* Get the ball rolling again... */
17465     if (newState) {
17466         AnalysisPeriodicEvent(1);
17467         StartAnalysisClock();
17468     }
17469 }
17470
17471 void
17472 PonderNextMoveEvent (int newState)
17473 {
17474     if (newState == appData.ponderNextMove) return;
17475     if (gameMode == EditPosition) EditPositionDone(TRUE);
17476     if (newState) {
17477         SendToProgram("hard\n", &first);
17478         if (gameMode == TwoMachinesPlay) {
17479             SendToProgram("hard\n", &second);
17480         }
17481     } else {
17482         SendToProgram("easy\n", &first);
17483         thinkOutput[0] = NULLCHAR;
17484         if (gameMode == TwoMachinesPlay) {
17485             SendToProgram("easy\n", &second);
17486         }
17487     }
17488     appData.ponderNextMove = newState;
17489 }
17490
17491 void
17492 NewSettingEvent (int option, int *feature, char *command, int value)
17493 {
17494     char buf[MSG_SIZ];
17495
17496     if (gameMode == EditPosition) EditPositionDone(TRUE);
17497     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17498     if(feature == NULL || *feature) SendToProgram(buf, &first);
17499     if (gameMode == TwoMachinesPlay) {
17500         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17501     }
17502 }
17503
17504 void
17505 ShowThinkingEvent ()
17506 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17507 {
17508     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17509     int newState = appData.showThinking
17510         // [HGM] thinking: other features now need thinking output as well
17511         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17512
17513     if (oldState == newState) return;
17514     oldState = newState;
17515     if (gameMode == EditPosition) EditPositionDone(TRUE);
17516     if (oldState) {
17517         SendToProgram("post\n", &first);
17518         if (gameMode == TwoMachinesPlay) {
17519             SendToProgram("post\n", &second);
17520         }
17521     } else {
17522         SendToProgram("nopost\n", &first);
17523         thinkOutput[0] = NULLCHAR;
17524         if (gameMode == TwoMachinesPlay) {
17525             SendToProgram("nopost\n", &second);
17526         }
17527     }
17528 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17529 }
17530
17531 void
17532 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17533 {
17534   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17535   if (pr == NoProc) return;
17536   AskQuestion(title, question, replyPrefix, pr);
17537 }
17538
17539 void
17540 TypeInEvent (char firstChar)
17541 {
17542     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17543         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17544         gameMode == AnalyzeMode || gameMode == EditGame ||
17545         gameMode == EditPosition || gameMode == IcsExamining ||
17546         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17547         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17548                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17549                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17550         gameMode == Training) PopUpMoveDialog(firstChar);
17551 }
17552
17553 void
17554 TypeInDoneEvent (char *move)
17555 {
17556         Board board;
17557         int n, fromX, fromY, toX, toY;
17558         char promoChar;
17559         ChessMove moveType;
17560
17561         // [HGM] FENedit
17562         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17563                 EditPositionPasteFEN(move);
17564                 return;
17565         }
17566         // [HGM] movenum: allow move number to be typed in any mode
17567         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17568           ToNrEvent(2*n-1);
17569           return;
17570         }
17571         // undocumented kludge: allow command-line option to be typed in!
17572         // (potentially fatal, and does not implement the effect of the option.)
17573         // should only be used for options that are values on which future decisions will be made,
17574         // and definitely not on options that would be used during initialization.
17575         if(strstr(move, "!!! -") == move) {
17576             ParseArgsFromString(move+4);
17577             return;
17578         }
17579
17580       if (gameMode != EditGame && currentMove != forwardMostMove &&
17581         gameMode != Training) {
17582         DisplayMoveError(_("Displayed move is not current"));
17583       } else {
17584         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17585           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17586         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17587         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17588           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17589           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17590         } else {
17591           DisplayMoveError(_("Could not parse move"));
17592         }
17593       }
17594 }
17595
17596 void
17597 DisplayMove (int moveNumber)
17598 {
17599     char message[MSG_SIZ];
17600     char res[MSG_SIZ];
17601     char cpThinkOutput[MSG_SIZ];
17602
17603     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17604
17605     if (moveNumber == forwardMostMove - 1 ||
17606         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17607
17608         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17609
17610         if (strchr(cpThinkOutput, '\n')) {
17611             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17612         }
17613     } else {
17614         *cpThinkOutput = NULLCHAR;
17615     }
17616
17617     /* [AS] Hide thinking from human user */
17618     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17619         *cpThinkOutput = NULLCHAR;
17620         if( thinkOutput[0] != NULLCHAR ) {
17621             int i;
17622
17623             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17624                 cpThinkOutput[i] = '.';
17625             }
17626             cpThinkOutput[i] = NULLCHAR;
17627             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17628         }
17629     }
17630
17631     if (moveNumber == forwardMostMove - 1 &&
17632         gameInfo.resultDetails != NULL) {
17633         if (gameInfo.resultDetails[0] == NULLCHAR) {
17634           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17635         } else {
17636           snprintf(res, MSG_SIZ, " {%s} %s",
17637                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17638         }
17639     } else {
17640         res[0] = NULLCHAR;
17641     }
17642
17643     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17644         DisplayMessage(res, cpThinkOutput);
17645     } else {
17646       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17647                 WhiteOnMove(moveNumber) ? " " : ".. ",
17648                 parseList[moveNumber], res);
17649         DisplayMessage(message, cpThinkOutput);
17650     }
17651 }
17652
17653 void
17654 DisplayComment (int moveNumber, char *text)
17655 {
17656     char title[MSG_SIZ];
17657
17658     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17659       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17660     } else {
17661       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17662               WhiteOnMove(moveNumber) ? " " : ".. ",
17663               parseList[moveNumber]);
17664     }
17665     if (text != NULL && (appData.autoDisplayComment || commentUp))
17666         CommentPopUp(title, text);
17667 }
17668
17669 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17670  * might be busy thinking or pondering.  It can be omitted if your
17671  * gnuchess is configured to stop thinking immediately on any user
17672  * input.  However, that gnuchess feature depends on the FIONREAD
17673  * ioctl, which does not work properly on some flavors of Unix.
17674  */
17675 void
17676 Attention (ChessProgramState *cps)
17677 {
17678 #if ATTENTION
17679     if (!cps->useSigint) return;
17680     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17681     switch (gameMode) {
17682       case MachinePlaysWhite:
17683       case MachinePlaysBlack:
17684       case TwoMachinesPlay:
17685       case IcsPlayingWhite:
17686       case IcsPlayingBlack:
17687       case AnalyzeMode:
17688       case AnalyzeFile:
17689         /* Skip if we know it isn't thinking */
17690         if (!cps->maybeThinking) return;
17691         if (appData.debugMode)
17692           fprintf(debugFP, "Interrupting %s\n", cps->which);
17693         InterruptChildProcess(cps->pr);
17694         cps->maybeThinking = FALSE;
17695         break;
17696       default:
17697         break;
17698     }
17699 #endif /*ATTENTION*/
17700 }
17701
17702 int
17703 CheckFlags ()
17704 {
17705     if (whiteTimeRemaining <= 0) {
17706         if (!whiteFlag) {
17707             whiteFlag = TRUE;
17708             if (appData.icsActive) {
17709                 if (appData.autoCallFlag &&
17710                     gameMode == IcsPlayingBlack && !blackFlag) {
17711                   SendToICS(ics_prefix);
17712                   SendToICS("flag\n");
17713                 }
17714             } else {
17715                 if (blackFlag) {
17716                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17717                 } else {
17718                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17719                     if (appData.autoCallFlag) {
17720                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17721                         return TRUE;
17722                     }
17723                 }
17724             }
17725         }
17726     }
17727     if (blackTimeRemaining <= 0) {
17728         if (!blackFlag) {
17729             blackFlag = TRUE;
17730             if (appData.icsActive) {
17731                 if (appData.autoCallFlag &&
17732                     gameMode == IcsPlayingWhite && !whiteFlag) {
17733                   SendToICS(ics_prefix);
17734                   SendToICS("flag\n");
17735                 }
17736             } else {
17737                 if (whiteFlag) {
17738                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17739                 } else {
17740                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17741                     if (appData.autoCallFlag) {
17742                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17743                         return TRUE;
17744                     }
17745                 }
17746             }
17747         }
17748     }
17749     return FALSE;
17750 }
17751
17752 void
17753 CheckTimeControl ()
17754 {
17755     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17756         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17757
17758     /*
17759      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17760      */
17761     if ( !WhiteOnMove(forwardMostMove) ) {
17762         /* White made time control */
17763         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17764         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17765         /* [HGM] time odds: correct new time quota for time odds! */
17766                                             / WhitePlayer()->timeOdds;
17767         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17768     } else {
17769         lastBlack -= blackTimeRemaining;
17770         /* Black made time control */
17771         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17772                                             / WhitePlayer()->other->timeOdds;
17773         lastWhite = whiteTimeRemaining;
17774     }
17775 }
17776
17777 void
17778 DisplayBothClocks ()
17779 {
17780     int wom = gameMode == EditPosition ?
17781       !blackPlaysFirst : WhiteOnMove(currentMove);
17782     DisplayWhiteClock(whiteTimeRemaining, wom);
17783     DisplayBlackClock(blackTimeRemaining, !wom);
17784 }
17785
17786
17787 /* Timekeeping seems to be a portability nightmare.  I think everyone
17788    has ftime(), but I'm really not sure, so I'm including some ifdefs
17789    to use other calls if you don't.  Clocks will be less accurate if
17790    you have neither ftime nor gettimeofday.
17791 */
17792
17793 /* VS 2008 requires the #include outside of the function */
17794 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17795 #include <sys/timeb.h>
17796 #endif
17797
17798 /* Get the current time as a TimeMark */
17799 void
17800 GetTimeMark (TimeMark *tm)
17801 {
17802 #if HAVE_GETTIMEOFDAY
17803
17804     struct timeval timeVal;
17805     struct timezone timeZone;
17806
17807     gettimeofday(&timeVal, &timeZone);
17808     tm->sec = (long) timeVal.tv_sec;
17809     tm->ms = (int) (timeVal.tv_usec / 1000L);
17810
17811 #else /*!HAVE_GETTIMEOFDAY*/
17812 #if HAVE_FTIME
17813
17814 // include <sys/timeb.h> / moved to just above start of function
17815     struct timeb timeB;
17816
17817     ftime(&timeB);
17818     tm->sec = (long) timeB.time;
17819     tm->ms = (int) timeB.millitm;
17820
17821 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17822     tm->sec = (long) time(NULL);
17823     tm->ms = 0;
17824 #endif
17825 #endif
17826 }
17827
17828 /* Return the difference in milliseconds between two
17829    time marks.  We assume the difference will fit in a long!
17830 */
17831 long
17832 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17833 {
17834     return 1000L*(tm2->sec - tm1->sec) +
17835            (long) (tm2->ms - tm1->ms);
17836 }
17837
17838
17839 /*
17840  * Code to manage the game clocks.
17841  *
17842  * In tournament play, black starts the clock and then white makes a move.
17843  * We give the human user a slight advantage if he is playing white---the
17844  * clocks don't run until he makes his first move, so it takes zero time.
17845  * Also, we don't account for network lag, so we could get out of sync
17846  * with GNU Chess's clock -- but then, referees are always right.
17847  */
17848
17849 static TimeMark tickStartTM;
17850 static long intendedTickLength;
17851
17852 long
17853 NextTickLength (long timeRemaining)
17854 {
17855     long nominalTickLength, nextTickLength;
17856
17857     if (timeRemaining > 0L && timeRemaining <= 10000L)
17858       nominalTickLength = 100L;
17859     else
17860       nominalTickLength = 1000L;
17861     nextTickLength = timeRemaining % nominalTickLength;
17862     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17863
17864     return nextTickLength;
17865 }
17866
17867 /* Adjust clock one minute up or down */
17868 void
17869 AdjustClock (Boolean which, int dir)
17870 {
17871     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17872     if(which) blackTimeRemaining += 60000*dir;
17873     else      whiteTimeRemaining += 60000*dir;
17874     DisplayBothClocks();
17875     adjustedClock = TRUE;
17876 }
17877
17878 /* Stop clocks and reset to a fresh time control */
17879 void
17880 ResetClocks ()
17881 {
17882     (void) StopClockTimer();
17883     if (appData.icsActive) {
17884         whiteTimeRemaining = blackTimeRemaining = 0;
17885     } else if (searchTime) {
17886         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17887         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17888     } else { /* [HGM] correct new time quote for time odds */
17889         whiteTC = blackTC = fullTimeControlString;
17890         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17891         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17892     }
17893     if (whiteFlag || blackFlag) {
17894         DisplayTitle("");
17895         whiteFlag = blackFlag = FALSE;
17896     }
17897     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17898     DisplayBothClocks();
17899     adjustedClock = FALSE;
17900 }
17901
17902 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17903
17904 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17905
17906 /* Decrement running clock by amount of time that has passed */
17907 void
17908 DecrementClocks ()
17909 {
17910     long tRemaining;
17911     long lastTickLength, fudge;
17912     TimeMark now;
17913
17914     if (!appData.clockMode) return;
17915     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17916
17917     GetTimeMark(&now);
17918
17919     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17920
17921     /* Fudge if we woke up a little too soon */
17922     fudge = intendedTickLength - lastTickLength;
17923     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17924
17925     if (WhiteOnMove(forwardMostMove)) {
17926         if(whiteNPS >= 0) lastTickLength = 0;
17927          tRemaining = whiteTimeRemaining -= lastTickLength;
17928         if( tRemaining < 0 && !appData.icsActive) {
17929             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17930             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17931                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17932                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17933             }
17934         }
17935         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
17936         DisplayWhiteClock(whiteTimeRemaining - fudge,
17937                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17938         timeSuffix = 0;
17939     } else {
17940         if(blackNPS >= 0) lastTickLength = 0;
17941          tRemaining = blackTimeRemaining -= lastTickLength;
17942         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17943             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17944             if(suddenDeath) {
17945                 blackStartMove = forwardMostMove;
17946                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17947             }
17948         }
17949         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
17950         DisplayBlackClock(blackTimeRemaining - fudge,
17951                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17952         timeSuffix = 0;
17953     }
17954     if (CheckFlags()) return;
17955
17956     if(twoBoards) { // count down secondary board's clocks as well
17957         activePartnerTime -= lastTickLength;
17958         partnerUp = 1;
17959         if(activePartner == 'W')
17960             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17961         else
17962             DisplayBlackClock(activePartnerTime, TRUE);
17963         partnerUp = 0;
17964     }
17965
17966     tickStartTM = now;
17967     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
17968     StartClockTimer(intendedTickLength);
17969
17970     /* if the time remaining has fallen below the alarm threshold, sound the
17971      * alarm. if the alarm has sounded and (due to a takeback or time control
17972      * with increment) the time remaining has increased to a level above the
17973      * threshold, reset the alarm so it can sound again.
17974      */
17975
17976     if (appData.icsActive && appData.icsAlarm) {
17977
17978         /* make sure we are dealing with the user's clock */
17979         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17980                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17981            )) return;
17982
17983         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
17984             alarmSounded = FALSE;
17985         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
17986             PlayAlarmSound();
17987             alarmSounded = TRUE;
17988         }
17989     }
17990 }
17991
17992
17993 /* A player has just moved, so stop the previously running
17994    clock and (if in clock mode) start the other one.
17995    We redisplay both clocks in case we're in ICS mode, because
17996    ICS gives us an update to both clocks after every move.
17997    Note that this routine is called *after* forwardMostMove
17998    is updated, so the last fractional tick must be subtracted
17999    from the color that is *not* on move now.
18000 */
18001 void
18002 SwitchClocks (int newMoveNr)
18003 {
18004     long lastTickLength;
18005     TimeMark now;
18006     int flagged = FALSE;
18007
18008     GetTimeMark(&now);
18009
18010     if (StopClockTimer() && appData.clockMode) {
18011         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18012         if (!WhiteOnMove(forwardMostMove)) {
18013             if(blackNPS >= 0) lastTickLength = 0;
18014             blackTimeRemaining -= lastTickLength;
18015            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18016 //         if(pvInfoList[forwardMostMove].time == -1)
18017                  pvInfoList[forwardMostMove].time =               // use GUI time
18018                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18019         } else {
18020            if(whiteNPS >= 0) lastTickLength = 0;
18021            whiteTimeRemaining -= lastTickLength;
18022            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18023 //         if(pvInfoList[forwardMostMove].time == -1)
18024                  pvInfoList[forwardMostMove].time =
18025                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18026         }
18027         flagged = CheckFlags();
18028     }
18029     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18030     CheckTimeControl();
18031
18032     if (flagged || !appData.clockMode) return;
18033
18034     switch (gameMode) {
18035       case MachinePlaysBlack:
18036       case MachinePlaysWhite:
18037       case BeginningOfGame:
18038         if (pausing) return;
18039         break;
18040
18041       case EditGame:
18042       case PlayFromGameFile:
18043       case IcsExamining:
18044         return;
18045
18046       default:
18047         break;
18048     }
18049
18050     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18051         if(WhiteOnMove(forwardMostMove))
18052              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18053         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18054     }
18055
18056     tickStartTM = now;
18057     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18058       whiteTimeRemaining : blackTimeRemaining);
18059     StartClockTimer(intendedTickLength);
18060 }
18061
18062
18063 /* Stop both clocks */
18064 void
18065 StopClocks ()
18066 {
18067     long lastTickLength;
18068     TimeMark now;
18069
18070     if (!StopClockTimer()) return;
18071     if (!appData.clockMode) return;
18072
18073     GetTimeMark(&now);
18074
18075     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18076     if (WhiteOnMove(forwardMostMove)) {
18077         if(whiteNPS >= 0) lastTickLength = 0;
18078         whiteTimeRemaining -= lastTickLength;
18079         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18080     } else {
18081         if(blackNPS >= 0) lastTickLength = 0;
18082         blackTimeRemaining -= lastTickLength;
18083         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18084     }
18085     CheckFlags();
18086 }
18087
18088 /* Start clock of player on move.  Time may have been reset, so
18089    if clock is already running, stop and restart it. */
18090 void
18091 StartClocks ()
18092 {
18093     (void) StopClockTimer(); /* in case it was running already */
18094     DisplayBothClocks();
18095     if (CheckFlags()) return;
18096
18097     if (!appData.clockMode) return;
18098     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18099
18100     GetTimeMark(&tickStartTM);
18101     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18102       whiteTimeRemaining : blackTimeRemaining);
18103
18104    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18105     whiteNPS = blackNPS = -1;
18106     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18107        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18108         whiteNPS = first.nps;
18109     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18110        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18111         blackNPS = first.nps;
18112     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18113         whiteNPS = second.nps;
18114     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18115         blackNPS = second.nps;
18116     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18117
18118     StartClockTimer(intendedTickLength);
18119 }
18120
18121 char *
18122 TimeString (long ms)
18123 {
18124     long second, minute, hour, day;
18125     char *sign = "";
18126     static char buf[40], moveTime[8];
18127
18128     if (ms > 0 && ms <= 9900) {
18129       /* convert milliseconds to tenths, rounding up */
18130       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18131
18132       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18133       return buf;
18134     }
18135
18136     /* convert milliseconds to seconds, rounding up */
18137     /* use floating point to avoid strangeness of integer division
18138        with negative dividends on many machines */
18139     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18140
18141     if (second < 0) {
18142         sign = "-";
18143         second = -second;
18144     }
18145
18146     day = second / (60 * 60 * 24);
18147     second = second % (60 * 60 * 24);
18148     hour = second / (60 * 60);
18149     second = second % (60 * 60);
18150     minute = second / 60;
18151     second = second % 60;
18152
18153     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18154     else *moveTime = NULLCHAR;
18155
18156     if (day > 0)
18157       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18158               sign, day, hour, minute, second, moveTime);
18159     else if (hour > 0)
18160       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18161     else
18162       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18163
18164     return buf;
18165 }
18166
18167
18168 /*
18169  * This is necessary because some C libraries aren't ANSI C compliant yet.
18170  */
18171 char *
18172 StrStr (char *string, char *match)
18173 {
18174     int i, length;
18175
18176     length = strlen(match);
18177
18178     for (i = strlen(string) - length; i >= 0; i--, string++)
18179       if (!strncmp(match, string, length))
18180         return string;
18181
18182     return NULL;
18183 }
18184
18185 char *
18186 StrCaseStr (char *string, char *match)
18187 {
18188     int i, j, length;
18189
18190     length = strlen(match);
18191
18192     for (i = strlen(string) - length; i >= 0; i--, string++) {
18193         for (j = 0; j < length; j++) {
18194             if (ToLower(match[j]) != ToLower(string[j]))
18195               break;
18196         }
18197         if (j == length) return string;
18198     }
18199
18200     return NULL;
18201 }
18202
18203 #ifndef _amigados
18204 int
18205 StrCaseCmp (char *s1, char *s2)
18206 {
18207     char c1, c2;
18208
18209     for (;;) {
18210         c1 = ToLower(*s1++);
18211         c2 = ToLower(*s2++);
18212         if (c1 > c2) return 1;
18213         if (c1 < c2) return -1;
18214         if (c1 == NULLCHAR) return 0;
18215     }
18216 }
18217
18218
18219 int
18220 ToLower (int c)
18221 {
18222     return isupper(c) ? tolower(c) : c;
18223 }
18224
18225
18226 int
18227 ToUpper (int c)
18228 {
18229     return islower(c) ? toupper(c) : c;
18230 }
18231 #endif /* !_amigados    */
18232
18233 char *
18234 StrSave (char *s)
18235 {
18236   char *ret;
18237
18238   if ((ret = (char *) malloc(strlen(s) + 1)))
18239     {
18240       safeStrCpy(ret, s, strlen(s)+1);
18241     }
18242   return ret;
18243 }
18244
18245 char *
18246 StrSavePtr (char *s, char **savePtr)
18247 {
18248     if (*savePtr) {
18249         free(*savePtr);
18250     }
18251     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18252       safeStrCpy(*savePtr, s, strlen(s)+1);
18253     }
18254     return(*savePtr);
18255 }
18256
18257 char *
18258 PGNDate ()
18259 {
18260     time_t clock;
18261     struct tm *tm;
18262     char buf[MSG_SIZ];
18263
18264     clock = time((time_t *)NULL);
18265     tm = localtime(&clock);
18266     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18267             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18268     return StrSave(buf);
18269 }
18270
18271
18272 char *
18273 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18274 {
18275     int i, j, fromX, fromY, toX, toY;
18276     int whiteToPlay, haveRights = nrCastlingRights;
18277     char buf[MSG_SIZ];
18278     char *p, *q;
18279     int emptycount;
18280     ChessSquare piece;
18281
18282     whiteToPlay = (gameMode == EditPosition) ?
18283       !blackPlaysFirst : (move % 2 == 0);
18284     p = buf;
18285
18286     /* Piece placement data */
18287     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18288         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18289         emptycount = 0;
18290         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18291             if (boards[move][i][j] == EmptySquare) {
18292                 emptycount++;
18293             } else { ChessSquare piece = boards[move][i][j];
18294                 if (emptycount > 0) {
18295                     if(emptycount<10) /* [HGM] can be >= 10 */
18296                         *p++ = '0' + emptycount;
18297                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18298                     emptycount = 0;
18299                 }
18300                 if(PieceToChar(piece) == '+') {
18301                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18302                     *p++ = '+';
18303                     piece = (ChessSquare)(CHUDEMOTED(piece));
18304                 }
18305                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18306                 if(*p = PieceSuffix(piece)) p++;
18307                 if(p[-1] == '~') {
18308                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18309                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18310                     *p++ = '~';
18311                 }
18312             }
18313         }
18314         if (emptycount > 0) {
18315             if(emptycount<10) /* [HGM] can be >= 10 */
18316                 *p++ = '0' + emptycount;
18317             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18318             emptycount = 0;
18319         }
18320         *p++ = '/';
18321     }
18322     *(p - 1) = ' ';
18323
18324     /* [HGM] print Crazyhouse or Shogi holdings */
18325     if( gameInfo.holdingsWidth ) {
18326         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18327         q = p;
18328         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18329             piece = boards[move][i][BOARD_WIDTH-1];
18330             if( piece != EmptySquare )
18331               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18332                   *p++ = PieceToChar(piece);
18333         }
18334         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18335             piece = boards[move][BOARD_HEIGHT-i-1][0];
18336             if( piece != EmptySquare )
18337               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18338                   *p++ = PieceToChar(piece);
18339         }
18340
18341         if( q == p ) *p++ = '-';
18342         *p++ = ']';
18343         *p++ = ' ';
18344     }
18345
18346     /* Active color */
18347     *p++ = whiteToPlay ? 'w' : 'b';
18348     *p++ = ' ';
18349
18350   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18351     haveRights = 0; q = p;
18352     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18353       piece = boards[move][0][i];
18354       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18355         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18356       }
18357     }
18358     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18359       piece = boards[move][BOARD_HEIGHT-1][i];
18360       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18361         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18362       }
18363     }
18364     if(p == q) *p++ = '-';
18365     *p++ = ' ';
18366   }
18367
18368   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18369     while(*p++ = *q++)
18370                       ;
18371     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18372   } else {
18373   if(haveRights) {
18374      int handW=0, handB=0;
18375      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18376         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18377         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18378      }
18379      q = p;
18380      if(appData.fischerCastling) {
18381         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18382            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18383                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18384         } else {
18385        /* [HGM] write directly from rights */
18386            if(boards[move][CASTLING][2] != NoRights &&
18387               boards[move][CASTLING][0] != NoRights   )
18388                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18389            if(boards[move][CASTLING][2] != NoRights &&
18390               boards[move][CASTLING][1] != NoRights   )
18391                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18392         }
18393         if(handB) {
18394            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18395                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18396         } else {
18397            if(boards[move][CASTLING][5] != NoRights &&
18398               boards[move][CASTLING][3] != NoRights   )
18399                 *p++ = boards[move][CASTLING][3] + AAA;
18400            if(boards[move][CASTLING][5] != NoRights &&
18401               boards[move][CASTLING][4] != NoRights   )
18402                 *p++ = boards[move][CASTLING][4] + AAA;
18403         }
18404      } else {
18405
18406         /* [HGM] write true castling rights */
18407         if( nrCastlingRights == 6 ) {
18408             int q, k=0;
18409             if(boards[move][CASTLING][0] != NoRights &&
18410                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18411             q = (boards[move][CASTLING][1] != NoRights &&
18412                  boards[move][CASTLING][2] != NoRights  );
18413             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18414                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18415                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18416                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18417             }
18418             if(q) *p++ = 'Q';
18419             k = 0;
18420             if(boards[move][CASTLING][3] != NoRights &&
18421                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18422             q = (boards[move][CASTLING][4] != NoRights &&
18423                  boards[move][CASTLING][5] != NoRights  );
18424             if(handB) {
18425                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18426                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18427                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18428             }
18429             if(q) *p++ = 'q';
18430         }
18431      }
18432      if (q == p) *p++ = '-'; /* No castling rights */
18433      *p++ = ' ';
18434   }
18435
18436   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18437      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18438      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18439     /* En passant target square */
18440     if (move > backwardMostMove) {
18441         fromX = moveList[move - 1][0] - AAA;
18442         fromY = moveList[move - 1][1] - ONE;
18443         toX = moveList[move - 1][2] - AAA;
18444         toY = moveList[move - 1][3] - ONE;
18445         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18446             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18447             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18448             fromX == toX) {
18449             /* 2-square pawn move just happened */
18450             *p++ = toX + AAA;
18451             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18452         } else {
18453             *p++ = '-';
18454         }
18455     } else if(move == backwardMostMove) {
18456         // [HGM] perhaps we should always do it like this, and forget the above?
18457         if((signed char)boards[move][EP_STATUS] >= 0) {
18458             *p++ = boards[move][EP_STATUS] + AAA;
18459             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18460         } else {
18461             *p++ = '-';
18462         }
18463     } else {
18464         *p++ = '-';
18465     }
18466     *p++ = ' ';
18467   }
18468   }
18469
18470     i = boards[move][CHECK_COUNT];
18471     if(i) {
18472         sprintf(p, "%d+%d ", i&255, i>>8);
18473         while(*p) p++;
18474     }
18475
18476     if(moveCounts)
18477     {   int i = 0, j=move;
18478
18479         /* [HGM] find reversible plies */
18480         if (appData.debugMode) { int k;
18481             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18482             for(k=backwardMostMove; k<=forwardMostMove; k++)
18483                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18484
18485         }
18486
18487         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18488         if( j == backwardMostMove ) i += initialRulePlies;
18489         sprintf(p, "%d ", i);
18490         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18491
18492         /* Fullmove number */
18493         sprintf(p, "%d", (move / 2) + 1);
18494     } else *--p = NULLCHAR;
18495
18496     return StrSave(buf);
18497 }
18498
18499 Boolean
18500 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18501 {
18502     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18503     char *p, c;
18504     int emptycount, virgin[BOARD_FILES];
18505     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18506
18507     p = fen;
18508
18509     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18510
18511     /* Piece placement data */
18512     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18513         j = 0;
18514         for (;;) {
18515             if (*p == '/' || *p == ' ' || *p == '[' ) {
18516                 if(j > w) w = j;
18517                 emptycount = gameInfo.boardWidth - j;
18518                 while (emptycount--)
18519                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18520                 if (*p == '/') p++;
18521                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18522                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18523                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18524                     }
18525                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18526                 }
18527                 break;
18528 #if(BOARD_FILES >= 10)*0
18529             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18530                 p++; emptycount=10;
18531                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18532                 while (emptycount--)
18533                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18534 #endif
18535             } else if (*p == '*') {
18536                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18537             } else if (isdigit(*p)) {
18538                 emptycount = *p++ - '0';
18539                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18540                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18541                 while (emptycount--)
18542                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18543             } else if (*p == '<') {
18544                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18545                 else if (i != 0 || !shuffle) return FALSE;
18546                 p++;
18547             } else if (shuffle && *p == '>') {
18548                 p++; // for now ignore closing shuffle range, and assume rank-end
18549             } else if (*p == '?') {
18550                 if (j >= gameInfo.boardWidth) return FALSE;
18551                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18552                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18553             } else if (*p == '+' || isalpha(*p)) {
18554                 char *q, *s = SUFFIXES;
18555                 if (j >= gameInfo.boardWidth) return FALSE;
18556                 if(*p=='+') {
18557                     char c = *++p;
18558                     if(q = strchr(s, p[1])) p++;
18559                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18560                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18561                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18562                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18563                 } else {
18564                     char c = *p++;
18565                     if(q = strchr(s, *p)) p++;
18566                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18567                 }
18568
18569                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18570                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18571                     piece = (ChessSquare) (PROMOTED(piece));
18572                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18573                     p++;
18574                 }
18575                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18576                 if(piece == king) wKingRank = i;
18577                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18578             } else {
18579                 return FALSE;
18580             }
18581         }
18582     }
18583     while (*p == '/' || *p == ' ') p++;
18584
18585     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18586
18587     /* [HGM] by default clear Crazyhouse holdings, if present */
18588     if(gameInfo.holdingsWidth) {
18589        for(i=0; i<BOARD_HEIGHT; i++) {
18590            board[i][0]             = EmptySquare; /* black holdings */
18591            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18592            board[i][1]             = (ChessSquare) 0; /* black counts */
18593            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18594        }
18595     }
18596
18597     /* [HGM] look for Crazyhouse holdings here */
18598     while(*p==' ') p++;
18599     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18600         int swap=0, wcnt=0, bcnt=0;
18601         if(*p == '[') p++;
18602         if(*p == '<') swap++, p++;
18603         if(*p == '-' ) p++; /* empty holdings */ else {
18604             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18605             /* if we would allow FEN reading to set board size, we would   */
18606             /* have to add holdings and shift the board read so far here   */
18607             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18608                 p++;
18609                 if((int) piece >= (int) BlackPawn ) {
18610                     i = (int)piece - (int)BlackPawn;
18611                     i = PieceToNumber((ChessSquare)i);
18612                     if( i >= gameInfo.holdingsSize ) return FALSE;
18613                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18614                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18615                     bcnt++;
18616                 } else {
18617                     i = (int)piece - (int)WhitePawn;
18618                     i = PieceToNumber((ChessSquare)i);
18619                     if( i >= gameInfo.holdingsSize ) return FALSE;
18620                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18621                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18622                     wcnt++;
18623                 }
18624             }
18625             if(subst) { // substitute back-rank question marks by holdings pieces
18626                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18627                     int k, m, n = bcnt + 1;
18628                     if(board[0][j] == ClearBoard) {
18629                         if(!wcnt) return FALSE;
18630                         n = rand() % wcnt;
18631                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18632                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18633                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18634                             break;
18635                         }
18636                     }
18637                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18638                         if(!bcnt) return FALSE;
18639                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18640                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18641                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18642                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18643                             break;
18644                         }
18645                     }
18646                 }
18647                 subst = 0;
18648             }
18649         }
18650         if(*p == ']') p++;
18651     }
18652
18653     if(subst) return FALSE; // substitution requested, but no holdings
18654
18655     while(*p == ' ') p++;
18656
18657     /* Active color */
18658     c = *p++;
18659     if(appData.colorNickNames) {
18660       if( c == appData.colorNickNames[0] ) c = 'w'; else
18661       if( c == appData.colorNickNames[1] ) c = 'b';
18662     }
18663     switch (c) {
18664       case 'w':
18665         *blackPlaysFirst = FALSE;
18666         break;
18667       case 'b':
18668         *blackPlaysFirst = TRUE;
18669         break;
18670       default:
18671         return FALSE;
18672     }
18673
18674     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18675     /* return the extra info in global variiables             */
18676
18677     while(*p==' ') p++;
18678
18679     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18680         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18681         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18682     }
18683
18684     /* set defaults in case FEN is incomplete */
18685     board[EP_STATUS] = EP_UNKNOWN;
18686     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18687     for(i=0; i<nrCastlingRights; i++ ) {
18688         board[CASTLING][i] =
18689             appData.fischerCastling ? NoRights : initialRights[i];
18690     }   /* assume possible unless obviously impossible */
18691     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18692     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18693     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18694                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18695     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18696     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18697     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18698                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18699     FENrulePlies = 0;
18700
18701     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18702       char *q = p;
18703       int w=0, b=0;
18704       while(isalpha(*p)) {
18705         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18706         if(islower(*p)) b |= 1 << (*p++ - 'a');
18707       }
18708       if(*p == '-') p++;
18709       if(p != q) {
18710         board[TOUCHED_W] = ~w;
18711         board[TOUCHED_B] = ~b;
18712         while(*p == ' ') p++;
18713       }
18714     } else
18715
18716     if(nrCastlingRights) {
18717       int fischer = 0;
18718       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18719       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18720           /* castling indicator present, so default becomes no castlings */
18721           for(i=0; i<nrCastlingRights; i++ ) {
18722                  board[CASTLING][i] = NoRights;
18723           }
18724       }
18725       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18726              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18727              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18728              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18729         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18730
18731         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18732             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18733             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18734         }
18735         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18736             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18737         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18738                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18739         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18740                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18741         switch(c) {
18742           case'K':
18743               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18744               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18745               board[CASTLING][2] = whiteKingFile;
18746               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18747               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18748               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18749               break;
18750           case'Q':
18751               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18752               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18753               board[CASTLING][2] = whiteKingFile;
18754               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18755               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18756               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18757               break;
18758           case'k':
18759               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18760               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18761               board[CASTLING][5] = blackKingFile;
18762               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18763               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18764               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18765               break;
18766           case'q':
18767               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18768               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18769               board[CASTLING][5] = blackKingFile;
18770               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18771               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18772               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18773           case '-':
18774               break;
18775           default: /* FRC castlings */
18776               if(c >= 'a') { /* black rights */
18777                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18778                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18779                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18780                   if(i == BOARD_RGHT) break;
18781                   board[CASTLING][5] = i;
18782                   c -= AAA;
18783                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18784                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18785                   if(c > i)
18786                       board[CASTLING][3] = c;
18787                   else
18788                       board[CASTLING][4] = c;
18789               } else { /* white rights */
18790                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18791                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18792                     if(board[0][i] == WhiteKing) break;
18793                   if(i == BOARD_RGHT) break;
18794                   board[CASTLING][2] = i;
18795                   c -= AAA - 'a' + 'A';
18796                   if(board[0][c] >= WhiteKing) break;
18797                   if(c > i)
18798                       board[CASTLING][0] = c;
18799                   else
18800                       board[CASTLING][1] = c;
18801               }
18802         }
18803       }
18804       for(i=0; i<nrCastlingRights; i++)
18805         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18806       if(gameInfo.variant == VariantSChess)
18807         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18808       if(fischer && shuffle) appData.fischerCastling = TRUE;
18809     if (appData.debugMode) {
18810         fprintf(debugFP, "FEN castling rights:");
18811         for(i=0; i<nrCastlingRights; i++)
18812         fprintf(debugFP, " %d", board[CASTLING][i]);
18813         fprintf(debugFP, "\n");
18814     }
18815
18816       while(*p==' ') p++;
18817     }
18818
18819     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18820
18821     /* read e.p. field in games that know e.p. capture */
18822     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18823        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18824        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18825       if(*p=='-') {
18826         p++; board[EP_STATUS] = EP_NONE;
18827       } else {
18828          char c = *p++ - AAA;
18829
18830          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18831          if(*p >= '0' && *p <='9') p++;
18832          board[EP_STATUS] = c;
18833       }
18834     }
18835
18836     while(*p == ' ') p++;
18837
18838     board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18839     if(sscanf(p, "%d+%d", &i, &j) == 2) {
18840         board[CHECK_COUNT] = i + 256*j;
18841         while(*p && *p != ' ') p++;
18842     }
18843
18844     c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18845     if(c > 0) {
18846         FENrulePlies = i; /* 50-move ply counter */
18847         /* (The move number is still ignored)    */
18848         if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18849     }
18850
18851     return TRUE;
18852 }
18853
18854 void
18855 EditPositionPasteFEN (char *fen)
18856 {
18857   if (fen != NULL) {
18858     Board initial_position;
18859
18860     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18861       DisplayError(_("Bad FEN position in clipboard"), 0);
18862       return ;
18863     } else {
18864       int savedBlackPlaysFirst = blackPlaysFirst;
18865       EditPositionEvent();
18866       blackPlaysFirst = savedBlackPlaysFirst;
18867       CopyBoard(boards[0], initial_position);
18868       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18869       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18870       DisplayBothClocks();
18871       DrawPosition(FALSE, boards[currentMove]);
18872     }
18873   }
18874 }
18875
18876 static char cseq[12] = "\\   ";
18877
18878 Boolean
18879 set_cont_sequence (char *new_seq)
18880 {
18881     int len;
18882     Boolean ret;
18883
18884     // handle bad attempts to set the sequence
18885         if (!new_seq)
18886                 return 0; // acceptable error - no debug
18887
18888     len = strlen(new_seq);
18889     ret = (len > 0) && (len < sizeof(cseq));
18890     if (ret)
18891       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18892     else if (appData.debugMode)
18893       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18894     return ret;
18895 }
18896
18897 /*
18898     reformat a source message so words don't cross the width boundary.  internal
18899     newlines are not removed.  returns the wrapped size (no null character unless
18900     included in source message).  If dest is NULL, only calculate the size required
18901     for the dest buffer.  lp argument indicats line position upon entry, and it's
18902     passed back upon exit.
18903 */
18904 int
18905 wrap (char *dest, char *src, int count, int width, int *lp)
18906 {
18907     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18908
18909     cseq_len = strlen(cseq);
18910     old_line = line = *lp;
18911     ansi = len = clen = 0;
18912
18913     for (i=0; i < count; i++)
18914     {
18915         if (src[i] == '\033')
18916             ansi = 1;
18917
18918         // if we hit the width, back up
18919         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18920         {
18921             // store i & len in case the word is too long
18922             old_i = i, old_len = len;
18923
18924             // find the end of the last word
18925             while (i && src[i] != ' ' && src[i] != '\n')
18926             {
18927                 i--;
18928                 len--;
18929             }
18930
18931             // word too long?  restore i & len before splitting it
18932             if ((old_i-i+clen) >= width)
18933             {
18934                 i = old_i;
18935                 len = old_len;
18936             }
18937
18938             // extra space?
18939             if (i && src[i-1] == ' ')
18940                 len--;
18941
18942             if (src[i] != ' ' && src[i] != '\n')
18943             {
18944                 i--;
18945                 if (len)
18946                     len--;
18947             }
18948
18949             // now append the newline and continuation sequence
18950             if (dest)
18951                 dest[len] = '\n';
18952             len++;
18953             if (dest)
18954                 strncpy(dest+len, cseq, cseq_len);
18955             len += cseq_len;
18956             line = cseq_len;
18957             clen = cseq_len;
18958             continue;
18959         }
18960
18961         if (dest)
18962             dest[len] = src[i];
18963         len++;
18964         if (!ansi)
18965             line++;
18966         if (src[i] == '\n')
18967             line = 0;
18968         if (src[i] == 'm')
18969             ansi = 0;
18970     }
18971     if (dest && appData.debugMode)
18972     {
18973         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18974             count, width, line, len, *lp);
18975         show_bytes(debugFP, src, count);
18976         fprintf(debugFP, "\ndest: ");
18977         show_bytes(debugFP, dest, len);
18978         fprintf(debugFP, "\n");
18979     }
18980     *lp = dest ? line : old_line;
18981
18982     return len;
18983 }
18984
18985 // [HGM] vari: routines for shelving variations
18986 Boolean modeRestore = FALSE;
18987
18988 void
18989 PushInner (int firstMove, int lastMove)
18990 {
18991         int i, j, nrMoves = lastMove - firstMove;
18992
18993         // push current tail of game on stack
18994         savedResult[storedGames] = gameInfo.result;
18995         savedDetails[storedGames] = gameInfo.resultDetails;
18996         gameInfo.resultDetails = NULL;
18997         savedFirst[storedGames] = firstMove;
18998         savedLast [storedGames] = lastMove;
18999         savedFramePtr[storedGames] = framePtr;
19000         framePtr -= nrMoves; // reserve space for the boards
19001         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19002             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19003             for(j=0; j<MOVE_LEN; j++)
19004                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19005             for(j=0; j<2*MOVE_LEN; j++)
19006                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19007             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19008             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19009             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19010             pvInfoList[firstMove+i-1].depth = 0;
19011             commentList[framePtr+i] = commentList[firstMove+i];
19012             commentList[firstMove+i] = NULL;
19013         }
19014
19015         storedGames++;
19016         forwardMostMove = firstMove; // truncate game so we can start variation
19017 }
19018
19019 void
19020 PushTail (int firstMove, int lastMove)
19021 {
19022         if(appData.icsActive) { // only in local mode
19023                 forwardMostMove = currentMove; // mimic old ICS behavior
19024                 return;
19025         }
19026         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19027
19028         PushInner(firstMove, lastMove);
19029         if(storedGames == 1) GreyRevert(FALSE);
19030         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19031 }
19032
19033 void
19034 PopInner (Boolean annotate)
19035 {
19036         int i, j, nrMoves;
19037         char buf[8000], moveBuf[20];
19038
19039         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19040         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19041         nrMoves = savedLast[storedGames] - currentMove;
19042         if(annotate) {
19043                 int cnt = 10;
19044                 if(!WhiteOnMove(currentMove))
19045                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19046                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19047                 for(i=currentMove; i<forwardMostMove; i++) {
19048                         if(WhiteOnMove(i))
19049                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19050                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19051                         strcat(buf, moveBuf);
19052                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19053                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19054                 }
19055                 strcat(buf, ")");
19056         }
19057         for(i=1; i<=nrMoves; i++) { // copy last variation back
19058             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19059             for(j=0; j<MOVE_LEN; j++)
19060                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19061             for(j=0; j<2*MOVE_LEN; j++)
19062                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19063             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19064             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19065             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19066             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19067             commentList[currentMove+i] = commentList[framePtr+i];
19068             commentList[framePtr+i] = NULL;
19069         }
19070         if(annotate) AppendComment(currentMove+1, buf, FALSE);
19071         framePtr = savedFramePtr[storedGames];
19072         gameInfo.result = savedResult[storedGames];
19073         if(gameInfo.resultDetails != NULL) {
19074             free(gameInfo.resultDetails);
19075       }
19076         gameInfo.resultDetails = savedDetails[storedGames];
19077         forwardMostMove = currentMove + nrMoves;
19078 }
19079
19080 Boolean
19081 PopTail (Boolean annotate)
19082 {
19083         if(appData.icsActive) return FALSE; // only in local mode
19084         if(!storedGames) return FALSE; // sanity
19085         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19086
19087         PopInner(annotate);
19088         if(currentMove < forwardMostMove) ForwardEvent(); else
19089         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19090
19091         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19092         return TRUE;
19093 }
19094
19095 void
19096 CleanupTail ()
19097 {       // remove all shelved variations
19098         int i;
19099         for(i=0; i<storedGames; i++) {
19100             if(savedDetails[i])
19101                 free(savedDetails[i]);
19102             savedDetails[i] = NULL;
19103         }
19104         for(i=framePtr; i<MAX_MOVES; i++) {
19105                 if(commentList[i]) free(commentList[i]);
19106                 commentList[i] = NULL;
19107         }
19108         framePtr = MAX_MOVES-1;
19109         storedGames = 0;
19110 }
19111
19112 void
19113 LoadVariation (int index, char *text)
19114 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19115         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19116         int level = 0, move;
19117
19118         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19119         // first find outermost bracketing variation
19120         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19121             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19122                 if(*p == '{') wait = '}'; else
19123                 if(*p == '[') wait = ']'; else
19124                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19125                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19126             }
19127             if(*p == wait) wait = NULLCHAR; // closing ]} found
19128             p++;
19129         }
19130         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19131         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19132         end[1] = NULLCHAR; // clip off comment beyond variation
19133         ToNrEvent(currentMove-1);
19134         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19135         // kludge: use ParsePV() to append variation to game
19136         move = currentMove;
19137         ParsePV(start, TRUE, TRUE);
19138         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19139         ClearPremoveHighlights();
19140         CommentPopDown();
19141         ToNrEvent(currentMove+1);
19142 }
19143
19144 int transparency[2];
19145
19146 void
19147 LoadTheme ()
19148 {
19149 #define BUF_SIZ (2*MSG_SIZ)
19150     char *p, *q, buf[BUF_SIZ];
19151     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19152         snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19153         ParseArgsFromString(buf);
19154         ActivateTheme(TRUE); // also redo colors
19155         return;
19156     }
19157     p = nickName;
19158     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19159     {
19160         int len;
19161         q = appData.themeNames;
19162         snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19163       if(appData.useBitmaps) {
19164         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19165                 Shorten(appData.liteBackTextureFile));
19166         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19167                 Shorten(appData.darkBackTextureFile),
19168                 appData.liteBackTextureMode,
19169                 appData.darkBackTextureMode );
19170       } else {
19171         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19172       }
19173       if(!appData.useBitmaps || transparency[0]) {
19174         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19175       }
19176       if(!appData.useBitmaps || transparency[1]) {
19177         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19178       }
19179       if(appData.useBorder) {
19180         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19181                 appData.border);
19182       } else {
19183         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19184       }
19185       if(appData.useFont) {
19186         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19187                 appData.renderPiecesWithFont,
19188                 appData.fontToPieceTable,
19189                 Col2Text(9),    // appData.fontBackColorWhite
19190                 Col2Text(10) ); // appData.fontForeColorBlack
19191       } else {
19192         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19193         if(appData.pieceDirectory[0]) {
19194           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19195           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19196             snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19197         }
19198         if(!appData.pieceDirectory[0] || !appData.trueColors)
19199           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19200                 Col2Text(0),   // whitePieceColor
19201                 Col2Text(1) ); // blackPieceColor
19202       }
19203       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19204                 Col2Text(4),   // highlightSquareColor
19205                 Col2Text(5) ); // premoveHighlightColor
19206         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19207         if(insert != q) insert[-1] = NULLCHAR;
19208         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19209         if(q)   free(q);
19210     }
19211     ActivateTheme(FALSE);
19212 }