Add msimg32 library to makefile.gcc for WinBoard build
[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   if (appData.debugMode) {
6455     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6456   }
6457     if(shuffleOpenings) {
6458         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6459         startedFromSetupPosition = TRUE;
6460     }
6461     if(startedFromPositionFile) {
6462       /* [HGM] loadPos: use PositionFile for every new game */
6463       CopyBoard(initialPosition, filePosition);
6464       for(i=0; i<nrCastlingRights; i++)
6465           initialRights[i] = filePosition[CASTLING][i];
6466       startedFromSetupPosition = TRUE;
6467     }
6468     if(*appData.men) LoadPieceDesc(appData.men);
6469
6470     CopyBoard(boards[0], initialPosition);
6471
6472     if(oldx != gameInfo.boardWidth ||
6473        oldy != gameInfo.boardHeight ||
6474        oldv != gameInfo.variant ||
6475        oldh != gameInfo.holdingsWidth
6476                                          )
6477             InitDrawingSizes(-2 ,0);
6478
6479     oldv = gameInfo.variant;
6480     if (redraw)
6481       DrawPosition(TRUE, boards[currentMove]);
6482 }
6483
6484 void
6485 SendBoard (ChessProgramState *cps, int moveNum)
6486 {
6487     char message[MSG_SIZ];
6488
6489     if (cps->useSetboard) {
6490       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6491       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6492       SendToProgram(message, cps);
6493       free(fen);
6494
6495     } else {
6496       ChessSquare *bp;
6497       int i, j, left=0, right=BOARD_WIDTH;
6498       /* Kludge to set black to move, avoiding the troublesome and now
6499        * deprecated "black" command.
6500        */
6501       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6502         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6503
6504       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6505
6506       SendToProgram("edit\n", cps);
6507       SendToProgram("#\n", cps);
6508       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6509         bp = &boards[moveNum][i][left];
6510         for (j = left; j < right; j++, bp++) {
6511           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6512           if ((int) *bp < (int) BlackPawn) {
6513             if(j == BOARD_RGHT+1)
6514                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6515             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6516             if(message[0] == '+' || message[0] == '~') {
6517               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6518                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6519                         AAA + j, ONE + i - '0');
6520             }
6521             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6522                 message[1] = BOARD_RGHT   - 1 - j + '1';
6523                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6524             }
6525             SendToProgram(message, cps);
6526           }
6527         }
6528       }
6529
6530       SendToProgram("c\n", cps);
6531       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6532         bp = &boards[moveNum][i][left];
6533         for (j = left; j < right; j++, bp++) {
6534           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6535           if (((int) *bp != (int) EmptySquare)
6536               && ((int) *bp >= (int) BlackPawn)) {
6537             if(j == BOARD_LEFT-2)
6538                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6539             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6540                     AAA + j, ONE + i - '0');
6541             if(message[0] == '+' || message[0] == '~') {
6542               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6543                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6544                         AAA + j, ONE + i - '0');
6545             }
6546             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6547                 message[1] = BOARD_RGHT   - 1 - j + '1';
6548                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6549             }
6550             SendToProgram(message, cps);
6551           }
6552         }
6553       }
6554
6555       SendToProgram(".\n", cps);
6556     }
6557     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6558 }
6559
6560 char exclusionHeader[MSG_SIZ];
6561 int exCnt, excludePtr;
6562 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6563 static Exclusion excluTab[200];
6564 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6565
6566 static void
6567 WriteMap (int s)
6568 {
6569     int j;
6570     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6571     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6572 }
6573
6574 static void
6575 ClearMap ()
6576 {
6577     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6578     excludePtr = 24; exCnt = 0;
6579     WriteMap(0);
6580 }
6581
6582 static void
6583 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6584 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6585     char buf[2*MOVE_LEN], *p;
6586     Exclusion *e = excluTab;
6587     int i;
6588     for(i=0; i<exCnt; i++)
6589         if(e[i].ff == fromX && e[i].fr == fromY &&
6590            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6591     if(i == exCnt) { // was not in exclude list; add it
6592         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6593         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6594             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6595             return; // abort
6596         }
6597         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6598         excludePtr++; e[i].mark = excludePtr++;
6599         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6600         exCnt++;
6601     }
6602     exclusionHeader[e[i].mark] = state;
6603 }
6604
6605 static int
6606 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6607 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6608     char buf[MSG_SIZ];
6609     int j, k;
6610     ChessMove moveType;
6611     if((signed char)promoChar == -1) { // kludge to indicate best move
6612         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6613             return 1; // if unparsable, abort
6614     }
6615     // update exclusion map (resolving toggle by consulting existing state)
6616     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6617     j = k%8; k >>= 3;
6618     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6619     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6620          excludeMap[k] |=   1<<j;
6621     else excludeMap[k] &= ~(1<<j);
6622     // update header
6623     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6624     // inform engine
6625     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6626     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6627     SendToBoth(buf);
6628     return (state == '+');
6629 }
6630
6631 static void
6632 ExcludeClick (int index)
6633 {
6634     int i, j;
6635     Exclusion *e = excluTab;
6636     if(index < 25) { // none, best or tail clicked
6637         if(index < 13) { // none: include all
6638             WriteMap(0); // clear map
6639             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6640             SendToBoth("include all\n"); // and inform engine
6641         } else if(index > 18) { // tail
6642             if(exclusionHeader[19] == '-') { // tail was excluded
6643                 SendToBoth("include all\n");
6644                 WriteMap(0); // clear map completely
6645                 // now re-exclude selected moves
6646                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6647                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6648             } else { // tail was included or in mixed state
6649                 SendToBoth("exclude all\n");
6650                 WriteMap(0xFF); // fill map completely
6651                 // now re-include selected moves
6652                 j = 0; // count them
6653                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6654                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6655                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6656             }
6657         } else { // best
6658             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6659         }
6660     } else {
6661         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6662             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6663             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6664             break;
6665         }
6666     }
6667 }
6668
6669 ChessSquare
6670 DefaultPromoChoice (int white)
6671 {
6672     ChessSquare result;
6673     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6674        gameInfo.variant == VariantMakruk)
6675         result = WhiteFerz; // no choice
6676     else if(gameInfo.variant == VariantASEAN)
6677         result = WhiteRook; // no choice
6678     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6679         result= WhiteKing; // in Suicide Q is the last thing we want
6680     else if(gameInfo.variant == VariantSpartan)
6681         result = white ? WhiteQueen : WhiteAngel;
6682     else result = WhiteQueen;
6683     if(!white) result = WHITE_TO_BLACK result;
6684     return result;
6685 }
6686
6687 static int autoQueen; // [HGM] oneclick
6688
6689 int
6690 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6691 {
6692     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6693     /* [HGM] add Shogi promotions */
6694     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6695     ChessSquare piece, partner;
6696     ChessMove moveType;
6697     Boolean premove;
6698
6699     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6700     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6701
6702     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6703       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6704         return FALSE;
6705
6706     if(legal[toY][toX] == 4) return FALSE;
6707
6708     piece = boards[currentMove][fromY][fromX];
6709     if(gameInfo.variant == VariantChu) {
6710         promotionZoneSize = BOARD_HEIGHT/3;
6711         if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6712         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6713     } else if(gameInfo.variant == VariantShogi) {
6714         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6715         highestPromotingPiece = (int)WhiteAlfil;
6716     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6717         promotionZoneSize = 3;
6718     }
6719
6720     // Treat Lance as Pawn when it is not representing Amazon or Lance
6721     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6722         if(piece == WhiteLance) piece = WhitePawn; else
6723         if(piece == BlackLance) piece = BlackPawn;
6724     }
6725
6726     // next weed out all moves that do not touch the promotion zone at all
6727     if((int)piece >= BlackPawn) {
6728         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6729              return FALSE;
6730         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6731         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6732     } else {
6733         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6734            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6735         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6736              return FALSE;
6737     }
6738
6739     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6740
6741     // weed out mandatory Shogi promotions
6742     if(gameInfo.variant == VariantShogi) {
6743         if(piece >= BlackPawn) {
6744             if(toY == 0 && piece == BlackPawn ||
6745                toY == 0 && piece == BlackQueen ||
6746                toY <= 1 && piece == BlackKnight) {
6747                 *promoChoice = '+';
6748                 return FALSE;
6749             }
6750         } else {
6751             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6752                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6753                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6754                 *promoChoice = '+';
6755                 return FALSE;
6756             }
6757         }
6758     }
6759
6760     // weed out obviously illegal Pawn moves
6761     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6762         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6763         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6764         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6765         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6766         // note we are not allowed to test for valid (non-)capture, due to premove
6767     }
6768
6769     // we either have a choice what to promote to, or (in Shogi) whether to promote
6770     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6771        gameInfo.variant == VariantMakruk) {
6772         ChessSquare p=BlackFerz;  // no choice
6773         while(p < EmptySquare) {  //but make sure we use piece that exists
6774             *promoChoice = PieceToChar(p++);
6775             if(*promoChoice != '.') break;
6776         }
6777         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6778     }
6779     // no sense asking what we must promote to if it is going to explode...
6780     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6781         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6782         return FALSE;
6783     }
6784     // give caller the default choice even if we will not make it
6785     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6786     partner = piece; // pieces can promote if the pieceToCharTable says so
6787     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6788     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6789     if(        sweepSelect && gameInfo.variant != VariantGreat
6790                            && gameInfo.variant != VariantGrand
6791                            && gameInfo.variant != VariantSuper) return FALSE;
6792     if(autoQueen) return FALSE; // predetermined
6793
6794     // suppress promotion popup on illegal moves that are not premoves
6795     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6796               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6797     if(appData.testLegality && !premove) {
6798         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6799                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6800         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6801         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6802             return FALSE;
6803     }
6804
6805     return TRUE;
6806 }
6807
6808 int
6809 InPalace (int row, int column)
6810 {   /* [HGM] for Xiangqi */
6811     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6812          column < (BOARD_WIDTH + 4)/2 &&
6813          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6814     return FALSE;
6815 }
6816
6817 int
6818 PieceForSquare (int x, int y)
6819 {
6820   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6821      return -1;
6822   else
6823      return boards[currentMove][y][x];
6824 }
6825
6826 int
6827 OKToStartUserMove (int x, int y)
6828 {
6829     ChessSquare from_piece;
6830     int white_piece;
6831
6832     if (matchMode) return FALSE;
6833     if (gameMode == EditPosition) return TRUE;
6834
6835     if (x >= 0 && y >= 0)
6836       from_piece = boards[currentMove][y][x];
6837     else
6838       from_piece = EmptySquare;
6839
6840     if (from_piece == EmptySquare) return FALSE;
6841
6842     white_piece = (int)from_piece >= (int)WhitePawn &&
6843       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6844
6845     switch (gameMode) {
6846       case AnalyzeFile:
6847       case TwoMachinesPlay:
6848       case EndOfGame:
6849         return FALSE;
6850
6851       case IcsObserving:
6852       case IcsIdle:
6853         return FALSE;
6854
6855       case MachinePlaysWhite:
6856       case IcsPlayingBlack:
6857         if (appData.zippyPlay) return FALSE;
6858         if (white_piece) {
6859             DisplayMoveError(_("You are playing Black"));
6860             return FALSE;
6861         }
6862         break;
6863
6864       case MachinePlaysBlack:
6865       case IcsPlayingWhite:
6866         if (appData.zippyPlay) return FALSE;
6867         if (!white_piece) {
6868             DisplayMoveError(_("You are playing White"));
6869             return FALSE;
6870         }
6871         break;
6872
6873       case PlayFromGameFile:
6874             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6875       case EditGame:
6876       case AnalyzeMode:
6877         if (!white_piece && WhiteOnMove(currentMove)) {
6878             DisplayMoveError(_("It is White's turn"));
6879             return FALSE;
6880         }
6881         if (white_piece && !WhiteOnMove(currentMove)) {
6882             DisplayMoveError(_("It is Black's turn"));
6883             return FALSE;
6884         }
6885         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6886             /* Editing correspondence game history */
6887             /* Could disallow this or prompt for confirmation */
6888             cmailOldMove = -1;
6889         }
6890         break;
6891
6892       case BeginningOfGame:
6893         if (appData.icsActive) return FALSE;
6894         if (!appData.noChessProgram) {
6895             if (!white_piece) {
6896                 DisplayMoveError(_("You are playing White"));
6897                 return FALSE;
6898             }
6899         }
6900         break;
6901
6902       case Training:
6903         if (!white_piece && WhiteOnMove(currentMove)) {
6904             DisplayMoveError(_("It is White's turn"));
6905             return FALSE;
6906         }
6907         if (white_piece && !WhiteOnMove(currentMove)) {
6908             DisplayMoveError(_("It is Black's turn"));
6909             return FALSE;
6910         }
6911         break;
6912
6913       default:
6914       case IcsExamining:
6915         break;
6916     }
6917     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6918         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6919         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6920         && gameMode != AnalyzeFile && gameMode != Training) {
6921         DisplayMoveError(_("Displayed position is not current"));
6922         return FALSE;
6923     }
6924     return TRUE;
6925 }
6926
6927 Boolean
6928 OnlyMove (int *x, int *y, Boolean captures)
6929 {
6930     DisambiguateClosure cl;
6931     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6932     switch(gameMode) {
6933       case MachinePlaysBlack:
6934       case IcsPlayingWhite:
6935       case BeginningOfGame:
6936         if(!WhiteOnMove(currentMove)) return FALSE;
6937         break;
6938       case MachinePlaysWhite:
6939       case IcsPlayingBlack:
6940         if(WhiteOnMove(currentMove)) return FALSE;
6941         break;
6942       case EditGame:
6943         break;
6944       default:
6945         return FALSE;
6946     }
6947     cl.pieceIn = EmptySquare;
6948     cl.rfIn = *y;
6949     cl.ffIn = *x;
6950     cl.rtIn = -1;
6951     cl.ftIn = -1;
6952     cl.promoCharIn = NULLCHAR;
6953     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6954     if( cl.kind == NormalMove ||
6955         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6956         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6957         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6958       fromX = cl.ff;
6959       fromY = cl.rf;
6960       *x = cl.ft;
6961       *y = cl.rt;
6962       return TRUE;
6963     }
6964     if(cl.kind != ImpossibleMove) return FALSE;
6965     cl.pieceIn = EmptySquare;
6966     cl.rfIn = -1;
6967     cl.ffIn = -1;
6968     cl.rtIn = *y;
6969     cl.ftIn = *x;
6970     cl.promoCharIn = NULLCHAR;
6971     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6972     if( cl.kind == NormalMove ||
6973         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6974         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6975         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6976       fromX = cl.ff;
6977       fromY = cl.rf;
6978       *x = cl.ft;
6979       *y = cl.rt;
6980       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6981       return TRUE;
6982     }
6983     return FALSE;
6984 }
6985
6986 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6987 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6988 int lastLoadGameUseList = FALSE;
6989 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6990 ChessMove lastLoadGameStart = EndOfFile;
6991 int doubleClick;
6992 Boolean addToBookFlag;
6993 static Board rightsBoard, nullBoard;
6994
6995 void
6996 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6997 {
6998     ChessMove moveType;
6999     ChessSquare pup;
7000     int ff=fromX, rf=fromY, ft=toX, rt=toY;
7001
7002     /* Check if the user is playing in turn.  This is complicated because we
7003        let the user "pick up" a piece before it is his turn.  So the piece he
7004        tried to pick up may have been captured by the time he puts it down!
7005        Therefore we use the color the user is supposed to be playing in this
7006        test, not the color of the piece that is currently on the starting
7007        square---except in EditGame mode, where the user is playing both
7008        sides; fortunately there the capture race can't happen.  (It can
7009        now happen in IcsExamining mode, but that's just too bad.  The user
7010        will get a somewhat confusing message in that case.)
7011        */
7012
7013     switch (gameMode) {
7014       case AnalyzeFile:
7015       case TwoMachinesPlay:
7016       case EndOfGame:
7017       case IcsObserving:
7018       case IcsIdle:
7019         /* We switched into a game mode where moves are not accepted,
7020            perhaps while the mouse button was down. */
7021         return;
7022
7023       case MachinePlaysWhite:
7024         /* User is moving for Black */
7025         if (WhiteOnMove(currentMove)) {
7026             DisplayMoveError(_("It is White's turn"));
7027             return;
7028         }
7029         break;
7030
7031       case MachinePlaysBlack:
7032         /* User is moving for White */
7033         if (!WhiteOnMove(currentMove)) {
7034             DisplayMoveError(_("It is Black's turn"));
7035             return;
7036         }
7037         break;
7038
7039       case PlayFromGameFile:
7040             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7041       case EditGame:
7042       case IcsExamining:
7043       case BeginningOfGame:
7044       case AnalyzeMode:
7045       case Training:
7046         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7047         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7048             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7049             /* User is moving for Black */
7050             if (WhiteOnMove(currentMove)) {
7051                 DisplayMoveError(_("It is White's turn"));
7052                 return;
7053             }
7054         } else {
7055             /* User is moving for White */
7056             if (!WhiteOnMove(currentMove)) {
7057                 DisplayMoveError(_("It is Black's turn"));
7058                 return;
7059             }
7060         }
7061         break;
7062
7063       case IcsPlayingBlack:
7064         /* User is moving for Black */
7065         if (WhiteOnMove(currentMove)) {
7066             if (!appData.premove) {
7067                 DisplayMoveError(_("It is White's turn"));
7068             } else if (toX >= 0 && toY >= 0) {
7069                 premoveToX = toX;
7070                 premoveToY = toY;
7071                 premoveFromX = fromX;
7072                 premoveFromY = fromY;
7073                 premovePromoChar = promoChar;
7074                 gotPremove = 1;
7075                 if (appData.debugMode)
7076                     fprintf(debugFP, "Got premove: fromX %d,"
7077                             "fromY %d, toX %d, toY %d\n",
7078                             fromX, fromY, toX, toY);
7079             }
7080             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7081             return;
7082         }
7083         break;
7084
7085       case IcsPlayingWhite:
7086         /* User is moving for White */
7087         if (!WhiteOnMove(currentMove)) {
7088             if (!appData.premove) {
7089                 DisplayMoveError(_("It is Black's turn"));
7090             } else if (toX >= 0 && toY >= 0) {
7091                 premoveToX = toX;
7092                 premoveToY = toY;
7093                 premoveFromX = fromX;
7094                 premoveFromY = fromY;
7095                 premovePromoChar = promoChar;
7096                 gotPremove = 1;
7097                 if (appData.debugMode)
7098                     fprintf(debugFP, "Got premove: fromX %d,"
7099                             "fromY %d, toX %d, toY %d\n",
7100                             fromX, fromY, toX, toY);
7101             }
7102             DrawPosition(TRUE, boards[currentMove]);
7103             return;
7104         }
7105         break;
7106
7107       default:
7108         break;
7109
7110       case EditPosition:
7111         /* EditPosition, empty square, or different color piece;
7112            click-click move is possible */
7113         if (toX == -2 || toY == -2) {
7114             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7115             DrawPosition(FALSE, boards[currentMove]);
7116             return;
7117         } else if (toX >= 0 && toY >= 0) {
7118             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7119                 ChessSquare p = boards[0][rf][ff];
7120                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7121                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7122                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7123                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7124                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7125                     gatingPiece = p;
7126                 }
7127             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7128             boards[0][toY][toX] = boards[0][fromY][fromX];
7129             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7130                 if(boards[0][fromY][0] != EmptySquare) {
7131                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7132                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7133                 }
7134             } else
7135             if(fromX == BOARD_RGHT+1) {
7136                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7137                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7138                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7139                 }
7140             } else
7141             boards[0][fromY][fromX] = gatingPiece;
7142             ClearHighlights();
7143             DrawPosition(FALSE, boards[currentMove]);
7144             return;
7145         }
7146         return;
7147     }
7148
7149     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7150     pup = boards[currentMove][toY][toX];
7151
7152     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7153     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7154          if( pup != EmptySquare ) return;
7155          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7156            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7157                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7158            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7159            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7160            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7161            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7162          fromY = DROP_RANK;
7163     }
7164
7165     /* [HGM] always test for legality, to get promotion info */
7166     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7167                                          fromY, fromX, toY, toX, promoChar);
7168
7169     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7170
7171     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7172
7173     /* [HGM] but possibly ignore an IllegalMove result */
7174     if (appData.testLegality) {
7175         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7176             DisplayMoveError(_("Illegal move"));
7177             return;
7178         }
7179     }
7180
7181     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7182         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7183              ClearPremoveHighlights(); // was included
7184         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7185         DrawPosition(FALSE, NULL);
7186         return;
7187     }
7188
7189     if(addToBookFlag) { // adding moves to book
7190         char buf[MSG_SIZ], move[MSG_SIZ];
7191         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7192         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7193                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7194         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7195         AddBookMove(buf);
7196         addToBookFlag = FALSE;
7197         ClearHighlights();
7198         return;
7199     }
7200
7201     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7202 }
7203
7204 /* Common tail of UserMoveEvent and DropMenuEvent */
7205 int
7206 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7207 {
7208     char *bookHit = 0;
7209
7210     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7211         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7212         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7213         if(WhiteOnMove(currentMove)) {
7214             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7215         } else {
7216             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7217         }
7218     }
7219
7220     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7221        move type in caller when we know the move is a legal promotion */
7222     if(moveType == NormalMove && promoChar)
7223         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7224
7225     /* [HGM] <popupFix> The following if has been moved here from
7226        UserMoveEvent(). Because it seemed to belong here (why not allow
7227        piece drops in training games?), and because it can only be
7228        performed after it is known to what we promote. */
7229     if (gameMode == Training) {
7230       /* compare the move played on the board to the next move in the
7231        * game. If they match, display the move and the opponent's response.
7232        * If they don't match, display an error message.
7233        */
7234       int saveAnimate;
7235       Board testBoard;
7236       CopyBoard(testBoard, boards[currentMove]);
7237       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7238
7239       if (CompareBoards(testBoard, boards[currentMove+1])) {
7240         ForwardInner(currentMove+1);
7241
7242         /* Autoplay the opponent's response.
7243          * if appData.animate was TRUE when Training mode was entered,
7244          * the response will be animated.
7245          */
7246         saveAnimate = appData.animate;
7247         appData.animate = animateTraining;
7248         ForwardInner(currentMove+1);
7249         appData.animate = saveAnimate;
7250
7251         /* check for the end of the game */
7252         if (currentMove >= forwardMostMove) {
7253           gameMode = PlayFromGameFile;
7254           ModeHighlight();
7255           SetTrainingModeOff();
7256           DisplayInformation(_("End of game"));
7257         }
7258       } else {
7259         DisplayError(_("Incorrect move"), 0);
7260       }
7261       return 1;
7262     }
7263
7264   /* Ok, now we know that the move is good, so we can kill
7265      the previous line in Analysis Mode */
7266   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7267                                 && currentMove < forwardMostMove) {
7268     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7269     else forwardMostMove = currentMove;
7270   }
7271
7272   ClearMap();
7273
7274   /* If we need the chess program but it's dead, restart it */
7275   ResurrectChessProgram();
7276
7277   /* A user move restarts a paused game*/
7278   if (pausing)
7279     PauseEvent();
7280
7281   thinkOutput[0] = NULLCHAR;
7282
7283   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7284
7285   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7286     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7287     return 1;
7288   }
7289
7290   if (gameMode == BeginningOfGame) {
7291     if (appData.noChessProgram) {
7292       gameMode = EditGame;
7293       SetGameInfo();
7294     } else {
7295       char buf[MSG_SIZ];
7296       gameMode = MachinePlaysBlack;
7297       StartClocks();
7298       SetGameInfo();
7299       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7300       DisplayTitle(buf);
7301       if (first.sendName) {
7302         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7303         SendToProgram(buf, &first);
7304       }
7305       StartClocks();
7306     }
7307     ModeHighlight();
7308   }
7309
7310   /* Relay move to ICS or chess engine */
7311   if (appData.icsActive) {
7312     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7313         gameMode == IcsExamining) {
7314       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7315         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7316         SendToICS("draw ");
7317         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7318       }
7319       // also send plain move, in case ICS does not understand atomic claims
7320       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7321       ics_user_moved = 1;
7322     }
7323   } else {
7324     if (first.sendTime && (gameMode == BeginningOfGame ||
7325                            gameMode == MachinePlaysWhite ||
7326                            gameMode == MachinePlaysBlack)) {
7327       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7328     }
7329     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7330          // [HGM] book: if program might be playing, let it use book
7331         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7332         first.maybeThinking = TRUE;
7333     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7334         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7335         SendBoard(&first, currentMove+1);
7336         if(second.analyzing) {
7337             if(!second.useSetboard) SendToProgram("undo\n", &second);
7338             SendBoard(&second, currentMove+1);
7339         }
7340     } else {
7341         SendMoveToProgram(forwardMostMove-1, &first);
7342         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7343     }
7344     if (currentMove == cmailOldMove + 1) {
7345       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7346     }
7347   }
7348
7349   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7350
7351   switch (gameMode) {
7352   case EditGame:
7353     if(appData.testLegality)
7354     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7355     case MT_NONE:
7356     case MT_CHECK:
7357       break;
7358     case MT_CHECKMATE:
7359     case MT_STAINMATE:
7360       if (WhiteOnMove(currentMove)) {
7361         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7362       } else {
7363         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7364       }
7365       break;
7366     case MT_STALEMATE:
7367       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7368       break;
7369     }
7370     break;
7371
7372   case MachinePlaysBlack:
7373   case MachinePlaysWhite:
7374     /* disable certain menu options while machine is thinking */
7375     SetMachineThinkingEnables();
7376     break;
7377
7378   default:
7379     break;
7380   }
7381
7382   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7383   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7384
7385   if(bookHit) { // [HGM] book: simulate book reply
7386         static char bookMove[MSG_SIZ]; // a bit generous?
7387
7388         programStats.nodes = programStats.depth = programStats.time =
7389         programStats.score = programStats.got_only_move = 0;
7390         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7391
7392         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7393         strcat(bookMove, bookHit);
7394         HandleMachineMove(bookMove, &first);
7395   }
7396   return 1;
7397 }
7398
7399 void
7400 MarkByFEN(char *fen)
7401 {
7402         int r, f;
7403         if(!appData.markers || !appData.highlightDragging) return;
7404         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7405         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7406         while(*fen) {
7407             int s = 0;
7408             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7409             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7410             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7411             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7412             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7413             if(*fen == 'T') marker[r][f++] = 0; else
7414             if(*fen == 'Y') marker[r][f++] = 1; else
7415             if(*fen == 'G') marker[r][f++] = 3; else
7416             if(*fen == 'B') marker[r][f++] = 4; else
7417             if(*fen == 'C') marker[r][f++] = 5; else
7418             if(*fen == 'M') marker[r][f++] = 6; else
7419             if(*fen == 'W') marker[r][f++] = 7; else
7420             if(*fen == 'D') marker[r][f++] = 8; else
7421             if(*fen == 'R') marker[r][f++] = 2; else {
7422                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7423               f += s; fen -= s>0;
7424             }
7425             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7426             if(r < 0) break;
7427             fen++;
7428         }
7429         DrawPosition(TRUE, NULL);
7430 }
7431
7432 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7433
7434 void
7435 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7436 {
7437     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7438     Markers *m = (Markers *) closure;
7439     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7440                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7441         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7442                          || kind == WhiteCapturesEnPassant
7443                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7444     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7445 }
7446
7447 static int hoverSavedValid;
7448
7449 void
7450 MarkTargetSquares (int clear)
7451 {
7452   int x, y, sum=0;
7453   if(clear) { // no reason to ever suppress clearing
7454     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7455     hoverSavedValid = 0;
7456     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7457   } else {
7458     int capt = 0;
7459     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7460        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7461     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7462     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7463       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7464       if(capt)
7465       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7466     }
7467   }
7468   DrawPosition(FALSE, NULL);
7469 }
7470
7471 int
7472 Explode (Board board, int fromX, int fromY, int toX, int toY)
7473 {
7474     if(gameInfo.variant == VariantAtomic &&
7475        (board[toY][toX] != EmptySquare ||                     // capture?
7476         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7477                          board[fromY][fromX] == BlackPawn   )
7478       )) {
7479         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7480         return TRUE;
7481     }
7482     return FALSE;
7483 }
7484
7485 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7486
7487 int
7488 CanPromote (ChessSquare piece, int y)
7489 {
7490         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7491         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7492         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7493         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7494            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7495           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7496            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7497         return (piece == BlackPawn && y <= zone ||
7498                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7499                 piece == BlackLance && y <= zone ||
7500                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7501 }
7502
7503 void
7504 HoverEvent (int xPix, int yPix, int x, int y)
7505 {
7506         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7507         int r, f;
7508         if(!first.highlight) return;
7509         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7510         if(x == oldX && y == oldY) return; // only do something if we enter new square
7511         oldFromX = fromX; oldFromY = fromY;
7512         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7513           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7514             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7515           hoverSavedValid = 1;
7516         } else if(oldX != x || oldY != y) {
7517           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7518           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7519           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7520             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7521           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7522             char buf[MSG_SIZ];
7523             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7524             SendToProgram(buf, &first);
7525           }
7526           oldX = x; oldY = y;
7527 //        SetHighlights(fromX, fromY, x, y);
7528         }
7529 }
7530
7531 void ReportClick(char *action, int x, int y)
7532 {
7533         char buf[MSG_SIZ]; // Inform engine of what user does
7534         int r, f;
7535         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7536           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7537             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7538         if(!first.highlight || gameMode == EditPosition) return;
7539         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7540         SendToProgram(buf, &first);
7541 }
7542
7543 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7544 Boolean deferChoice;
7545
7546 void
7547 LeftClick (ClickType clickType, int xPix, int yPix)
7548 {
7549     int x, y;
7550     static Boolean saveAnimate;
7551     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7552     char promoChoice = NULLCHAR;
7553     ChessSquare piece;
7554     static TimeMark lastClickTime, prevClickTime;
7555
7556     if(flashing) return;
7557
7558   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7559     x = EventToSquare(xPix, BOARD_WIDTH);
7560     y = EventToSquare(yPix, BOARD_HEIGHT);
7561     if (!flipView && y >= 0) {
7562         y = BOARD_HEIGHT - 1 - y;
7563     }
7564     if (flipView && x >= 0) {
7565         x = BOARD_WIDTH - 1 - x;
7566     }
7567
7568     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7569         static int dummy;
7570         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7571         right = TRUE;
7572         return;
7573     }
7574
7575     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7576
7577     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7578
7579     if (clickType == Press) ErrorPopDown();
7580     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7581
7582     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7583         defaultPromoChoice = promoSweep;
7584         promoSweep = EmptySquare;   // terminate sweep
7585         promoDefaultAltered = TRUE;
7586         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7587     }
7588
7589     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7590         if(clickType == Release) return; // ignore upclick of click-click destination
7591         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7592         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7593         if(gameInfo.holdingsWidth &&
7594                 (WhiteOnMove(currentMove)
7595                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7596                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7597             // click in right holdings, for determining promotion piece
7598             ChessSquare p = boards[currentMove][y][x];
7599             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7600             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7601             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7602                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7603                 fromX = fromY = -1;
7604                 return;
7605             }
7606         }
7607         DrawPosition(FALSE, boards[currentMove]);
7608         return;
7609     }
7610
7611     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7612     if(clickType == Press
7613             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7614               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7615               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7616         return;
7617
7618     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7619         // could be static click on premove from-square: abort premove
7620         gotPremove = 0;
7621         ClearPremoveHighlights();
7622     }
7623
7624     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7625         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7626
7627     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7628         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7629                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7630         defaultPromoChoice = DefaultPromoChoice(side);
7631     }
7632
7633     autoQueen = appData.alwaysPromoteToQueen;
7634
7635     if (fromX == -1) {
7636       int originalY = y;
7637       gatingPiece = EmptySquare;
7638       if (clickType != Press) {
7639         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7640             DragPieceEnd(xPix, yPix); dragging = 0;
7641             DrawPosition(FALSE, NULL);
7642         }
7643         return;
7644       }
7645       doubleClick = FALSE;
7646       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7647         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7648       }
7649       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7650       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7651          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7652          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7653             /* First square */
7654             if (OKToStartUserMove(fromX, fromY)) {
7655                 second = 0;
7656                 ReportClick("lift", x, y);
7657                 MarkTargetSquares(0);
7658                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7659                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7660                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7661                     promoSweep = defaultPromoChoice;
7662                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7663                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7664                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7665                 }
7666                 if (appData.highlightDragging) {
7667                     SetHighlights(fromX, fromY, -1, -1);
7668                 } else {
7669                     ClearHighlights();
7670                 }
7671             } else fromX = fromY = -1;
7672             return;
7673         }
7674     }
7675
7676     /* fromX != -1 */
7677     if (clickType == Press && gameMode != EditPosition) {
7678         ChessSquare fromP;
7679         ChessSquare toP;
7680         int frc;
7681
7682         // ignore off-board to clicks
7683         if(y < 0 || x < 0) return;
7684
7685         /* Check if clicking again on the same color piece */
7686         fromP = boards[currentMove][fromY][fromX];
7687         toP = boards[currentMove][y][x];
7688         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7689         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7690             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7691            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7692              WhitePawn <= toP && toP <= WhiteKing &&
7693              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7694              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7695             (BlackPawn <= fromP && fromP <= BlackKing &&
7696              BlackPawn <= toP && toP <= BlackKing &&
7697              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7698              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7699             /* Clicked again on same color piece -- changed his mind */
7700             second = (x == fromX && y == fromY);
7701             killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7702             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7703                 second = FALSE; // first double-click rather than scond click
7704                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7705             }
7706             promoDefaultAltered = FALSE;
7707            if(!second) MarkTargetSquares(1);
7708            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7709             if (appData.highlightDragging) {
7710                 SetHighlights(x, y, -1, -1);
7711             } else {
7712                 ClearHighlights();
7713             }
7714             if (OKToStartUserMove(x, y)) {
7715                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7716                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7717                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7718                  gatingPiece = boards[currentMove][fromY][fromX];
7719                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7720                 fromX = x;
7721                 fromY = y; dragging = 1;
7722                 if(!second) ReportClick("lift", x, y);
7723                 MarkTargetSquares(0);
7724                 DragPieceBegin(xPix, yPix, FALSE);
7725                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7726                     promoSweep = defaultPromoChoice;
7727                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7728                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7729                 }
7730             }
7731            }
7732            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7733            second = FALSE;
7734         }
7735         // ignore clicks on holdings
7736         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7737     }
7738
7739     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7740         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7741         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7742         return;
7743     }
7744
7745     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7746         DragPieceEnd(xPix, yPix); dragging = 0;
7747         if(clearFlag) {
7748             // a deferred attempt to click-click move an empty square on top of a piece
7749             boards[currentMove][y][x] = EmptySquare;
7750             ClearHighlights();
7751             DrawPosition(FALSE, boards[currentMove]);
7752             fromX = fromY = -1; clearFlag = 0;
7753             return;
7754         }
7755         if (appData.animateDragging) {
7756             /* Undo animation damage if any */
7757             DrawPosition(FALSE, NULL);
7758         }
7759         if (second) {
7760             /* Second up/down in same square; just abort move */
7761             second = 0;
7762             fromX = fromY = -1;
7763             gatingPiece = EmptySquare;
7764             ClearHighlights();
7765             gotPremove = 0;
7766             ClearPremoveHighlights();
7767             MarkTargetSquares(-1);
7768             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7769         } else {
7770             /* First upclick in same square; start click-click mode */
7771             SetHighlights(x, y, -1, -1);
7772         }
7773         return;
7774     }
7775
7776     clearFlag = 0;
7777
7778     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7779        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7780         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7781         DisplayMessage(_("only marked squares are legal"),"");
7782         DrawPosition(TRUE, NULL);
7783         return; // ignore to-click
7784     }
7785
7786     /* we now have a different from- and (possibly off-board) to-square */
7787     /* Completed move */
7788     if(!sweepSelecting) {
7789         toX = x;
7790         toY = y;
7791     }
7792
7793     piece = boards[currentMove][fromY][fromX];
7794
7795     saveAnimate = appData.animate;
7796     if (clickType == Press) {
7797         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7798         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7799             // must be Edit Position mode with empty-square selected
7800             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7801             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7802             return;
7803         }
7804         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7805             return;
7806         }
7807         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7808             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7809         } else
7810         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7811         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7812           if(appData.sweepSelect) {
7813             promoSweep = defaultPromoChoice;
7814             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7815             selectFlag = 0; lastX = xPix; lastY = yPix;
7816             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7817             saveFlash = appData.flashCount; appData.flashCount = 0;
7818             Sweep(0); // Pawn that is going to promote: preview promotion piece
7819             sweepSelecting = 1;
7820             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7821             MarkTargetSquares(1);
7822           }
7823           return; // promo popup appears on up-click
7824         }
7825         /* Finish clickclick move */
7826         if (appData.animate || appData.highlightLastMove) {
7827             SetHighlights(fromX, fromY, toX, toY);
7828         } else {
7829             ClearHighlights();
7830         }
7831         MarkTargetSquares(1);
7832     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7833         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7834         *promoRestrict = 0; appData.flashCount = saveFlash;
7835         if (appData.animate || appData.highlightLastMove) {
7836             SetHighlights(fromX, fromY, toX, toY);
7837         } else {
7838             ClearHighlights();
7839         }
7840         MarkTargetSquares(1);
7841     } else {
7842 #if 0
7843 // [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
7844         /* Finish drag move */
7845         if (appData.highlightLastMove) {
7846             SetHighlights(fromX, fromY, toX, toY);
7847         } else {
7848             ClearHighlights();
7849         }
7850 #endif
7851         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7852           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7853         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7854         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7855           dragging *= 2;            // flag button-less dragging if we are dragging
7856           MarkTargetSquares(1);
7857           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7858           else {
7859             kill2X = killX; kill2Y = killY;
7860             killX = x; killY = y;     // remember this square as intermediate
7861             ReportClick("put", x, y); // and inform engine
7862             ReportClick("lift", x, y);
7863             MarkTargetSquares(0);
7864             return;
7865           }
7866         }
7867         DragPieceEnd(xPix, yPix); dragging = 0;
7868         /* Don't animate move and drag both */
7869         appData.animate = FALSE;
7870         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7871     }
7872
7873     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7874     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7875         ChessSquare piece = boards[currentMove][fromY][fromX];
7876         if(gameMode == EditPosition && piece != EmptySquare &&
7877            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7878             int n;
7879
7880             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7881                 n = PieceToNumber(piece - (int)BlackPawn);
7882                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7883                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7884                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7885             } else
7886             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7887                 n = PieceToNumber(piece);
7888                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7889                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7890                 boards[currentMove][n][BOARD_WIDTH-2]++;
7891             }
7892             boards[currentMove][fromY][fromX] = EmptySquare;
7893         }
7894         ClearHighlights();
7895         fromX = fromY = -1;
7896         MarkTargetSquares(1);
7897         DrawPosition(TRUE, boards[currentMove]);
7898         return;
7899     }
7900
7901     // off-board moves should not be highlighted
7902     if(x < 0 || y < 0) {
7903         ClearHighlights();
7904         DrawPosition(FALSE, NULL);
7905     } else ReportClick("put", x, y);
7906
7907     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7908  }
7909
7910     if(legal[toY][toX] == 2) { // highlight-induced promotion
7911         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7912         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7913     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7914       if(!*promoRestrict) {           // but has not done that yet
7915         deferChoice = TRUE;           // set up retry for when it does
7916         return;                       // and wait for that
7917       }
7918       promoChoice = ToLower(*promoRestrict); // force engine's choice
7919       deferChoice = FALSE;
7920     }
7921
7922     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7923         SetHighlights(fromX, fromY, toX, toY);
7924         MarkTargetSquares(1);
7925         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7926             // [HGM] super: promotion to captured piece selected from holdings
7927             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7928             promotionChoice = TRUE;
7929             // kludge follows to temporarily execute move on display, without promoting yet
7930             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7931             boards[currentMove][toY][toX] = p;
7932             DrawPosition(FALSE, boards[currentMove]);
7933             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7934             boards[currentMove][toY][toX] = q;
7935             DisplayMessage("Click in holdings to choose piece", "");
7936             return;
7937         }
7938         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7939         PromotionPopUp(promoChoice);
7940     } else {
7941         int oldMove = currentMove;
7942         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7943         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7944         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7945         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7946         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7947            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7948             DrawPosition(TRUE, boards[currentMove]);
7949         else DrawPosition(FALSE, NULL);
7950         fromX = fromY = -1;
7951         flashing = 0;
7952     }
7953     appData.animate = saveAnimate;
7954     if (appData.animate || appData.animateDragging) {
7955         /* Undo animation damage if needed */
7956 //      DrawPosition(FALSE, NULL);
7957     }
7958 }
7959
7960 int
7961 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7962 {   // front-end-free part taken out of PieceMenuPopup
7963     int whichMenu; int xSqr, ySqr;
7964
7965     if(seekGraphUp) { // [HGM] seekgraph
7966         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7967         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7968         return -2;
7969     }
7970
7971     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7972          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7973         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7974         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7975         if(action == Press)   {
7976             originalFlip = flipView;
7977             flipView = !flipView; // temporarily flip board to see game from partners perspective
7978             DrawPosition(TRUE, partnerBoard);
7979             DisplayMessage(partnerStatus, "");
7980             partnerUp = TRUE;
7981         } else if(action == Release) {
7982             flipView = originalFlip;
7983             DrawPosition(TRUE, boards[currentMove]);
7984             partnerUp = FALSE;
7985         }
7986         return -2;
7987     }
7988
7989     xSqr = EventToSquare(x, BOARD_WIDTH);
7990     ySqr = EventToSquare(y, BOARD_HEIGHT);
7991     if (action == Release) {
7992         if(pieceSweep != EmptySquare) {
7993             EditPositionMenuEvent(pieceSweep, toX, toY);
7994             pieceSweep = EmptySquare;
7995         } else UnLoadPV(); // [HGM] pv
7996     }
7997     if (action != Press) return -2; // return code to be ignored
7998     switch (gameMode) {
7999       case IcsExamining:
8000         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8001       case EditPosition:
8002         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8003         if (xSqr < 0 || ySqr < 0) return -1;
8004         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8005         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8006         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
8007         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
8008         NextPiece(0);
8009         return 2; // grab
8010       case IcsObserving:
8011         if(!appData.icsEngineAnalyze) return -1;
8012       case IcsPlayingWhite:
8013       case IcsPlayingBlack:
8014         if(!appData.zippyPlay) goto noZip;
8015       case AnalyzeMode:
8016       case AnalyzeFile:
8017       case MachinePlaysWhite:
8018       case MachinePlaysBlack:
8019       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8020         if (!appData.dropMenu) {
8021           LoadPV(x, y);
8022           return 2; // flag front-end to grab mouse events
8023         }
8024         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8025            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8026       case EditGame:
8027       noZip:
8028         if (xSqr < 0 || ySqr < 0) return -1;
8029         if (!appData.dropMenu || appData.testLegality &&
8030             gameInfo.variant != VariantBughouse &&
8031             gameInfo.variant != VariantCrazyhouse) return -1;
8032         whichMenu = 1; // drop menu
8033         break;
8034       default:
8035         return -1;
8036     }
8037
8038     if (((*fromX = xSqr) < 0) ||
8039         ((*fromY = ySqr) < 0)) {
8040         *fromX = *fromY = -1;
8041         return -1;
8042     }
8043     if (flipView)
8044       *fromX = BOARD_WIDTH - 1 - *fromX;
8045     else
8046       *fromY = BOARD_HEIGHT - 1 - *fromY;
8047
8048     return whichMenu;
8049 }
8050
8051 void
8052 Wheel (int dir, int x, int y)
8053 {
8054     if(gameMode == EditPosition) {
8055         int xSqr = EventToSquare(x, BOARD_WIDTH);
8056         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8057         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8058         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8059         do {
8060             boards[currentMove][ySqr][xSqr] += dir;
8061             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8062             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8063         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8064         DrawPosition(FALSE, boards[currentMove]);
8065     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8066 }
8067
8068 void
8069 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8070 {
8071 //    char * hint = lastHint;
8072     FrontEndProgramStats stats;
8073
8074     stats.which = cps == &first ? 0 : 1;
8075     stats.depth = cpstats->depth;
8076     stats.nodes = cpstats->nodes;
8077     stats.score = cpstats->score;
8078     stats.time = cpstats->time;
8079     stats.pv = cpstats->movelist;
8080     stats.hint = lastHint;
8081     stats.an_move_index = 0;
8082     stats.an_move_count = 0;
8083
8084     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8085         stats.hint = cpstats->move_name;
8086         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8087         stats.an_move_count = cpstats->nr_moves;
8088     }
8089
8090     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
8091
8092     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8093         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8094
8095     SetProgramStats( &stats );
8096 }
8097
8098 void
8099 ClearEngineOutputPane (int which)
8100 {
8101     static FrontEndProgramStats dummyStats;
8102     dummyStats.which = which;
8103     dummyStats.pv = "#";
8104     SetProgramStats( &dummyStats );
8105 }
8106
8107 #define MAXPLAYERS 500
8108
8109 char *
8110 TourneyStandings (int display)
8111 {
8112     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8113     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8114     char result, *p, *names[MAXPLAYERS];
8115
8116     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8117         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8118     names[0] = p = strdup(appData.participants);
8119     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8120
8121     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8122
8123     while(result = appData.results[nr]) {
8124         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8125         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8126         wScore = bScore = 0;
8127         switch(result) {
8128           case '+': wScore = 2; break;
8129           case '-': bScore = 2; break;
8130           case '=': wScore = bScore = 1; break;
8131           case ' ':
8132           case '*': return strdup("busy"); // tourney not finished
8133         }
8134         score[w] += wScore;
8135         score[b] += bScore;
8136         games[w]++;
8137         games[b]++;
8138         nr++;
8139     }
8140     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8141     for(w=0; w<nPlayers; w++) {
8142         bScore = -1;
8143         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8144         ranking[w] = b; points[w] = bScore; score[b] = -2;
8145     }
8146     p = malloc(nPlayers*34+1);
8147     for(w=0; w<nPlayers && w<display; w++)
8148         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8149     free(names[0]);
8150     return p;
8151 }
8152
8153 void
8154 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8155 {       // count all piece types
8156         int p, f, r;
8157         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8158         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8159         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8160                 p = board[r][f];
8161                 pCnt[p]++;
8162                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8163                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8164                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8165                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8166                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8167                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8168         }
8169 }
8170
8171 int
8172 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8173 {
8174         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8175         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8176
8177         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8178         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8179         if(myPawns == 2 && nMine == 3) // KPP
8180             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8181         if(myPawns == 1 && nMine == 2) // KP
8182             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8183         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8184             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8185         if(myPawns) return FALSE;
8186         if(pCnt[WhiteRook+side])
8187             return pCnt[BlackRook-side] ||
8188                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8189                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8190                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8191         if(pCnt[WhiteCannon+side]) {
8192             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8193             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8194         }
8195         if(pCnt[WhiteKnight+side])
8196             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8197         return FALSE;
8198 }
8199
8200 int
8201 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8202 {
8203         VariantClass v = gameInfo.variant;
8204
8205         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8206         if(v == VariantShatranj) return TRUE; // always winnable through baring
8207         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8208         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8209
8210         if(v == VariantXiangqi) {
8211                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8212
8213                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8214                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8215                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8216                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8217                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8218                 if(stale) // we have at least one last-rank P plus perhaps C
8219                     return majors // KPKX
8220                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8221                 else // KCA*E*
8222                     return pCnt[WhiteFerz+side] // KCAK
8223                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8224                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8225                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8226
8227         } else if(v == VariantKnightmate) {
8228                 if(nMine == 1) return FALSE;
8229                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8230         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8231                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8232
8233                 if(nMine == 1) return FALSE; // bare King
8234                 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
8235                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8236                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8237                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8238                 if(pCnt[WhiteKnight+side])
8239                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8240                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8241                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8242                 if(nBishops)
8243                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8244                 if(pCnt[WhiteAlfil+side])
8245                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8246                 if(pCnt[WhiteWazir+side])
8247                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8248         }
8249
8250         return TRUE;
8251 }
8252
8253 int
8254 CompareWithRights (Board b1, Board b2)
8255 {
8256     int rights = 0;
8257     if(!CompareBoards(b1, b2)) return FALSE;
8258     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8259     /* compare castling rights */
8260     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8261            rights++; /* King lost rights, while rook still had them */
8262     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8263         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8264            rights++; /* but at least one rook lost them */
8265     }
8266     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8267            rights++;
8268     if( b1[CASTLING][5] != NoRights ) {
8269         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8270            rights++;
8271     }
8272     return rights == 0;
8273 }
8274
8275 int
8276 Adjudicate (ChessProgramState *cps)
8277 {       // [HGM] some adjudications useful with buggy engines
8278         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8279         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8280         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8281         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8282         int k, drop, count = 0; static int bare = 1;
8283         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8284         Boolean canAdjudicate = !appData.icsActive;
8285
8286         // most tests only when we understand the game, i.e. legality-checking on
8287             if( appData.testLegality )
8288             {   /* [HGM] Some more adjudications for obstinate engines */
8289                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8290                 static int moveCount = 6;
8291                 ChessMove result;
8292                 char *reason = NULL;
8293
8294                 /* Count what is on board. */
8295                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8296
8297                 /* Some material-based adjudications that have to be made before stalemate test */
8298                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8299                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8300                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8301                      if(canAdjudicate && appData.checkMates) {
8302                          if(engineOpponent)
8303                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8304                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8305                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8306                          return 1;
8307                      }
8308                 }
8309
8310                 /* Bare King in Shatranj (loses) or Losers (wins) */
8311                 if( nrW == 1 || nrB == 1) {
8312                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8313                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8314                      if(canAdjudicate && appData.checkMates) {
8315                          if(engineOpponent)
8316                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8317                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8318                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8319                          return 1;
8320                      }
8321                   } else
8322                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8323                   {    /* bare King */
8324                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8325                         if(canAdjudicate && appData.checkMates) {
8326                             /* but only adjudicate if adjudication enabled */
8327                             if(engineOpponent)
8328                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8329                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8330                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8331                             return 1;
8332                         }
8333                   }
8334                 } else bare = 1;
8335
8336
8337             // don't wait for engine to announce game end if we can judge ourselves
8338             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8339               case MT_CHECK:
8340                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8341                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8342                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8343                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8344                             checkCnt++;
8345                         if(checkCnt >= 2) {
8346                             reason = "Xboard adjudication: 3rd check";
8347                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8348                             break;
8349                         }
8350                     }
8351                 }
8352               case MT_NONE:
8353               default:
8354                 break;
8355               case MT_STEALMATE:
8356               case MT_STALEMATE:
8357               case MT_STAINMATE:
8358                 reason = "Xboard adjudication: Stalemate";
8359                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8360                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8361                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8362                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8363                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8364                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8365                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8366                                                                         EP_CHECKMATE : EP_WINS);
8367                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8368                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8369                 }
8370                 break;
8371               case MT_CHECKMATE:
8372                 reason = "Xboard adjudication: Checkmate";
8373                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8374                 if(gameInfo.variant == VariantShogi) {
8375                     if(forwardMostMove > backwardMostMove
8376                        && moveList[forwardMostMove-1][1] == '@'
8377                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8378                         reason = "XBoard adjudication: pawn-drop mate";
8379                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8380                     }
8381                 }
8382                 break;
8383             }
8384
8385                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8386                     case EP_STALEMATE:
8387                         result = GameIsDrawn; break;
8388                     case EP_CHECKMATE:
8389                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8390                     case EP_WINS:
8391                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8392                     default:
8393                         result = EndOfFile;
8394                 }
8395                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8396                     if(engineOpponent)
8397                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8398                     GameEnds( result, reason, GE_XBOARD );
8399                     return 1;
8400                 }
8401
8402                 /* Next absolutely insufficient mating material. */
8403                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8404                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8405                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8406
8407                      /* always flag draws, for judging claims */
8408                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8409
8410                      if(canAdjudicate && appData.materialDraws) {
8411                          /* but only adjudicate them if adjudication enabled */
8412                          if(engineOpponent) {
8413                            SendToProgram("force\n", engineOpponent); // suppress reply
8414                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8415                          }
8416                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8417                          return 1;
8418                      }
8419                 }
8420
8421                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8422                 if(gameInfo.variant == VariantXiangqi ?
8423                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8424                  : nrW + nrB == 4 &&
8425                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8426                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8427                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8428                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8429                    ) ) {
8430                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8431                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8432                           if(engineOpponent) {
8433                             SendToProgram("force\n", engineOpponent); // suppress reply
8434                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8435                           }
8436                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8437                           return 1;
8438                      }
8439                 } else moveCount = 6;
8440             }
8441
8442         // Repetition draws and 50-move rule can be applied independently of legality testing
8443
8444                 /* Check for rep-draws */
8445                 count = 0;
8446                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8447                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8448                 for(k = forwardMostMove-2;
8449                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8450                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8451                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8452                     k-=2)
8453                 {   int rights=0;
8454                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8455                         /* compare castling rights */
8456                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8457                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8458                                 rights++; /* King lost rights, while rook still had them */
8459                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8460                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8461                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8462                                    rights++; /* but at least one rook lost them */
8463                         }
8464                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8465                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8466                                 rights++;
8467                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8468                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8469                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8470                                    rights++;
8471                         }
8472                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8473                             && appData.drawRepeats > 1) {
8474                              /* adjudicate after user-specified nr of repeats */
8475                              int result = GameIsDrawn;
8476                              char *details = "XBoard adjudication: repetition draw";
8477                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8478                                 // [HGM] xiangqi: check for forbidden perpetuals
8479                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8480                                 for(m=forwardMostMove; m>k; m-=2) {
8481                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8482                                         ourPerpetual = 0; // the current mover did not always check
8483                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8484                                         hisPerpetual = 0; // the opponent did not always check
8485                                 }
8486                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8487                                                                         ourPerpetual, hisPerpetual);
8488                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8489                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8490                                     details = "Xboard adjudication: perpetual checking";
8491                                 } else
8492                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8493                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8494                                 } else
8495                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8496                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8497                                         result = BlackWins;
8498                                         details = "Xboard adjudication: repetition";
8499                                     }
8500                                 } else // it must be XQ
8501                                 // Now check for perpetual chases
8502                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8503                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8504                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8505                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8506                                         static char resdet[MSG_SIZ];
8507                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8508                                         details = resdet;
8509                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8510                                     } else
8511                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8512                                         break; // Abort repetition-checking loop.
8513                                 }
8514                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8515                              }
8516                              if(engineOpponent) {
8517                                SendToProgram("force\n", engineOpponent); // suppress reply
8518                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8519                              }
8520                              GameEnds( result, details, GE_XBOARD );
8521                              return 1;
8522                         }
8523                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8524                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8525                     }
8526                 }
8527
8528                 /* Now we test for 50-move draws. Determine ply count */
8529                 count = forwardMostMove;
8530                 /* look for last irreversble move */
8531                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8532                     count--;
8533                 /* if we hit starting position, add initial plies */
8534                 if( count == backwardMostMove )
8535                     count -= initialRulePlies;
8536                 count = forwardMostMove - count;
8537                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8538                         // adjust reversible move counter for checks in Xiangqi
8539                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8540                         if(i < backwardMostMove) i = backwardMostMove;
8541                         while(i <= forwardMostMove) {
8542                                 lastCheck = inCheck; // check evasion does not count
8543                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8544                                 if(inCheck || lastCheck) count--; // check does not count
8545                                 i++;
8546                         }
8547                 }
8548                 if( count >= 100)
8549                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8550                          /* this is used to judge if draw claims are legal */
8551                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8552                          if(engineOpponent) {
8553                            SendToProgram("force\n", engineOpponent); // suppress reply
8554                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8555                          }
8556                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8557                          return 1;
8558                 }
8559
8560                 /* if draw offer is pending, treat it as a draw claim
8561                  * when draw condition present, to allow engines a way to
8562                  * claim draws before making their move to avoid a race
8563                  * condition occurring after their move
8564                  */
8565                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8566                          char *p = NULL;
8567                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8568                              p = "Draw claim: 50-move rule";
8569                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8570                              p = "Draw claim: 3-fold repetition";
8571                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8572                              p = "Draw claim: insufficient mating material";
8573                          if( p != NULL && canAdjudicate) {
8574                              if(engineOpponent) {
8575                                SendToProgram("force\n", engineOpponent); // suppress reply
8576                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8577                              }
8578                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8579                              return 1;
8580                          }
8581                 }
8582
8583                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8584                     if(engineOpponent) {
8585                       SendToProgram("force\n", engineOpponent); // suppress reply
8586                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8587                     }
8588                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8589                     return 1;
8590                 }
8591         return 0;
8592 }
8593
8594 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8595 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8596 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8597
8598 static int
8599 BitbaseProbe ()
8600 {
8601     int pieces[10], squares[10], cnt=0, r, f, res;
8602     static int loaded;
8603     static PPROBE_EGBB probeBB;
8604     if(!appData.testLegality) return 10;
8605     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8606     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8607     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8608     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8609         ChessSquare piece = boards[forwardMostMove][r][f];
8610         int black = (piece >= BlackPawn);
8611         int type = piece - black*BlackPawn;
8612         if(piece == EmptySquare) continue;
8613         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8614         if(type == WhiteKing) type = WhiteQueen + 1;
8615         type = egbbCode[type];
8616         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8617         pieces[cnt] = type + black*6;
8618         if(++cnt > 5) return 11;
8619     }
8620     pieces[cnt] = squares[cnt] = 0;
8621     // probe EGBB
8622     if(loaded == 2) return 13; // loading failed before
8623     if(loaded == 0) {
8624         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8625         HMODULE lib;
8626         PLOAD_EGBB loadBB;
8627         loaded = 2; // prepare for failure
8628         if(!path) return 13; // no egbb installed
8629         strncpy(buf, path + 8, MSG_SIZ);
8630         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8631         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8632         lib = LoadLibrary(buf);
8633         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8634         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8635         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8636         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8637         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8638         loaded = 1; // success!
8639     }
8640     res = probeBB(forwardMostMove & 1, pieces, squares);
8641     return res > 0 ? 1 : res < 0 ? -1 : 0;
8642 }
8643
8644 char *
8645 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8646 {   // [HGM] book: this routine intercepts moves to simulate book replies
8647     char *bookHit = NULL;
8648
8649     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8650         char buf[MSG_SIZ];
8651         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8652         SendToProgram(buf, cps);
8653     }
8654     //first determine if the incoming move brings opponent into his book
8655     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8656         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8657     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8658     if(bookHit != NULL && !cps->bookSuspend) {
8659         // make sure opponent is not going to reply after receiving move to book position
8660         SendToProgram("force\n", cps);
8661         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8662     }
8663     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8664     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8665     // now arrange restart after book miss
8666     if(bookHit) {
8667         // after a book hit we never send 'go', and the code after the call to this routine
8668         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8669         char buf[MSG_SIZ], *move = bookHit;
8670         if(cps->useSAN) {
8671             int fromX, fromY, toX, toY;
8672             char promoChar;
8673             ChessMove moveType;
8674             move = buf + 30;
8675             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8676                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8677                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8678                                     PosFlags(forwardMostMove),
8679                                     fromY, fromX, toY, toX, promoChar, move);
8680             } else {
8681                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8682                 bookHit = NULL;
8683             }
8684         }
8685         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8686         SendToProgram(buf, cps);
8687         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8688     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8689         SendToProgram("go\n", cps);
8690         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8691     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8692         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8693             SendToProgram("go\n", cps);
8694         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8695     }
8696     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8697 }
8698
8699 int
8700 LoadError (char *errmess, ChessProgramState *cps)
8701 {   // unloads engine and switches back to -ncp mode if it was first
8702     if(cps->initDone) return FALSE;
8703     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8704     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8705     cps->pr = NoProc;
8706     if(cps == &first) {
8707         appData.noChessProgram = TRUE;
8708         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8709         gameMode = BeginningOfGame; ModeHighlight();
8710         SetNCPMode();
8711     }
8712     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8713     DisplayMessage("", ""); // erase waiting message
8714     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8715     return TRUE;
8716 }
8717
8718 char *savedMessage;
8719 ChessProgramState *savedState;
8720 void
8721 DeferredBookMove (void)
8722 {
8723         if(savedState->lastPing != savedState->lastPong)
8724                     ScheduleDelayedEvent(DeferredBookMove, 10);
8725         else
8726         HandleMachineMove(savedMessage, savedState);
8727 }
8728
8729 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8730 static ChessProgramState *stalledEngine;
8731 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8732
8733 void
8734 HandleMachineMove (char *message, ChessProgramState *cps)
8735 {
8736     static char firstLeg[20], legs;
8737     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8738     char realname[MSG_SIZ];
8739     int fromX, fromY, toX, toY;
8740     ChessMove moveType;
8741     char promoChar, roar;
8742     char *p, *pv=buf1;
8743     int oldError;
8744     char *bookHit;
8745
8746     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8747         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8748         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8749             DisplayError(_("Invalid pairing from pairing engine"), 0);
8750             return;
8751         }
8752         pairingReceived = 1;
8753         NextMatchGame();
8754         return; // Skim the pairing messages here.
8755     }
8756
8757     oldError = cps->userError; cps->userError = 0;
8758
8759 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8760     /*
8761      * Kludge to ignore BEL characters
8762      */
8763     while (*message == '\007') message++;
8764
8765     /*
8766      * [HGM] engine debug message: ignore lines starting with '#' character
8767      */
8768     if(cps->debug && *message == '#') return;
8769
8770     /*
8771      * Look for book output
8772      */
8773     if (cps == &first && bookRequested) {
8774         if (message[0] == '\t' || message[0] == ' ') {
8775             /* Part of the book output is here; append it */
8776             strcat(bookOutput, message);
8777             strcat(bookOutput, "  \n");
8778             return;
8779         } else if (bookOutput[0] != NULLCHAR) {
8780             /* All of book output has arrived; display it */
8781             char *p = bookOutput;
8782             while (*p != NULLCHAR) {
8783                 if (*p == '\t') *p = ' ';
8784                 p++;
8785             }
8786             DisplayInformation(bookOutput);
8787             bookRequested = FALSE;
8788             /* Fall through to parse the current output */
8789         }
8790     }
8791
8792     /*
8793      * Look for machine move.
8794      */
8795     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8796         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8797     {
8798         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8799             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8800             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8801             stalledEngine = cps;
8802             if(appData.ponderNextMove) { // bring opponent out of ponder
8803                 if(gameMode == TwoMachinesPlay) {
8804                     if(cps->other->pause)
8805                         PauseEngine(cps->other);
8806                     else
8807                         SendToProgram("easy\n", cps->other);
8808                 }
8809             }
8810             StopClocks();
8811             return;
8812         }
8813
8814       if(cps->usePing) {
8815
8816         /* This method is only useful on engines that support ping */
8817         if(abortEngineThink) {
8818             if (appData.debugMode) {
8819                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8820             }
8821             SendToProgram("undo\n", cps);
8822             return;
8823         }
8824
8825         if (cps->lastPing != cps->lastPong) {
8826             /* Extra move from before last new; ignore */
8827             if (appData.debugMode) {
8828                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8829             }
8830           return;
8831         }
8832
8833       } else {
8834
8835         int machineWhite = FALSE;
8836
8837         switch (gameMode) {
8838           case BeginningOfGame:
8839             /* Extra move from before last reset; ignore */
8840             if (appData.debugMode) {
8841                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8842             }
8843             return;
8844
8845           case EndOfGame:
8846           case IcsIdle:
8847           default:
8848             /* Extra move after we tried to stop.  The mode test is
8849                not a reliable way of detecting this problem, but it's
8850                the best we can do on engines that don't support ping.
8851             */
8852             if (appData.debugMode) {
8853                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8854                         cps->which, gameMode);
8855             }
8856             SendToProgram("undo\n", cps);
8857             return;
8858
8859           case MachinePlaysWhite:
8860           case IcsPlayingWhite:
8861             machineWhite = TRUE;
8862             break;
8863
8864           case MachinePlaysBlack:
8865           case IcsPlayingBlack:
8866             machineWhite = FALSE;
8867             break;
8868
8869           case TwoMachinesPlay:
8870             machineWhite = (cps->twoMachinesColor[0] == 'w');
8871             break;
8872         }
8873         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8874             if (appData.debugMode) {
8875                 fprintf(debugFP,
8876                         "Ignoring move out of turn by %s, gameMode %d"
8877                         ", forwardMost %d\n",
8878                         cps->which, gameMode, forwardMostMove);
8879             }
8880             return;
8881         }
8882       }
8883
8884         if(cps->alphaRank) AlphaRank(machineMove, 4);
8885
8886         // [HGM] lion: (some very limited) support for Alien protocol
8887         killX = killY = kill2X = kill2Y = -1;
8888         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8889             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8890             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8891             return;
8892         }
8893         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8894             char *q = strchr(p+1, ',');            // second comma?
8895             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8896             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8897             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8898         }
8899         if(firstLeg[0]) { // there was a previous leg;
8900             // only support case where same piece makes two step
8901             char buf[20], *p = machineMove+1, *q = buf+1, f;
8902             safeStrCpy(buf, machineMove, 20);
8903             while(isdigit(*q)) q++; // find start of to-square
8904             safeStrCpy(machineMove, firstLeg, 20);
8905             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8906             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
8907             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)
8908             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8909             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8910             firstLeg[0] = NULLCHAR; legs = 0;
8911         }
8912
8913         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8914                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8915             /* Machine move could not be parsed; ignore it. */
8916           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8917                     machineMove, _(cps->which));
8918             DisplayMoveError(buf1);
8919             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8920                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8921             if (gameMode == TwoMachinesPlay) {
8922               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8923                        buf1, GE_XBOARD);
8924             }
8925             return;
8926         }
8927
8928         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8929         /* So we have to redo legality test with true e.p. status here,  */
8930         /* to make sure an illegal e.p. capture does not slip through,   */
8931         /* to cause a forfeit on a justified illegal-move complaint      */
8932         /* of the opponent.                                              */
8933         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8934            ChessMove moveType;
8935            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8936                              fromY, fromX, toY, toX, promoChar);
8937             if(moveType == IllegalMove) {
8938               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8939                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8940                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8941                            buf1, GE_XBOARD);
8942                 return;
8943            } else if(!appData.fischerCastling)
8944            /* [HGM] Kludge to handle engines that send FRC-style castling
8945               when they shouldn't (like TSCP-Gothic) */
8946            switch(moveType) {
8947              case WhiteASideCastleFR:
8948              case BlackASideCastleFR:
8949                toX+=2;
8950                currentMoveString[2]++;
8951                break;
8952              case WhiteHSideCastleFR:
8953              case BlackHSideCastleFR:
8954                toX--;
8955                currentMoveString[2]--;
8956                break;
8957              default: ; // nothing to do, but suppresses warning of pedantic compilers
8958            }
8959         }
8960         hintRequested = FALSE;
8961         lastHint[0] = NULLCHAR;
8962         bookRequested = FALSE;
8963         /* Program may be pondering now */
8964         cps->maybeThinking = TRUE;
8965         if (cps->sendTime == 2) cps->sendTime = 1;
8966         if (cps->offeredDraw) cps->offeredDraw--;
8967
8968         /* [AS] Save move info*/
8969         pvInfoList[ forwardMostMove ].score = programStats.score;
8970         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8971         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8972
8973         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8974
8975         /* Test suites abort the 'game' after one move */
8976         if(*appData.finger) {
8977            static FILE *f;
8978            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8979            if(!f) f = fopen(appData.finger, "w");
8980            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8981            else { DisplayFatalError("Bad output file", errno, 0); return; }
8982            free(fen);
8983            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8984         }
8985         if(appData.epd) {
8986            if(solvingTime >= 0) {
8987               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8988               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8989            } else {
8990               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8991               if(solvingTime == -2) second.matchWins++;
8992            }
8993            OutputKibitz(2, buf1);
8994            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8995         }
8996
8997         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8998         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8999             int count = 0;
9000
9001             while( count < adjudicateLossPlies ) {
9002                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9003
9004                 if( count & 1 ) {
9005                     score = -score; /* Flip score for winning side */
9006                 }
9007
9008                 if( score > appData.adjudicateLossThreshold ) {
9009                     break;
9010                 }
9011
9012                 count++;
9013             }
9014
9015             if( count >= adjudicateLossPlies ) {
9016                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9017
9018                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9019                     "Xboard adjudication",
9020                     GE_XBOARD );
9021
9022                 return;
9023             }
9024         }
9025
9026         if(Adjudicate(cps)) {
9027             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9028             return; // [HGM] adjudicate: for all automatic game ends
9029         }
9030
9031 #if ZIPPY
9032         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9033             first.initDone) {
9034           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9035                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9036                 SendToICS("draw ");
9037                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9038           }
9039           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9040           ics_user_moved = 1;
9041           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9042                 char buf[3*MSG_SIZ];
9043
9044                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9045                         programStats.score / 100.,
9046                         programStats.depth,
9047                         programStats.time / 100.,
9048                         (unsigned int)programStats.nodes,
9049                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9050                         programStats.movelist);
9051                 SendToICS(buf);
9052           }
9053         }
9054 #endif
9055
9056         /* [AS] Clear stats for next move */
9057         ClearProgramStats();
9058         thinkOutput[0] = NULLCHAR;
9059         hiddenThinkOutputState = 0;
9060
9061         bookHit = NULL;
9062         if (gameMode == TwoMachinesPlay) {
9063             /* [HGM] relaying draw offers moved to after reception of move */
9064             /* and interpreting offer as claim if it brings draw condition */
9065             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9066                 SendToProgram("draw\n", cps->other);
9067             }
9068             if (cps->other->sendTime) {
9069                 SendTimeRemaining(cps->other,
9070                                   cps->other->twoMachinesColor[0] == 'w');
9071             }
9072             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9073             if (firstMove && !bookHit) {
9074                 firstMove = FALSE;
9075                 if (cps->other->useColors) {
9076                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9077                 }
9078                 SendToProgram("go\n", cps->other);
9079             }
9080             cps->other->maybeThinking = TRUE;
9081         }
9082
9083         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9084
9085         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9086
9087         if (!pausing && appData.ringBellAfterMoves) {
9088             if(!roar) RingBell();
9089         }
9090
9091         /*
9092          * Reenable menu items that were disabled while
9093          * machine was thinking
9094          */
9095         if (gameMode != TwoMachinesPlay)
9096             SetUserThinkingEnables();
9097
9098         // [HGM] book: after book hit opponent has received move and is now in force mode
9099         // force the book reply into it, and then fake that it outputted this move by jumping
9100         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9101         if(bookHit) {
9102                 static char bookMove[MSG_SIZ]; // a bit generous?
9103
9104                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9105                 strcat(bookMove, bookHit);
9106                 message = bookMove;
9107                 cps = cps->other;
9108                 programStats.nodes = programStats.depth = programStats.time =
9109                 programStats.score = programStats.got_only_move = 0;
9110                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9111
9112                 if(cps->lastPing != cps->lastPong) {
9113                     savedMessage = message; // args for deferred call
9114                     savedState = cps;
9115                     ScheduleDelayedEvent(DeferredBookMove, 10);
9116                     return;
9117                 }
9118                 goto FakeBookMove;
9119         }
9120
9121         return;
9122     }
9123
9124     /* Set special modes for chess engines.  Later something general
9125      *  could be added here; for now there is just one kludge feature,
9126      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9127      *  when "xboard" is given as an interactive command.
9128      */
9129     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9130         cps->useSigint = FALSE;
9131         cps->useSigterm = FALSE;
9132     }
9133     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9134       ParseFeatures(message+8, cps);
9135       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9136     }
9137
9138     if (!strncmp(message, "setup ", 6) && 
9139         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9140           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9141                                         ) { // [HGM] allow first engine to define opening position
9142       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9143       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9144       *buf = NULLCHAR;
9145       if(sscanf(message, "setup (%s", buf) == 1) {
9146         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9147         ASSIGN(appData.pieceToCharTable, buf);
9148       }
9149       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9150       if(dummy >= 3) {
9151         while(message[s] && message[s++] != ' ');
9152         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9153            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9154             if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9155             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9156             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9157           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9158           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9159           startedFromSetupPosition = FALSE;
9160         }
9161       }
9162       if(startedFromSetupPosition) return;
9163       ParseFEN(boards[0], &dummy, message+s, FALSE);
9164       DrawPosition(TRUE, boards[0]);
9165       CopyBoard(initialPosition, boards[0]);
9166       startedFromSetupPosition = TRUE;
9167       return;
9168     }
9169     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9170       ChessSquare piece = WhitePawn;
9171       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9172       if(*p == '+') promoted++, ID = *++p;
9173       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9174       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9175       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9176       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9177       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9178       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9179       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9180       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9181                                                && gameInfo.variant != VariantGreat
9182                                                && gameInfo.variant != VariantFairy    ) return;
9183       if(piece < EmptySquare) {
9184         pieceDefs = TRUE;
9185         ASSIGN(pieceDesc[piece], buf1);
9186         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9187       }
9188       return;
9189     }
9190     if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9191       if(deferChoice) {
9192         LeftClick(Press, 0, 0); // finish the click that was interrupted
9193       } else if(promoSweep != EmptySquare) {
9194         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9195         if(strlen(promoRestrict) > 1) Sweep(0);
9196       }
9197       return;
9198     }
9199     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9200      * want this, I was asked to put it in, and obliged.
9201      */
9202     if (!strncmp(message, "setboard ", 9)) {
9203         Board initial_position;
9204
9205         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9206
9207         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9208             DisplayError(_("Bad FEN received from engine"), 0);
9209             return ;
9210         } else {
9211            Reset(TRUE, FALSE);
9212            CopyBoard(boards[0], initial_position);
9213            initialRulePlies = FENrulePlies;
9214            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9215            else gameMode = MachinePlaysBlack;
9216            DrawPosition(FALSE, boards[currentMove]);
9217         }
9218         return;
9219     }
9220
9221     /*
9222      * Look for communication commands
9223      */
9224     if (!strncmp(message, "telluser ", 9)) {
9225         if(message[9] == '\\' && message[10] == '\\')
9226             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9227         PlayTellSound();
9228         DisplayNote(message + 9);
9229         return;
9230     }
9231     if (!strncmp(message, "tellusererror ", 14)) {
9232         cps->userError = 1;
9233         if(message[14] == '\\' && message[15] == '\\')
9234             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9235         PlayTellSound();
9236         DisplayError(message + 14, 0);
9237         return;
9238     }
9239     if (!strncmp(message, "tellopponent ", 13)) {
9240       if (appData.icsActive) {
9241         if (loggedOn) {
9242           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9243           SendToICS(buf1);
9244         }
9245       } else {
9246         DisplayNote(message + 13);
9247       }
9248       return;
9249     }
9250     if (!strncmp(message, "tellothers ", 11)) {
9251       if (appData.icsActive) {
9252         if (loggedOn) {
9253           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9254           SendToICS(buf1);
9255         }
9256       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9257       return;
9258     }
9259     if (!strncmp(message, "tellall ", 8)) {
9260       if (appData.icsActive) {
9261         if (loggedOn) {
9262           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9263           SendToICS(buf1);
9264         }
9265       } else {
9266         DisplayNote(message + 8);
9267       }
9268       return;
9269     }
9270     if (strncmp(message, "warning", 7) == 0) {
9271         /* Undocumented feature, use tellusererror in new code */
9272         DisplayError(message, 0);
9273         return;
9274     }
9275     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9276         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9277         strcat(realname, " query");
9278         AskQuestion(realname, buf2, buf1, cps->pr);
9279         return;
9280     }
9281     /* Commands from the engine directly to ICS.  We don't allow these to be
9282      *  sent until we are logged on. Crafty kibitzes have been known to
9283      *  interfere with the login process.
9284      */
9285     if (loggedOn) {
9286         if (!strncmp(message, "tellics ", 8)) {
9287             SendToICS(message + 8);
9288             SendToICS("\n");
9289             return;
9290         }
9291         if (!strncmp(message, "tellicsnoalias ", 15)) {
9292             SendToICS(ics_prefix);
9293             SendToICS(message + 15);
9294             SendToICS("\n");
9295             return;
9296         }
9297         /* The following are for backward compatibility only */
9298         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9299             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9300             SendToICS(ics_prefix);
9301             SendToICS(message);
9302             SendToICS("\n");
9303             return;
9304         }
9305     }
9306     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9307         if(initPing == cps->lastPong) {
9308             if(gameInfo.variant == VariantUnknown) {
9309                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9310                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9311                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9312             }
9313             initPing = -1;
9314         }
9315         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9316             abortEngineThink = FALSE;
9317             DisplayMessage("", "");
9318             ThawUI();
9319         }
9320         return;
9321     }
9322     if(!strncmp(message, "highlight ", 10)) {
9323         if(appData.testLegality && !*engineVariant && appData.markers) return;
9324         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9325         return;
9326     }
9327     if(!strncmp(message, "click ", 6)) {
9328         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9329         if(appData.testLegality || !appData.oneClick) return;
9330         sscanf(message+6, "%c%d%c", &f, &y, &c);
9331         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9332         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9333         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9334         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9335         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9336         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9337             LeftClick(Release, lastLeftX, lastLeftY);
9338         controlKey  = (c == ',');
9339         LeftClick(Press, x, y);
9340         LeftClick(Release, x, y);
9341         first.highlight = f;
9342         return;
9343     }
9344     /*
9345      * If the move is illegal, cancel it and redraw the board.
9346      * Also deal with other error cases.  Matching is rather loose
9347      * here to accommodate engines written before the spec.
9348      */
9349     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9350         strncmp(message, "Error", 5) == 0) {
9351         if (StrStr(message, "name") ||
9352             StrStr(message, "rating") || StrStr(message, "?") ||
9353             StrStr(message, "result") || StrStr(message, "board") ||
9354             StrStr(message, "bk") || StrStr(message, "computer") ||
9355             StrStr(message, "variant") || StrStr(message, "hint") ||
9356             StrStr(message, "random") || StrStr(message, "depth") ||
9357             StrStr(message, "accepted")) {
9358             return;
9359         }
9360         if (StrStr(message, "protover")) {
9361           /* Program is responding to input, so it's apparently done
9362              initializing, and this error message indicates it is
9363              protocol version 1.  So we don't need to wait any longer
9364              for it to initialize and send feature commands. */
9365           FeatureDone(cps, 1);
9366           cps->protocolVersion = 1;
9367           return;
9368         }
9369         cps->maybeThinking = FALSE;
9370
9371         if (StrStr(message, "draw")) {
9372             /* Program doesn't have "draw" command */
9373             cps->sendDrawOffers = 0;
9374             return;
9375         }
9376         if (cps->sendTime != 1 &&
9377             (StrStr(message, "time") || StrStr(message, "otim"))) {
9378           /* Program apparently doesn't have "time" or "otim" command */
9379           cps->sendTime = 0;
9380           return;
9381         }
9382         if (StrStr(message, "analyze")) {
9383             cps->analysisSupport = FALSE;
9384             cps->analyzing = FALSE;
9385 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9386             EditGameEvent(); // [HGM] try to preserve loaded game
9387             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9388             DisplayError(buf2, 0);
9389             return;
9390         }
9391         if (StrStr(message, "(no matching move)st")) {
9392           /* Special kludge for GNU Chess 4 only */
9393           cps->stKludge = TRUE;
9394           SendTimeControl(cps, movesPerSession, timeControl,
9395                           timeIncrement, appData.searchDepth,
9396                           searchTime);
9397           return;
9398         }
9399         if (StrStr(message, "(no matching move)sd")) {
9400           /* Special kludge for GNU Chess 4 only */
9401           cps->sdKludge = TRUE;
9402           SendTimeControl(cps, movesPerSession, timeControl,
9403                           timeIncrement, appData.searchDepth,
9404                           searchTime);
9405           return;
9406         }
9407         if (!StrStr(message, "llegal")) {
9408             return;
9409         }
9410         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9411             gameMode == IcsIdle) return;
9412         if (forwardMostMove <= backwardMostMove) return;
9413         if (pausing) PauseEvent();
9414       if(appData.forceIllegal) {
9415             // [HGM] illegal: machine refused move; force position after move into it
9416           SendToProgram("force\n", cps);
9417           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9418                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9419                 // when black is to move, while there might be nothing on a2 or black
9420                 // might already have the move. So send the board as if white has the move.
9421                 // But first we must change the stm of the engine, as it refused the last move
9422                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9423                 if(WhiteOnMove(forwardMostMove)) {
9424                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9425                     SendBoard(cps, forwardMostMove); // kludgeless board
9426                 } else {
9427                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9428                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9429                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9430                 }
9431           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9432             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9433                  gameMode == TwoMachinesPlay)
9434               SendToProgram("go\n", cps);
9435             return;
9436       } else
9437         if (gameMode == PlayFromGameFile) {
9438             /* Stop reading this game file */
9439             gameMode = EditGame;
9440             ModeHighlight();
9441         }
9442         /* [HGM] illegal-move claim should forfeit game when Xboard */
9443         /* only passes fully legal moves                            */
9444         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9445             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9446                                 "False illegal-move claim", GE_XBOARD );
9447             return; // do not take back move we tested as valid
9448         }
9449         currentMove = forwardMostMove-1;
9450         DisplayMove(currentMove-1); /* before DisplayMoveError */
9451         SwitchClocks(forwardMostMove-1); // [HGM] race
9452         DisplayBothClocks();
9453         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9454                 parseList[currentMove], _(cps->which));
9455         DisplayMoveError(buf1);
9456         DrawPosition(FALSE, boards[currentMove]);
9457
9458         SetUserThinkingEnables();
9459         return;
9460     }
9461     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9462         /* Program has a broken "time" command that
9463            outputs a string not ending in newline.
9464            Don't use it. */
9465         cps->sendTime = 0;
9466     }
9467     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9468         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9469             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9470     }
9471
9472     /*
9473      * If chess program startup fails, exit with an error message.
9474      * Attempts to recover here are futile. [HGM] Well, we try anyway
9475      */
9476     if ((StrStr(message, "unknown host") != NULL)
9477         || (StrStr(message, "No remote directory") != NULL)
9478         || (StrStr(message, "not found") != NULL)
9479         || (StrStr(message, "No such file") != NULL)
9480         || (StrStr(message, "can't alloc") != NULL)
9481         || (StrStr(message, "Permission denied") != NULL)) {
9482
9483         cps->maybeThinking = FALSE;
9484         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9485                 _(cps->which), cps->program, cps->host, message);
9486         RemoveInputSource(cps->isr);
9487         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9488             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9489             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9490         }
9491         return;
9492     }
9493
9494     /*
9495      * Look for hint output
9496      */
9497     if (sscanf(message, "Hint: %s", buf1) == 1) {
9498         if (cps == &first && hintRequested) {
9499             hintRequested = FALSE;
9500             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9501                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9502                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9503                                     PosFlags(forwardMostMove),
9504                                     fromY, fromX, toY, toX, promoChar, buf1);
9505                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9506                 DisplayInformation(buf2);
9507             } else {
9508                 /* Hint move could not be parsed!? */
9509               snprintf(buf2, sizeof(buf2),
9510                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9511                         buf1, _(cps->which));
9512                 DisplayError(buf2, 0);
9513             }
9514         } else {
9515           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9516         }
9517         return;
9518     }
9519
9520     /*
9521      * Ignore other messages if game is not in progress
9522      */
9523     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9524         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9525
9526     /*
9527      * look for win, lose, draw, or draw offer
9528      */
9529     if (strncmp(message, "1-0", 3) == 0) {
9530         char *p, *q, *r = "";
9531         p = strchr(message, '{');
9532         if (p) {
9533             q = strchr(p, '}');
9534             if (q) {
9535                 *q = NULLCHAR;
9536                 r = p + 1;
9537             }
9538         }
9539         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9540         return;
9541     } else if (strncmp(message, "0-1", 3) == 0) {
9542         char *p, *q, *r = "";
9543         p = strchr(message, '{');
9544         if (p) {
9545             q = strchr(p, '}');
9546             if (q) {
9547                 *q = NULLCHAR;
9548                 r = p + 1;
9549             }
9550         }
9551         /* Kludge for Arasan 4.1 bug */
9552         if (strcmp(r, "Black resigns") == 0) {
9553             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9554             return;
9555         }
9556         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9557         return;
9558     } else if (strncmp(message, "1/2", 3) == 0) {
9559         char *p, *q, *r = "";
9560         p = strchr(message, '{');
9561         if (p) {
9562             q = strchr(p, '}');
9563             if (q) {
9564                 *q = NULLCHAR;
9565                 r = p + 1;
9566             }
9567         }
9568
9569         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9570         return;
9571
9572     } else if (strncmp(message, "White resign", 12) == 0) {
9573         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9574         return;
9575     } else if (strncmp(message, "Black resign", 12) == 0) {
9576         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9577         return;
9578     } else if (strncmp(message, "White matches", 13) == 0 ||
9579                strncmp(message, "Black matches", 13) == 0   ) {
9580         /* [HGM] ignore GNUShogi noises */
9581         return;
9582     } else if (strncmp(message, "White", 5) == 0 &&
9583                message[5] != '(' &&
9584                StrStr(message, "Black") == NULL) {
9585         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9586         return;
9587     } else if (strncmp(message, "Black", 5) == 0 &&
9588                message[5] != '(') {
9589         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9590         return;
9591     } else if (strcmp(message, "resign") == 0 ||
9592                strcmp(message, "computer resigns") == 0) {
9593         switch (gameMode) {
9594           case MachinePlaysBlack:
9595           case IcsPlayingBlack:
9596             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9597             break;
9598           case MachinePlaysWhite:
9599           case IcsPlayingWhite:
9600             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9601             break;
9602           case TwoMachinesPlay:
9603             if (cps->twoMachinesColor[0] == 'w')
9604               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9605             else
9606               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9607             break;
9608           default:
9609             /* can't happen */
9610             break;
9611         }
9612         return;
9613     } else if (strncmp(message, "opponent mates", 14) == 0) {
9614         switch (gameMode) {
9615           case MachinePlaysBlack:
9616           case IcsPlayingBlack:
9617             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9618             break;
9619           case MachinePlaysWhite:
9620           case IcsPlayingWhite:
9621             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9622             break;
9623           case TwoMachinesPlay:
9624             if (cps->twoMachinesColor[0] == 'w')
9625               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9626             else
9627               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9628             break;
9629           default:
9630             /* can't happen */
9631             break;
9632         }
9633         return;
9634     } else if (strncmp(message, "computer mates", 14) == 0) {
9635         switch (gameMode) {
9636           case MachinePlaysBlack:
9637           case IcsPlayingBlack:
9638             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9639             break;
9640           case MachinePlaysWhite:
9641           case IcsPlayingWhite:
9642             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9643             break;
9644           case TwoMachinesPlay:
9645             if (cps->twoMachinesColor[0] == 'w')
9646               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9647             else
9648               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9649             break;
9650           default:
9651             /* can't happen */
9652             break;
9653         }
9654         return;
9655     } else if (strncmp(message, "checkmate", 9) == 0) {
9656         if (WhiteOnMove(forwardMostMove)) {
9657             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9658         } else {
9659             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9660         }
9661         return;
9662     } else if (strstr(message, "Draw") != NULL ||
9663                strstr(message, "game is a draw") != NULL) {
9664         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9665         return;
9666     } else if (strstr(message, "offer") != NULL &&
9667                strstr(message, "draw") != NULL) {
9668 #if ZIPPY
9669         if (appData.zippyPlay && first.initDone) {
9670             /* Relay offer to ICS */
9671             SendToICS(ics_prefix);
9672             SendToICS("draw\n");
9673         }
9674 #endif
9675         cps->offeredDraw = 2; /* valid until this engine moves twice */
9676         if (gameMode == TwoMachinesPlay) {
9677             if (cps->other->offeredDraw) {
9678                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9679             /* [HGM] in two-machine mode we delay relaying draw offer      */
9680             /* until after we also have move, to see if it is really claim */
9681             }
9682         } else if (gameMode == MachinePlaysWhite ||
9683                    gameMode == MachinePlaysBlack) {
9684           if (userOfferedDraw) {
9685             DisplayInformation(_("Machine accepts your draw offer"));
9686             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9687           } else {
9688             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9689           }
9690         }
9691     }
9692
9693
9694     /*
9695      * Look for thinking output
9696      */
9697     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9698           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9699                                 ) {
9700         int plylev, mvleft, mvtot, curscore, time;
9701         char mvname[MOVE_LEN];
9702         u64 nodes; // [DM]
9703         char plyext;
9704         int ignore = FALSE;
9705         int prefixHint = FALSE;
9706         mvname[0] = NULLCHAR;
9707
9708         switch (gameMode) {
9709           case MachinePlaysBlack:
9710           case IcsPlayingBlack:
9711             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9712             break;
9713           case MachinePlaysWhite:
9714           case IcsPlayingWhite:
9715             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9716             break;
9717           case AnalyzeMode:
9718           case AnalyzeFile:
9719             break;
9720           case IcsObserving: /* [DM] icsEngineAnalyze */
9721             if (!appData.icsEngineAnalyze) ignore = TRUE;
9722             break;
9723           case TwoMachinesPlay:
9724             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9725                 ignore = TRUE;
9726             }
9727             break;
9728           default:
9729             ignore = TRUE;
9730             break;
9731         }
9732
9733         if (!ignore) {
9734             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9735             int solved = 0;
9736             buf1[0] = NULLCHAR;
9737             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9738                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9739                 char score_buf[MSG_SIZ];
9740
9741                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9742                     nodes += u64Const(0x100000000);
9743
9744                 if (plyext != ' ' && plyext != '\t') {
9745                     time *= 100;
9746                 }
9747
9748                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9749                 if( cps->scoreIsAbsolute &&
9750                     ( gameMode == MachinePlaysBlack ||
9751                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9752                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9753                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9754                      !WhiteOnMove(currentMove)
9755                     ) )
9756                 {
9757                     curscore = -curscore;
9758                 }
9759
9760                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9761
9762                 if(*bestMove) { // rememer time best EPD move was first found
9763                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9764                     ChessMove mt; char *p = bestMove;
9765                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9766                     solved = 0;
9767                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9768                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9769                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9770                             solved = 1;
9771                             break;
9772                         }
9773                         while(*p && *p != ' ') p++;
9774                         while(*p == ' ') p++;
9775                     }
9776                     if(!solved) solvingTime = -1;
9777                 }
9778                 if(*avoidMove && !solved) {
9779                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9780                     ChessMove mt; char *p = avoidMove, solved = 1;
9781                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9782                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9783                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9784                             solved = 0; solvingTime = -2;
9785                             break;
9786                         }
9787                         while(*p && *p != ' ') p++;
9788                         while(*p == ' ') p++;
9789                     }
9790                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9791                 }
9792
9793                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9794                         char buf[MSG_SIZ];
9795                         FILE *f;
9796                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9797                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9798                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9799                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9800                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9801                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9802                                 fclose(f);
9803                         }
9804                         else
9805                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9806                           DisplayError(_("failed writing PV"), 0);
9807                 }
9808
9809                 tempStats.depth = plylev;
9810                 tempStats.nodes = nodes;
9811                 tempStats.time = time;
9812                 tempStats.score = curscore;
9813                 tempStats.got_only_move = 0;
9814
9815                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9816                         int ticklen;
9817
9818                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9819                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9820                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9821                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9822                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9823                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9824                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9825                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9826                 }
9827
9828                 /* Buffer overflow protection */
9829                 if (pv[0] != NULLCHAR) {
9830                     if (strlen(pv) >= sizeof(tempStats.movelist)
9831                         && appData.debugMode) {
9832                         fprintf(debugFP,
9833                                 "PV is too long; using the first %u bytes.\n",
9834                                 (unsigned) sizeof(tempStats.movelist) - 1);
9835                     }
9836
9837                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9838                 } else {
9839                     sprintf(tempStats.movelist, " no PV\n");
9840                 }
9841
9842                 if (tempStats.seen_stat) {
9843                     tempStats.ok_to_send = 1;
9844                 }
9845
9846                 if (strchr(tempStats.movelist, '(') != NULL) {
9847                     tempStats.line_is_book = 1;
9848                     tempStats.nr_moves = 0;
9849                     tempStats.moves_left = 0;
9850                 } else {
9851                     tempStats.line_is_book = 0;
9852                 }
9853
9854                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9855                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9856
9857                 SendProgramStatsToFrontend( cps, &tempStats );
9858
9859                 /*
9860                     [AS] Protect the thinkOutput buffer from overflow... this
9861                     is only useful if buf1 hasn't overflowed first!
9862                 */
9863                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9864                 if(curscore >= MATE_SCORE) 
9865                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9866                 else if(curscore <= -MATE_SCORE) 
9867                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9868                 else
9869                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9870                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9871                          plylev,
9872                          (gameMode == TwoMachinesPlay ?
9873                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9874                          score_buf,
9875                          prefixHint ? lastHint : "",
9876                          prefixHint ? " " : "" );
9877
9878                 if( buf1[0] != NULLCHAR ) {
9879                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9880
9881                     if( strlen(pv) > max_len ) {
9882                         if( appData.debugMode) {
9883                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9884                         }
9885                         pv[max_len+1] = '\0';
9886                     }
9887
9888                     strcat( thinkOutput, pv);
9889                 }
9890
9891                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9892                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9893                     DisplayMove(currentMove - 1);
9894                 }
9895                 return;
9896
9897             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9898                 /* crafty (9.25+) says "(only move) <move>"
9899                  * if there is only 1 legal move
9900                  */
9901                 sscanf(p, "(only move) %s", buf1);
9902                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9903                 sprintf(programStats.movelist, "%s (only move)", buf1);
9904                 programStats.depth = 1;
9905                 programStats.nr_moves = 1;
9906                 programStats.moves_left = 1;
9907                 programStats.nodes = 1;
9908                 programStats.time = 1;
9909                 programStats.got_only_move = 1;
9910
9911                 /* Not really, but we also use this member to
9912                    mean "line isn't going to change" (Crafty
9913                    isn't searching, so stats won't change) */
9914                 programStats.line_is_book = 1;
9915
9916                 SendProgramStatsToFrontend( cps, &programStats );
9917
9918                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9919                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9920                     DisplayMove(currentMove - 1);
9921                 }
9922                 return;
9923             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9924                               &time, &nodes, &plylev, &mvleft,
9925                               &mvtot, mvname) >= 5) {
9926                 /* The stat01: line is from Crafty (9.29+) in response
9927                    to the "." command */
9928                 programStats.seen_stat = 1;
9929                 cps->maybeThinking = TRUE;
9930
9931                 if (programStats.got_only_move || !appData.periodicUpdates)
9932                   return;
9933
9934                 programStats.depth = plylev;
9935                 programStats.time = time;
9936                 programStats.nodes = nodes;
9937                 programStats.moves_left = mvleft;
9938                 programStats.nr_moves = mvtot;
9939                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9940                 programStats.ok_to_send = 1;
9941                 programStats.movelist[0] = '\0';
9942
9943                 SendProgramStatsToFrontend( cps, &programStats );
9944
9945                 return;
9946
9947             } else if (strncmp(message,"++",2) == 0) {
9948                 /* Crafty 9.29+ outputs this */
9949                 programStats.got_fail = 2;
9950                 return;
9951
9952             } else if (strncmp(message,"--",2) == 0) {
9953                 /* Crafty 9.29+ outputs this */
9954                 programStats.got_fail = 1;
9955                 return;
9956
9957             } else if (thinkOutput[0] != NULLCHAR &&
9958                        strncmp(message, "    ", 4) == 0) {
9959                 unsigned message_len;
9960
9961                 p = message;
9962                 while (*p && *p == ' ') p++;
9963
9964                 message_len = strlen( p );
9965
9966                 /* [AS] Avoid buffer overflow */
9967                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9968                     strcat(thinkOutput, " ");
9969                     strcat(thinkOutput, p);
9970                 }
9971
9972                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9973                     strcat(programStats.movelist, " ");
9974                     strcat(programStats.movelist, p);
9975                 }
9976
9977                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9978                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9979                     DisplayMove(currentMove - 1);
9980                 }
9981                 return;
9982             }
9983         }
9984         else {
9985             buf1[0] = NULLCHAR;
9986
9987             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9988                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9989             {
9990                 ChessProgramStats cpstats;
9991
9992                 if (plyext != ' ' && plyext != '\t') {
9993                     time *= 100;
9994                 }
9995
9996                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9997                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9998                     curscore = -curscore;
9999                 }
10000
10001                 cpstats.depth = plylev;
10002                 cpstats.nodes = nodes;
10003                 cpstats.time = time;
10004                 cpstats.score = curscore;
10005                 cpstats.got_only_move = 0;
10006                 cpstats.movelist[0] = '\0';
10007
10008                 if (buf1[0] != NULLCHAR) {
10009                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10010                 }
10011
10012                 cpstats.ok_to_send = 0;
10013                 cpstats.line_is_book = 0;
10014                 cpstats.nr_moves = 0;
10015                 cpstats.moves_left = 0;
10016
10017                 SendProgramStatsToFrontend( cps, &cpstats );
10018             }
10019         }
10020     }
10021 }
10022
10023
10024 /* Parse a game score from the character string "game", and
10025    record it as the history of the current game.  The game
10026    score is NOT assumed to start from the standard position.
10027    The display is not updated in any way.
10028    */
10029 void
10030 ParseGameHistory (char *game)
10031 {
10032     ChessMove moveType;
10033     int fromX, fromY, toX, toY, boardIndex;
10034     char promoChar;
10035     char *p, *q;
10036     char buf[MSG_SIZ];
10037
10038     if (appData.debugMode)
10039       fprintf(debugFP, "Parsing game history: %s\n", game);
10040
10041     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10042     gameInfo.site = StrSave(appData.icsHost);
10043     gameInfo.date = PGNDate();
10044     gameInfo.round = StrSave("-");
10045
10046     /* Parse out names of players */
10047     while (*game == ' ') game++;
10048     p = buf;
10049     while (*game != ' ') *p++ = *game++;
10050     *p = NULLCHAR;
10051     gameInfo.white = StrSave(buf);
10052     while (*game == ' ') game++;
10053     p = buf;
10054     while (*game != ' ' && *game != '\n') *p++ = *game++;
10055     *p = NULLCHAR;
10056     gameInfo.black = StrSave(buf);
10057
10058     /* Parse moves */
10059     boardIndex = blackPlaysFirst ? 1 : 0;
10060     yynewstr(game);
10061     for (;;) {
10062         yyboardindex = boardIndex;
10063         moveType = (ChessMove) Myylex();
10064         switch (moveType) {
10065           case IllegalMove:             /* maybe suicide chess, etc. */
10066   if (appData.debugMode) {
10067     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10068     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10069     setbuf(debugFP, NULL);
10070   }
10071           case WhitePromotion:
10072           case BlackPromotion:
10073           case WhiteNonPromotion:
10074           case BlackNonPromotion:
10075           case NormalMove:
10076           case FirstLeg:
10077           case WhiteCapturesEnPassant:
10078           case BlackCapturesEnPassant:
10079           case WhiteKingSideCastle:
10080           case WhiteQueenSideCastle:
10081           case BlackKingSideCastle:
10082           case BlackQueenSideCastle:
10083           case WhiteKingSideCastleWild:
10084           case WhiteQueenSideCastleWild:
10085           case BlackKingSideCastleWild:
10086           case BlackQueenSideCastleWild:
10087           /* PUSH Fabien */
10088           case WhiteHSideCastleFR:
10089           case WhiteASideCastleFR:
10090           case BlackHSideCastleFR:
10091           case BlackASideCastleFR:
10092           /* POP Fabien */
10093             fromX = currentMoveString[0] - AAA;
10094             fromY = currentMoveString[1] - ONE;
10095             toX = currentMoveString[2] - AAA;
10096             toY = currentMoveString[3] - ONE;
10097             promoChar = currentMoveString[4];
10098             break;
10099           case WhiteDrop:
10100           case BlackDrop:
10101             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10102             fromX = moveType == WhiteDrop ?
10103               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10104             (int) CharToPiece(ToLower(currentMoveString[0]));
10105             fromY = DROP_RANK;
10106             toX = currentMoveString[2] - AAA;
10107             toY = currentMoveString[3] - ONE;
10108             promoChar = NULLCHAR;
10109             break;
10110           case AmbiguousMove:
10111             /* bug? */
10112             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10113   if (appData.debugMode) {
10114     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10115     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10116     setbuf(debugFP, NULL);
10117   }
10118             DisplayError(buf, 0);
10119             return;
10120           case ImpossibleMove:
10121             /* bug? */
10122             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10123   if (appData.debugMode) {
10124     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10125     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10126     setbuf(debugFP, NULL);
10127   }
10128             DisplayError(buf, 0);
10129             return;
10130           case EndOfFile:
10131             if (boardIndex < backwardMostMove) {
10132                 /* Oops, gap.  How did that happen? */
10133                 DisplayError(_("Gap in move list"), 0);
10134                 return;
10135             }
10136             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10137             if (boardIndex > forwardMostMove) {
10138                 forwardMostMove = boardIndex;
10139             }
10140             return;
10141           case ElapsedTime:
10142             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10143                 strcat(parseList[boardIndex-1], " ");
10144                 strcat(parseList[boardIndex-1], yy_text);
10145             }
10146             continue;
10147           case Comment:
10148           case PGNTag:
10149           case NAG:
10150           default:
10151             /* ignore */
10152             continue;
10153           case WhiteWins:
10154           case BlackWins:
10155           case GameIsDrawn:
10156           case GameUnfinished:
10157             if (gameMode == IcsExamining) {
10158                 if (boardIndex < backwardMostMove) {
10159                     /* Oops, gap.  How did that happen? */
10160                     return;
10161                 }
10162                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10163                 return;
10164             }
10165             gameInfo.result = moveType;
10166             p = strchr(yy_text, '{');
10167             if (p == NULL) p = strchr(yy_text, '(');
10168             if (p == NULL) {
10169                 p = yy_text;
10170                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10171             } else {
10172                 q = strchr(p, *p == '{' ? '}' : ')');
10173                 if (q != NULL) *q = NULLCHAR;
10174                 p++;
10175             }
10176             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10177             gameInfo.resultDetails = StrSave(p);
10178             continue;
10179         }
10180         if (boardIndex >= forwardMostMove &&
10181             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10182             backwardMostMove = blackPlaysFirst ? 1 : 0;
10183             return;
10184         }
10185         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10186                                  fromY, fromX, toY, toX, promoChar,
10187                                  parseList[boardIndex]);
10188         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10189         /* currentMoveString is set as a side-effect of yylex */
10190         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10191         strcat(moveList[boardIndex], "\n");
10192         boardIndex++;
10193         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10194         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10195           case MT_NONE:
10196           case MT_STALEMATE:
10197           default:
10198             break;
10199           case MT_CHECK:
10200             if(!IS_SHOGI(gameInfo.variant))
10201                 strcat(parseList[boardIndex - 1], "+");
10202             break;
10203           case MT_CHECKMATE:
10204           case MT_STAINMATE:
10205             strcat(parseList[boardIndex - 1], "#");
10206             break;
10207         }
10208     }
10209 }
10210
10211
10212 /* Apply a move to the given board  */
10213 void
10214 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10215 {
10216   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10217   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10218
10219     /* [HGM] compute & store e.p. status and castling rights for new position */
10220     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10221
10222       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10223       oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_FILE],lastRank = board[LAST_RANK];
10224       board[EP_STATUS] = EP_NONE;
10225       board[EP_FILE] = board[EP_RANK] = board[LAST_FILE] = board[LAST_RANK] = 100;
10226
10227   if (fromY == DROP_RANK) {
10228         /* must be first */
10229         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10230             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10231             return;
10232         }
10233         piece = board[toY][toX] = (ChessSquare) fromX;
10234   } else {
10235 //      ChessSquare victim;
10236       int i;
10237
10238       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10239 //           victim = board[killY][killX],
10240            killed = board[killY][killX],
10241            board[killY][killX] = EmptySquare,
10242            board[EP_STATUS] = EP_CAPTURE;
10243            if( kill2X >= 0 && kill2Y >= 0)
10244              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10245       }
10246
10247       if( board[toY][toX] != EmptySquare ) {
10248            board[EP_STATUS] = EP_CAPTURE;
10249            if( (fromX != toX || fromY != toY) && // not igui!
10250                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10251                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10252                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10253            }
10254       }
10255
10256       pawn = board[fromY][fromX];
10257       if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10258         if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10259             captured = board[lastRank][lastFile]; // remove victim
10260             board[lastRank][lastFile] = EmptySquare;
10261             pawn = EmptySquare; // kludge to suppress old e.p. code
10262         }
10263       }
10264       if( pawn == WhiteLance || pawn == BlackLance ) {
10265            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10266                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10267                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10268            }
10269       }
10270       if( pawn == WhitePawn ) {
10271            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10272                board[EP_STATUS] = EP_PAWN_MOVE;
10273            if( toY-fromY>=2) {
10274                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10275                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10276                         gameInfo.variant != VariantBerolina || toX < fromX)
10277                       board[EP_STATUS] = toX | berolina;
10278                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10279                         gameInfo.variant != VariantBerolina || toX > fromX)
10280                       board[EP_STATUS] = toX;
10281                board[LAST_FILE] = toX; board[LAST_RANK] = toY;
10282            }
10283       } else
10284       if( pawn == BlackPawn ) {
10285            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10286                board[EP_STATUS] = EP_PAWN_MOVE;
10287            if( toY-fromY<= -2) {
10288                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10289                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10290                         gameInfo.variant != VariantBerolina || toX < fromX)
10291                       board[EP_STATUS] = toX | berolina;
10292                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10293                         gameInfo.variant != VariantBerolina || toX > fromX)
10294                       board[EP_STATUS] = toX;
10295                board[LAST_FILE] = toX; board[LAST_RANK] = toY;
10296            }
10297        }
10298
10299        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10300        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10301        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10302        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10303
10304        for(i=0; i<nrCastlingRights; i++) {
10305            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10306               board[CASTLING][i] == toX   && castlingRank[i] == toY
10307              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10308        }
10309
10310        if(gameInfo.variant == VariantSChess) { // update virginity
10311            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10312            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10313            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10314            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10315        }
10316
10317      if (fromX == toX && fromY == toY && killX < 0) return;
10318
10319      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10320      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10321      if(gameInfo.variant == VariantKnightmate)
10322          king += (int) WhiteUnicorn - (int) WhiteKing;
10323
10324     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10325        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10326         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10327         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10328         board[EP_STATUS] = EP_NONE; // capture was fake!
10329     } else
10330     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10331         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10332         board[toY][toX] = piece;
10333         board[EP_STATUS] = EP_NONE; // capture was fake!
10334     } else
10335     /* Code added by Tord: */
10336     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10337     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10338         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10339       board[EP_STATUS] = EP_NONE; // capture was fake!
10340       board[fromY][fromX] = EmptySquare;
10341       board[toY][toX] = EmptySquare;
10342       if((toX > fromX) != (piece == WhiteRook)) {
10343         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10344       } else {
10345         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10346       }
10347     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10348                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10349       board[EP_STATUS] = EP_NONE;
10350       board[fromY][fromX] = EmptySquare;
10351       board[toY][toX] = EmptySquare;
10352       if((toX > fromX) != (piece == BlackRook)) {
10353         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10354       } else {
10355         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10356       }
10357     /* End of code added by Tord */
10358
10359     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10360         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10361         board[toY][toX] = piece;
10362     } else if (board[fromY][fromX] == king
10363         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10364         && toY == fromY && toX > fromX+1) {
10365         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10366                                                                                              ; // castle with nearest piece
10367         board[fromY][toX-1] = board[fromY][rookX];
10368         board[fromY][rookX] = EmptySquare;
10369         board[fromY][fromX] = EmptySquare;
10370         board[toY][toX] = king;
10371     } else if (board[fromY][fromX] == king
10372         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10373                && toY == fromY && toX < fromX-1) {
10374         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10375                                                                                   ; // castle with nearest piece
10376         board[fromY][toX+1] = board[fromY][rookX];
10377         board[fromY][rookX] = EmptySquare;
10378         board[fromY][fromX] = EmptySquare;
10379         board[toY][toX] = king;
10380     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10381                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10382                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10383                ) {
10384         /* white pawn promotion */
10385         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10386         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10387             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10388         board[fromY][fromX] = EmptySquare;
10389     } else if ((fromY >= BOARD_HEIGHT>>1)
10390                && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10391                && (toX != fromX)
10392                && gameInfo.variant != VariantXiangqi
10393                && gameInfo.variant != VariantBerolina
10394                && (pawn == WhitePawn)
10395                && (board[toY][toX] == EmptySquare)) {
10396         if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10397         board[fromY][fromX] = EmptySquare;
10398         board[toY][toX] = piece;
10399         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10400     } else if ((fromY == BOARD_HEIGHT-4)
10401                && (toX == fromX)
10402                && gameInfo.variant == VariantBerolina
10403                && (board[fromY][fromX] == WhitePawn)
10404                && (board[toY][toX] == EmptySquare)) {
10405         board[fromY][fromX] = EmptySquare;
10406         board[toY][toX] = WhitePawn;
10407         if(oldEP & EP_BEROLIN_A) {
10408                 captured = board[fromY][fromX-1];
10409                 board[fromY][fromX-1] = EmptySquare;
10410         }else{  captured = board[fromY][fromX+1];
10411                 board[fromY][fromX+1] = EmptySquare;
10412         }
10413     } else if (board[fromY][fromX] == king
10414         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10415                && toY == fromY && toX > fromX+1) {
10416         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10417                                                                                              ;
10418         board[fromY][toX-1] = board[fromY][rookX];
10419         board[fromY][rookX] = EmptySquare;
10420         board[fromY][fromX] = EmptySquare;
10421         board[toY][toX] = king;
10422     } else if (board[fromY][fromX] == king
10423         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10424                && toY == fromY && toX < fromX-1) {
10425         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10426                                                                                 ;
10427         board[fromY][toX+1] = board[fromY][rookX];
10428         board[fromY][rookX] = EmptySquare;
10429         board[fromY][fromX] = EmptySquare;
10430         board[toY][toX] = king;
10431     } else if (fromY == 7 && fromX == 3
10432                && board[fromY][fromX] == BlackKing
10433                && toY == 7 && toX == 5) {
10434         board[fromY][fromX] = EmptySquare;
10435         board[toY][toX] = BlackKing;
10436         board[fromY][7] = EmptySquare;
10437         board[toY][4] = BlackRook;
10438     } else if (fromY == 7 && fromX == 3
10439                && board[fromY][fromX] == BlackKing
10440                && toY == 7 && toX == 1) {
10441         board[fromY][fromX] = EmptySquare;
10442         board[toY][toX] = BlackKing;
10443         board[fromY][0] = EmptySquare;
10444         board[toY][2] = BlackRook;
10445     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10446                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10447                && toY < promoRank && promoChar
10448                ) {
10449         /* black pawn promotion */
10450         board[toY][toX] = CharToPiece(ToLower(promoChar));
10451         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10452             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10453         board[fromY][fromX] = EmptySquare;
10454     } else if ((fromY < BOARD_HEIGHT>>1)
10455                && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10456                && (toX != fromX)
10457                && gameInfo.variant != VariantXiangqi
10458                && gameInfo.variant != VariantBerolina
10459                && (pawn == BlackPawn)
10460                && (board[toY][toX] == EmptySquare)) {
10461         if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10462         board[fromY][fromX] = EmptySquare;
10463         board[toY][toX] = piece;
10464         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10465     } else if ((fromY == 3)
10466                && (toX == fromX)
10467                && gameInfo.variant == VariantBerolina
10468                && (board[fromY][fromX] == BlackPawn)
10469                && (board[toY][toX] == EmptySquare)) {
10470         board[fromY][fromX] = EmptySquare;
10471         board[toY][toX] = BlackPawn;
10472         if(oldEP & EP_BEROLIN_A) {
10473                 captured = board[fromY][fromX-1];
10474                 board[fromY][fromX-1] = EmptySquare;
10475         }else{  captured = board[fromY][fromX+1];
10476                 board[fromY][fromX+1] = EmptySquare;
10477         }
10478     } else {
10479         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10480         board[fromY][fromX] = EmptySquare;
10481         board[toY][toX] = piece;
10482     }
10483   }
10484
10485     if (gameInfo.holdingsWidth != 0) {
10486
10487       /* !!A lot more code needs to be written to support holdings  */
10488       /* [HGM] OK, so I have written it. Holdings are stored in the */
10489       /* penultimate board files, so they are automaticlly stored   */
10490       /* in the game history.                                       */
10491       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10492                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10493         /* Delete from holdings, by decreasing count */
10494         /* and erasing image if necessary            */
10495         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10496         if(p < (int) BlackPawn) { /* white drop */
10497              p -= (int)WhitePawn;
10498                  p = PieceToNumber((ChessSquare)p);
10499              if(p >= gameInfo.holdingsSize) p = 0;
10500              if(--board[p][BOARD_WIDTH-2] <= 0)
10501                   board[p][BOARD_WIDTH-1] = EmptySquare;
10502              if((int)board[p][BOARD_WIDTH-2] < 0)
10503                         board[p][BOARD_WIDTH-2] = 0;
10504         } else {                  /* black drop */
10505              p -= (int)BlackPawn;
10506                  p = PieceToNumber((ChessSquare)p);
10507              if(p >= gameInfo.holdingsSize) p = 0;
10508              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10509                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10510              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10511                         board[BOARD_HEIGHT-1-p][1] = 0;
10512         }
10513       }
10514       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10515           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10516         /* [HGM] holdings: Add to holdings, if holdings exist */
10517         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10518                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10519                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10520         }
10521         p = (int) captured;
10522         if (p >= (int) BlackPawn) {
10523           p -= (int)BlackPawn;
10524           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10525                   /* Restore shogi-promoted piece to its original  first */
10526                   captured = (ChessSquare) (DEMOTED(captured));
10527                   p = DEMOTED(p);
10528           }
10529           p = PieceToNumber((ChessSquare)p);
10530           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10531           board[p][BOARD_WIDTH-2]++;
10532           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10533         } else {
10534           p -= (int)WhitePawn;
10535           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10536                   captured = (ChessSquare) (DEMOTED(captured));
10537                   p = DEMOTED(p);
10538           }
10539           p = PieceToNumber((ChessSquare)p);
10540           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10541           board[BOARD_HEIGHT-1-p][1]++;
10542           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10543         }
10544       }
10545     } else if (gameInfo.variant == VariantAtomic) {
10546       if (captured != EmptySquare) {
10547         int y, x;
10548         for (y = toY-1; y <= toY+1; y++) {
10549           for (x = toX-1; x <= toX+1; x++) {
10550             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10551                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10552               board[y][x] = EmptySquare;
10553             }
10554           }
10555         }
10556         board[toY][toX] = EmptySquare;
10557       }
10558     }
10559
10560     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10561         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10562     } else
10563     if(promoChar == '+') {
10564         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10565         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10566         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10567           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10568     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10569         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10570         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10571            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10572         board[toY][toX] = newPiece;
10573     }
10574     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10575                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10576         // [HGM] superchess: take promotion piece out of holdings
10577         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10578         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10579             if(!--board[k][BOARD_WIDTH-2])
10580                 board[k][BOARD_WIDTH-1] = EmptySquare;
10581         } else {
10582             if(!--board[BOARD_HEIGHT-1-k][1])
10583                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10584         }
10585     }
10586 }
10587
10588 /* Updates forwardMostMove */
10589 void
10590 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10591 {
10592     int x = toX, y = toY;
10593     char *s = parseList[forwardMostMove];
10594     ChessSquare p = boards[forwardMostMove][toY][toX];
10595 //    forwardMostMove++; // [HGM] bare: moved downstream
10596
10597     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10598     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10599     (void) CoordsToAlgebraic(boards[forwardMostMove],
10600                              PosFlags(forwardMostMove),
10601                              fromY, fromX, y, x, (killX < 0)*promoChar,
10602                              s);
10603     if(kill2X >= 0 && kill2Y >= 0)
10604         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10605     if(killX >= 0 && killY >= 0)
10606         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10607                                            toX + AAA, toY + ONE - '0', promoChar);
10608
10609     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10610         int timeLeft; static int lastLoadFlag=0; int king, piece;
10611         piece = boards[forwardMostMove][fromY][fromX];
10612         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10613         if(gameInfo.variant == VariantKnightmate)
10614             king += (int) WhiteUnicorn - (int) WhiteKing;
10615         if(forwardMostMove == 0) {
10616             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10617                 fprintf(serverMoves, "%s;", UserName());
10618             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10619                 fprintf(serverMoves, "%s;", second.tidy);
10620             fprintf(serverMoves, "%s;", first.tidy);
10621             if(gameMode == MachinePlaysWhite)
10622                 fprintf(serverMoves, "%s;", UserName());
10623             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10624                 fprintf(serverMoves, "%s;", second.tidy);
10625         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10626         lastLoadFlag = loadFlag;
10627         // print base move
10628         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10629         // print castling suffix
10630         if( toY == fromY && piece == king ) {
10631             if(toX-fromX > 1)
10632                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10633             if(fromX-toX >1)
10634                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10635         }
10636         // e.p. suffix
10637         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10638              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10639              boards[forwardMostMove][toY][toX] == EmptySquare
10640              && fromX != toX && fromY != toY)
10641                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10642         // promotion suffix
10643         if(promoChar != NULLCHAR) {
10644             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10645                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10646                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10647             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10648         }
10649         if(!loadFlag) {
10650                 char buf[MOVE_LEN*2], *p; int len;
10651             fprintf(serverMoves, "/%d/%d",
10652                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10653             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10654             else                      timeLeft = blackTimeRemaining/1000;
10655             fprintf(serverMoves, "/%d", timeLeft);
10656                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10657                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10658                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10659                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10660             fprintf(serverMoves, "/%s", buf);
10661         }
10662         fflush(serverMoves);
10663     }
10664
10665     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10666         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10667       return;
10668     }
10669     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10670     if (commentList[forwardMostMove+1] != NULL) {
10671         free(commentList[forwardMostMove+1]);
10672         commentList[forwardMostMove+1] = NULL;
10673     }
10674     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10675     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10676     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10677     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10678     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10679     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10680     adjustedClock = FALSE;
10681     gameInfo.result = GameUnfinished;
10682     if (gameInfo.resultDetails != NULL) {
10683         free(gameInfo.resultDetails);
10684         gameInfo.resultDetails = NULL;
10685     }
10686     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10687                               moveList[forwardMostMove - 1]);
10688     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10689       case MT_NONE:
10690       case MT_STALEMATE:
10691       default:
10692         break;
10693       case MT_CHECK:
10694         if(!IS_SHOGI(gameInfo.variant))
10695             strcat(parseList[forwardMostMove - 1], "+");
10696         break;
10697       case MT_CHECKMATE:
10698       case MT_STAINMATE:
10699         strcat(parseList[forwardMostMove - 1], "#");
10700         break;
10701     }
10702 }
10703
10704 /* Updates currentMove if not pausing */
10705 void
10706 ShowMove (int fromX, int fromY, int toX, int toY)
10707 {
10708     int instant = (gameMode == PlayFromGameFile) ?
10709         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10710     if(appData.noGUI) return;
10711     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10712         if (!instant) {
10713             if (forwardMostMove == currentMove + 1) {
10714                 AnimateMove(boards[forwardMostMove - 1],
10715                             fromX, fromY, toX, toY);
10716             }
10717         }
10718         currentMove = forwardMostMove;
10719     }
10720
10721     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10722
10723     if (instant) return;
10724
10725     DisplayMove(currentMove - 1);
10726     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10727             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10728                 SetHighlights(fromX, fromY, toX, toY);
10729             }
10730     }
10731     DrawPosition(FALSE, boards[currentMove]);
10732     DisplayBothClocks();
10733     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10734 }
10735
10736 void
10737 SendEgtPath (ChessProgramState *cps)
10738 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10739         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10740
10741         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10742
10743         while(*p) {
10744             char c, *q = name+1, *r, *s;
10745
10746             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10747             while(*p && *p != ',') *q++ = *p++;
10748             *q++ = ':'; *q = 0;
10749             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10750                 strcmp(name, ",nalimov:") == 0 ) {
10751                 // take nalimov path from the menu-changeable option first, if it is defined
10752               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10753                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10754             } else
10755             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10756                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10757                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10758                 s = r = StrStr(s, ":") + 1; // beginning of path info
10759                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10760                 c = *r; *r = 0;             // temporarily null-terminate path info
10761                     *--q = 0;               // strip of trailig ':' from name
10762                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10763                 *r = c;
10764                 SendToProgram(buf,cps);     // send egtbpath command for this format
10765             }
10766             if(*p == ',') p++; // read away comma to position for next format name
10767         }
10768 }
10769
10770 static int
10771 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10772 {
10773       int width = 8, height = 8, holdings = 0;             // most common sizes
10774       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10775       // correct the deviations default for each variant
10776       if( v == VariantXiangqi ) width = 9,  height = 10;
10777       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10778       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10779       if( v == VariantCapablanca || v == VariantCapaRandom ||
10780           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10781                                 width = 10;
10782       if( v == VariantCourier ) width = 12;
10783       if( v == VariantSuper )                            holdings = 8;
10784       if( v == VariantGreat )   width = 10,              holdings = 8;
10785       if( v == VariantSChess )                           holdings = 7;
10786       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10787       if( v == VariantChuChess) width = 10, height = 10;
10788       if( v == VariantChu )     width = 12, height = 12;
10789       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10790              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10791              holdingsSize >= 0 && holdingsSize != holdings;
10792 }
10793
10794 char variantError[MSG_SIZ];
10795
10796 char *
10797 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10798 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10799       char *p, *variant = VariantName(v);
10800       static char b[MSG_SIZ];
10801       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10802            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10803                                                holdingsSize, variant); // cook up sized variant name
10804            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10805            if(StrStr(list, b) == NULL) {
10806                // specific sized variant not known, check if general sizing allowed
10807                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10808                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10809                             boardWidth, boardHeight, holdingsSize, engine);
10810                    return NULL;
10811                }
10812                /* [HGM] here we really should compare with the maximum supported board size */
10813            }
10814       } else snprintf(b, MSG_SIZ,"%s", variant);
10815       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10816       p = StrStr(list, b);
10817       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10818       if(p == NULL) {
10819           // occurs not at all in list, or only as sub-string
10820           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10821           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10822               int l = strlen(variantError);
10823               char *q;
10824               while(p != list && p[-1] != ',') p--;
10825               q = strchr(p, ',');
10826               if(q) *q = NULLCHAR;
10827               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10828               if(q) *q= ',';
10829           }
10830           return NULL;
10831       }
10832       return b;
10833 }
10834
10835 void
10836 InitChessProgram (ChessProgramState *cps, int setup)
10837 /* setup needed to setup FRC opening position */
10838 {
10839     char buf[MSG_SIZ], *b;
10840     if (appData.noChessProgram) return;
10841     hintRequested = FALSE;
10842     bookRequested = FALSE;
10843
10844     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10845     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10846     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10847     if(cps->memSize) { /* [HGM] memory */
10848       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10849         SendToProgram(buf, cps);
10850     }
10851     SendEgtPath(cps); /* [HGM] EGT */
10852     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10853       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10854         SendToProgram(buf, cps);
10855     }
10856
10857     setboardSpoiledMachineBlack = FALSE;
10858     SendToProgram(cps->initString, cps);
10859     if (gameInfo.variant != VariantNormal &&
10860         gameInfo.variant != VariantLoadable
10861         /* [HGM] also send variant if board size non-standard */
10862         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10863
10864       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10865                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10866
10867       if (b == NULL) {
10868         VariantClass v;
10869         char c, *q = cps->variants, *p = strchr(q, ',');
10870         if(p) *p = NULLCHAR;
10871         v = StringToVariant(q);
10872         DisplayError(variantError, 0);
10873         if(v != VariantUnknown && cps == &first) {
10874             int w, h, s;
10875             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10876                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10877             ASSIGN(appData.variant, q);
10878             Reset(TRUE, FALSE);
10879         }
10880         if(p) *p = ',';
10881         return;
10882       }
10883
10884       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10885       SendToProgram(buf, cps);
10886     }
10887     currentlyInitializedVariant = gameInfo.variant;
10888
10889     /* [HGM] send opening position in FRC to first engine */
10890     if(setup) {
10891           SendToProgram("force\n", cps);
10892           SendBoard(cps, 0);
10893           /* engine is now in force mode! Set flag to wake it up after first move. */
10894           setboardSpoiledMachineBlack = 1;
10895     }
10896
10897     if (cps->sendICS) {
10898       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10899       SendToProgram(buf, cps);
10900     }
10901     cps->maybeThinking = FALSE;
10902     cps->offeredDraw = 0;
10903     if (!appData.icsActive) {
10904         SendTimeControl(cps, movesPerSession, timeControl,
10905                         timeIncrement, appData.searchDepth,
10906                         searchTime);
10907     }
10908     if (appData.showThinking
10909         // [HGM] thinking: four options require thinking output to be sent
10910         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10911                                 ) {
10912         SendToProgram("post\n", cps);
10913     }
10914     SendToProgram("hard\n", cps);
10915     if (!appData.ponderNextMove) {
10916         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10917            it without being sure what state we are in first.  "hard"
10918            is not a toggle, so that one is OK.
10919          */
10920         SendToProgram("easy\n", cps);
10921     }
10922     if (cps->usePing) {
10923       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10924       SendToProgram(buf, cps);
10925     }
10926     cps->initDone = TRUE;
10927     ClearEngineOutputPane(cps == &second);
10928 }
10929
10930
10931 char *
10932 ResendOptions (ChessProgramState *cps, int toEngine)
10933 { // send the stored value of the options
10934   int i;
10935   static char buf2[MSG_SIZ*10];
10936   char buf[MSG_SIZ], *p = buf2;
10937   Option *opt = cps->option;
10938   *p = NULLCHAR;
10939   for(i=0; i<cps->nrOptions; i++, opt++) {
10940       *buf = NULLCHAR;
10941       switch(opt->type) {
10942         case Spin:
10943         case Slider:
10944         case CheckBox:
10945             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10946             snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
10947           break;
10948         case ComboBox:
10949             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10950             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
10951           break;
10952         default:
10953             if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
10954             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
10955           break;
10956         case Button:
10957         case SaveButton:
10958           continue;
10959       }
10960       if(*buf) {
10961         if(toEngine) {
10962           snprintf(buf2, MSG_SIZ, "option %s\n", buf);
10963           SendToProgram(buf2, cps);
10964         } else {
10965           if(p != buf2) *p++ = ',';
10966           strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
10967           while(*p) p++;
10968         }
10969       }
10970   }
10971   return buf2;
10972 }
10973
10974 void
10975 StartChessProgram (ChessProgramState *cps)
10976 {
10977     char buf[MSG_SIZ];
10978     int err;
10979
10980     if (appData.noChessProgram) return;
10981     cps->initDone = FALSE;
10982
10983     if (strcmp(cps->host, "localhost") == 0) {
10984         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10985     } else if (*appData.remoteShell == NULLCHAR) {
10986         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10987     } else {
10988         if (*appData.remoteUser == NULLCHAR) {
10989           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10990                     cps->program);
10991         } else {
10992           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10993                     cps->host, appData.remoteUser, cps->program);
10994         }
10995         err = StartChildProcess(buf, "", &cps->pr);
10996     }
10997
10998     if (err != 0) {
10999       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11000         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11001         if(cps != &first) return;
11002         appData.noChessProgram = TRUE;
11003         ThawUI();
11004         SetNCPMode();
11005 //      DisplayFatalError(buf, err, 1);
11006 //      cps->pr = NoProc;
11007 //      cps->isr = NULL;
11008         return;
11009     }
11010
11011     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11012     if (cps->protocolVersion > 1) {
11013       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11014       if(!cps->reload) { // do not clear options when reloading because of -xreuse
11015         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11016         cps->comboCnt = 0;  //                and values of combo boxes
11017       }
11018       SendToProgram(buf, cps);
11019       if(cps->reload) ResendOptions(cps, TRUE);
11020     } else {
11021       SendToProgram("xboard\n", cps);
11022     }
11023 }
11024
11025 void
11026 TwoMachinesEventIfReady P((void))
11027 {
11028   static int curMess = 0;
11029   if (first.lastPing != first.lastPong) {
11030     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11031     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11032     return;
11033   }
11034   if (second.lastPing != second.lastPong) {
11035     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11036     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11037     return;
11038   }
11039   DisplayMessage("", ""); curMess = 0;
11040   TwoMachinesEvent();
11041 }
11042
11043 char *
11044 MakeName (char *template)
11045 {
11046     time_t clock;
11047     struct tm *tm;
11048     static char buf[MSG_SIZ];
11049     char *p = buf;
11050     int i;
11051
11052     clock = time((time_t *)NULL);
11053     tm = localtime(&clock);
11054
11055     while(*p++ = *template++) if(p[-1] == '%') {
11056         switch(*template++) {
11057           case 0:   *p = 0; return buf;
11058           case 'Y': i = tm->tm_year+1900; break;
11059           case 'y': i = tm->tm_year-100; break;
11060           case 'M': i = tm->tm_mon+1; break;
11061           case 'd': i = tm->tm_mday; break;
11062           case 'h': i = tm->tm_hour; break;
11063           case 'm': i = tm->tm_min; break;
11064           case 's': i = tm->tm_sec; break;
11065           default:  i = 0;
11066         }
11067         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11068     }
11069     return buf;
11070 }
11071
11072 int
11073 CountPlayers (char *p)
11074 {
11075     int n = 0;
11076     while(p = strchr(p, '\n')) p++, n++; // count participants
11077     return n;
11078 }
11079
11080 FILE *
11081 WriteTourneyFile (char *results, FILE *f)
11082 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11083     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11084     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11085         // create a file with tournament description
11086         fprintf(f, "-participants {%s}\n", appData.participants);
11087         fprintf(f, "-seedBase %d\n", appData.seedBase);
11088         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11089         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11090         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11091         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11092         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11093         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11094         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11095         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11096         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11097         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11098         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11099         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11100         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11101         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11102         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11103         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11104         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11105         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11106         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11107         fprintf(f, "-smpCores %d\n", appData.smpCores);
11108         if(searchTime > 0)
11109                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11110         else {
11111                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11112                 fprintf(f, "-tc %s\n", appData.timeControl);
11113                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11114         }
11115         fprintf(f, "-results \"%s\"\n", results);
11116     }
11117     return f;
11118 }
11119
11120 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11121
11122 void
11123 Substitute (char *participants, int expunge)
11124 {
11125     int i, changed, changes=0, nPlayers=0;
11126     char *p, *q, *r, buf[MSG_SIZ];
11127     if(participants == NULL) return;
11128     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11129     r = p = participants; q = appData.participants;
11130     while(*p && *p == *q) {
11131         if(*p == '\n') r = p+1, nPlayers++;
11132         p++; q++;
11133     }
11134     if(*p) { // difference
11135         while(*p && *p++ != '\n')
11136                                  ;
11137         while(*q && *q++ != '\n')
11138                                  ;
11139       changed = nPlayers;
11140         changes = 1 + (strcmp(p, q) != 0);
11141     }
11142     if(changes == 1) { // a single engine mnemonic was changed
11143         q = r; while(*q) nPlayers += (*q++ == '\n');
11144         p = buf; while(*r && (*p = *r++) != '\n') p++;
11145         *p = NULLCHAR;
11146         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11147         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11148         if(mnemonic[i]) { // The substitute is valid
11149             FILE *f;
11150             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11151                 flock(fileno(f), LOCK_EX);
11152                 ParseArgsFromFile(f);
11153                 fseek(f, 0, SEEK_SET);
11154                 FREE(appData.participants); appData.participants = participants;
11155                 if(expunge) { // erase results of replaced engine
11156                     int len = strlen(appData.results), w, b, dummy;
11157                     for(i=0; i<len; i++) {
11158                         Pairing(i, nPlayers, &w, &b, &dummy);
11159                         if((w == changed || b == changed) && appData.results[i] == '*') {
11160                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11161                             fclose(f);
11162                             return;
11163                         }
11164                     }
11165                     for(i=0; i<len; i++) {
11166                         Pairing(i, nPlayers, &w, &b, &dummy);
11167                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11168                     }
11169                 }
11170                 WriteTourneyFile(appData.results, f);
11171                 fclose(f); // release lock
11172                 return;
11173             }
11174         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11175     }
11176     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11177     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11178     free(participants);
11179     return;
11180 }
11181
11182 int
11183 CheckPlayers (char *participants)
11184 {
11185         int i;
11186         char buf[MSG_SIZ], *p;
11187         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11188         while(p = strchr(participants, '\n')) {
11189             *p = NULLCHAR;
11190             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11191             if(!mnemonic[i]) {
11192                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11193                 *p = '\n';
11194                 DisplayError(buf, 0);
11195                 return 1;
11196             }
11197             *p = '\n';
11198             participants = p + 1;
11199         }
11200         return 0;
11201 }
11202
11203 int
11204 CreateTourney (char *name)
11205 {
11206         FILE *f;
11207         if(matchMode && strcmp(name, appData.tourneyFile)) {
11208              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11209         }
11210         if(name[0] == NULLCHAR) {
11211             if(appData.participants[0])
11212                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11213             return 0;
11214         }
11215         f = fopen(name, "r");
11216         if(f) { // file exists
11217             ASSIGN(appData.tourneyFile, name);
11218             ParseArgsFromFile(f); // parse it
11219         } else {
11220             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11221             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11222                 DisplayError(_("Not enough participants"), 0);
11223                 return 0;
11224             }
11225             if(CheckPlayers(appData.participants)) return 0;
11226             ASSIGN(appData.tourneyFile, name);
11227             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11228             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11229         }
11230         fclose(f);
11231         appData.noChessProgram = FALSE;
11232         appData.clockMode = TRUE;
11233         SetGNUMode();
11234         return 1;
11235 }
11236
11237 int
11238 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11239 {
11240     char buf[2*MSG_SIZ], *p, *q;
11241     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11242     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11243     skip = !all && group[0]; // if group requested, we start in skip mode
11244     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11245         p = names; q = buf; header = 0;
11246         while(*p && *p != '\n') *q++ = *p++;
11247         *q = 0;
11248         if(*p == '\n') p++;
11249         if(buf[0] == '#') {
11250             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11251             depth++; // we must be entering a new group
11252             if(all) continue; // suppress printing group headers when complete list requested
11253             header = 1;
11254             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11255         }
11256         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11257         if(engineList[i]) free(engineList[i]);
11258         engineList[i] = strdup(buf);
11259         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11260         if(engineMnemonic[i]) free(engineMnemonic[i]);
11261         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11262             strcat(buf, " (");
11263             sscanf(q + 8, "%s", buf + strlen(buf));
11264             strcat(buf, ")");
11265         }
11266         engineMnemonic[i] = strdup(buf);
11267         i++;
11268     }
11269     engineList[i] = engineMnemonic[i] = NULL;
11270     return i;
11271 }
11272
11273 void
11274 SaveEngineSettings (int n)
11275 {
11276     int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11277     if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11278     p = strstr(firstChessProgramNames, currentEngine[n]);
11279     if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11280     optionSettings = ResendOptions(n ? &second : &first, FALSE);
11281     len = strlen(currentEngine[n]);
11282     q = p + len; *p = 0; // cut list into head and tail piece
11283     s = strstr(currentEngine[n], "firstOptions");
11284     if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11285         char *r = s + 14;
11286         while(*r && *r != s[13]) r++;
11287         s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11288         snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11289     } else if(*optionSettings) {
11290         snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11291     }
11292     ASSIGN(currentEngine[n], buf); // updated engine line
11293     len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11294     s = malloc(len);
11295     snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11296     FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11297 }
11298
11299 // following implemented as macro to avoid type limitations
11300 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11301
11302 void
11303 SwapEngines (int n)
11304 {   // swap settings for first engine and other engine (so far only some selected options)
11305     int h;
11306     char *p;
11307     if(n == 0) return;
11308     SWAP(directory, p)
11309     SWAP(chessProgram, p)
11310     SWAP(isUCI, h)
11311     SWAP(hasOwnBookUCI, h)
11312     SWAP(protocolVersion, h)
11313     SWAP(reuse, h)
11314     SWAP(scoreIsAbsolute, h)
11315     SWAP(timeOdds, h)
11316     SWAP(logo, p)
11317     SWAP(pgnName, p)
11318     SWAP(pvSAN, h)
11319     SWAP(engOptions, p)
11320     SWAP(engInitString, p)
11321     SWAP(computerString, p)
11322     SWAP(features, p)
11323     SWAP(fenOverride, p)
11324     SWAP(NPS, h)
11325     SWAP(accumulateTC, h)
11326     SWAP(drawDepth, h)
11327     SWAP(host, p)
11328     SWAP(pseudo, h)
11329 }
11330
11331 int
11332 GetEngineLine (char *s, int n)
11333 {
11334     int i;
11335     char buf[MSG_SIZ];
11336     extern char *icsNames;
11337     if(!s || !*s) return 0;
11338     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11339     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11340     if(!mnemonic[i]) return 0;
11341     if(n == 11) return 1; // just testing if there was a match
11342     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11343     if(n == 1) SwapEngines(n);
11344     ParseArgsFromString(buf);
11345     if(n == 1) SwapEngines(n);
11346     if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11347     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11348         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11349         ParseArgsFromString(buf);
11350     }
11351     return 1;
11352 }
11353
11354 int
11355 SetPlayer (int player, char *p)
11356 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11357     int i;
11358     char buf[MSG_SIZ], *engineName;
11359     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11360     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11361     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11362     if(mnemonic[i]) {
11363         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11364         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11365         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11366         ParseArgsFromString(buf);
11367     } else { // no engine with this nickname is installed!
11368         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11369         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11370         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11371         ModeHighlight();
11372         DisplayError(buf, 0);
11373         return 0;
11374     }
11375     free(engineName);
11376     return i;
11377 }
11378
11379 char *recentEngines;
11380
11381 void
11382 RecentEngineEvent (int nr)
11383 {
11384     int n;
11385 //    SwapEngines(1); // bump first to second
11386 //    ReplaceEngine(&second, 1); // and load it there
11387     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11388     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11389     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11390         ReplaceEngine(&first, 0);
11391         FloatToFront(&appData.recentEngineList, command[n]);
11392     }
11393 }
11394
11395 int
11396 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11397 {   // determine players from game number
11398     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11399
11400     if(appData.tourneyType == 0) {
11401         roundsPerCycle = (nPlayers - 1) | 1;
11402         pairingsPerRound = nPlayers / 2;
11403     } else if(appData.tourneyType > 0) {
11404         roundsPerCycle = nPlayers - appData.tourneyType;
11405         pairingsPerRound = appData.tourneyType;
11406     }
11407     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11408     gamesPerCycle = gamesPerRound * roundsPerCycle;
11409     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11410     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11411     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11412     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11413     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11414     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11415
11416     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11417     if(appData.roundSync) *syncInterval = gamesPerRound;
11418
11419     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11420
11421     if(appData.tourneyType == 0) {
11422         if(curPairing == (nPlayers-1)/2 ) {
11423             *whitePlayer = curRound;
11424             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11425         } else {
11426             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11427             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11428             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11429             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11430         }
11431     } else if(appData.tourneyType > 1) {
11432         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11433         *whitePlayer = curRound + appData.tourneyType;
11434     } else if(appData.tourneyType > 0) {
11435         *whitePlayer = curPairing;
11436         *blackPlayer = curRound + appData.tourneyType;
11437     }
11438
11439     // take care of white/black alternation per round.
11440     // For cycles and games this is already taken care of by default, derived from matchGame!
11441     return curRound & 1;
11442 }
11443
11444 int
11445 NextTourneyGame (int nr, int *swapColors)
11446 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11447     char *p, *q;
11448     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11449     FILE *tf;
11450     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11451     tf = fopen(appData.tourneyFile, "r");
11452     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11453     ParseArgsFromFile(tf); fclose(tf);
11454     InitTimeControls(); // TC might be altered from tourney file
11455
11456     nPlayers = CountPlayers(appData.participants); // count participants
11457     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11458     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11459
11460     if(syncInterval) {
11461         p = q = appData.results;
11462         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11463         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11464             DisplayMessage(_("Waiting for other game(s)"),"");
11465             waitingForGame = TRUE;
11466             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11467             return 0;
11468         }
11469         waitingForGame = FALSE;
11470     }
11471
11472     if(appData.tourneyType < 0) {
11473         if(nr>=0 && !pairingReceived) {
11474             char buf[1<<16];
11475             if(pairing.pr == NoProc) {
11476                 if(!appData.pairingEngine[0]) {
11477                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11478                     return 0;
11479                 }
11480                 StartChessProgram(&pairing); // starts the pairing engine
11481             }
11482             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11483             SendToProgram(buf, &pairing);
11484             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11485             SendToProgram(buf, &pairing);
11486             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11487         }
11488         pairingReceived = 0;                              // ... so we continue here
11489         *swapColors = 0;
11490         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11491         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11492         matchGame = 1; roundNr = nr / syncInterval + 1;
11493     }
11494
11495     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11496
11497     // redefine engines, engine dir, etc.
11498     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11499     if(first.pr == NoProc) {
11500       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11501       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11502     }
11503     if(second.pr == NoProc) {
11504       SwapEngines(1);
11505       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11506       SwapEngines(1);         // and make that valid for second engine by swapping
11507       InitEngine(&second, 1);
11508     }
11509     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11510     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11511     return OK;
11512 }
11513
11514 void
11515 NextMatchGame ()
11516 {   // performs game initialization that does not invoke engines, and then tries to start the game
11517     int res, firstWhite, swapColors = 0;
11518     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11519     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
11520         char buf[MSG_SIZ];
11521         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11522         if(strcmp(buf, currentDebugFile)) { // name has changed
11523             FILE *f = fopen(buf, "w");
11524             if(f) { // if opening the new file failed, just keep using the old one
11525                 ASSIGN(currentDebugFile, buf);
11526                 fclose(debugFP);
11527                 debugFP = f;
11528             }
11529             if(appData.serverFileName) {
11530                 if(serverFP) fclose(serverFP);
11531                 serverFP = fopen(appData.serverFileName, "w");
11532                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11533                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11534             }
11535         }
11536     }
11537     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11538     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11539     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11540     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11541     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11542     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11543     Reset(FALSE, first.pr != NoProc);
11544     res = LoadGameOrPosition(matchGame); // setup game
11545     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11546     if(!res) return; // abort when bad game/pos file
11547     if(appData.epd) {// in EPD mode we make sure first engine is to move
11548         firstWhite = !(forwardMostMove & 1);
11549         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11550         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11551     }
11552     TwoMachinesEvent();
11553 }
11554
11555 void
11556 UserAdjudicationEvent (int result)
11557 {
11558     ChessMove gameResult = GameIsDrawn;
11559
11560     if( result > 0 ) {
11561         gameResult = WhiteWins;
11562     }
11563     else if( result < 0 ) {
11564         gameResult = BlackWins;
11565     }
11566
11567     if( gameMode == TwoMachinesPlay ) {
11568         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11569     }
11570 }
11571
11572
11573 // [HGM] save: calculate checksum of game to make games easily identifiable
11574 int
11575 StringCheckSum (char *s)
11576 {
11577         int i = 0;
11578         if(s==NULL) return 0;
11579         while(*s) i = i*259 + *s++;
11580         return i;
11581 }
11582
11583 int
11584 GameCheckSum ()
11585 {
11586         int i, sum=0;
11587         for(i=backwardMostMove; i<forwardMostMove; i++) {
11588                 sum += pvInfoList[i].depth;
11589                 sum += StringCheckSum(parseList[i]);
11590                 sum += StringCheckSum(commentList[i]);
11591                 sum *= 261;
11592         }
11593         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11594         return sum + StringCheckSum(commentList[i]);
11595 } // end of save patch
11596
11597 void
11598 GameEnds (ChessMove result, char *resultDetails, int whosays)
11599 {
11600     GameMode nextGameMode;
11601     int isIcsGame;
11602     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11603
11604     if(endingGame) return; /* [HGM] crash: forbid recursion */
11605     endingGame = 1;
11606     if(twoBoards) { // [HGM] dual: switch back to one board
11607         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11608         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11609     }
11610     if (appData.debugMode) {
11611       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11612               result, resultDetails ? resultDetails : "(null)", whosays);
11613     }
11614
11615     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11616
11617     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11618
11619     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11620         /* If we are playing on ICS, the server decides when the
11621            game is over, but the engine can offer to draw, claim
11622            a draw, or resign.
11623          */
11624 #if ZIPPY
11625         if (appData.zippyPlay && first.initDone) {
11626             if (result == GameIsDrawn) {
11627                 /* In case draw still needs to be claimed */
11628                 SendToICS(ics_prefix);
11629                 SendToICS("draw\n");
11630             } else if (StrCaseStr(resultDetails, "resign")) {
11631                 SendToICS(ics_prefix);
11632                 SendToICS("resign\n");
11633             }
11634         }
11635 #endif
11636         endingGame = 0; /* [HGM] crash */
11637         return;
11638     }
11639
11640     /* If we're loading the game from a file, stop */
11641     if (whosays == GE_FILE) {
11642       (void) StopLoadGameTimer();
11643       gameFileFP = NULL;
11644     }
11645
11646     /* Cancel draw offers */
11647     first.offeredDraw = second.offeredDraw = 0;
11648
11649     /* If this is an ICS game, only ICS can really say it's done;
11650        if not, anyone can. */
11651     isIcsGame = (gameMode == IcsPlayingWhite ||
11652                  gameMode == IcsPlayingBlack ||
11653                  gameMode == IcsObserving    ||
11654                  gameMode == IcsExamining);
11655
11656     if (!isIcsGame || whosays == GE_ICS) {
11657         /* OK -- not an ICS game, or ICS said it was done */
11658         StopClocks();
11659         if (!isIcsGame && !appData.noChessProgram)
11660           SetUserThinkingEnables();
11661
11662         /* [HGM] if a machine claims the game end we verify this claim */
11663         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11664             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11665                 char claimer;
11666                 ChessMove trueResult = (ChessMove) -1;
11667
11668                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11669                                             first.twoMachinesColor[0] :
11670                                             second.twoMachinesColor[0] ;
11671
11672                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11673                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11674                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11675                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11676                 } else
11677                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11678                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11679                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11680                 } else
11681                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11682                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11683                 }
11684
11685                 // now verify win claims, but not in drop games, as we don't understand those yet
11686                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11687                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11688                     (result == WhiteWins && claimer == 'w' ||
11689                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11690                       if (appData.debugMode) {
11691                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11692                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11693                       }
11694                       if(result != trueResult) {
11695                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11696                               result = claimer == 'w' ? BlackWins : WhiteWins;
11697                               resultDetails = buf;
11698                       }
11699                 } else
11700                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11701                     && (forwardMostMove <= backwardMostMove ||
11702                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11703                         (claimer=='b')==(forwardMostMove&1))
11704                                                                                   ) {
11705                       /* [HGM] verify: draws that were not flagged are false claims */
11706                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11707                       result = claimer == 'w' ? BlackWins : WhiteWins;
11708                       resultDetails = buf;
11709                 }
11710                 /* (Claiming a loss is accepted no questions asked!) */
11711             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11712                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11713                 result = GameUnfinished;
11714                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11715             }
11716             /* [HGM] bare: don't allow bare King to win */
11717             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11718                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11719                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11720                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11721                && result != GameIsDrawn)
11722             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11723                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11724                         int p = (int)boards[forwardMostMove][i][j] - color;
11725                         if(p >= 0 && p <= (int)WhiteKing) k++;
11726                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11727                 }
11728                 if (appData.debugMode) {
11729                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11730                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11731                 }
11732                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11733                         result = GameIsDrawn;
11734                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11735                         resultDetails = buf;
11736                 }
11737             }
11738         }
11739
11740
11741         if(serverMoves != NULL && !loadFlag) { char c = '=';
11742             if(result==WhiteWins) c = '+';
11743             if(result==BlackWins) c = '-';
11744             if(resultDetails != NULL)
11745                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11746         }
11747         if (resultDetails != NULL) {
11748             gameInfo.result = result;
11749             gameInfo.resultDetails = StrSave(resultDetails);
11750
11751             /* display last move only if game was not loaded from file */
11752             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11753                 DisplayMove(currentMove - 1);
11754
11755             if (forwardMostMove != 0) {
11756                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11757                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11758                                                                 ) {
11759                     if (*appData.saveGameFile != NULLCHAR) {
11760                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11761                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11762                         else
11763                         SaveGameToFile(appData.saveGameFile, TRUE);
11764                     } else if (appData.autoSaveGames) {
11765                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11766                     }
11767                     if (*appData.savePositionFile != NULLCHAR) {
11768                         SavePositionToFile(appData.savePositionFile);
11769                     }
11770                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11771                 }
11772             }
11773
11774             /* Tell program how game ended in case it is learning */
11775             /* [HGM] Moved this to after saving the PGN, just in case */
11776             /* engine died and we got here through time loss. In that */
11777             /* case we will get a fatal error writing the pipe, which */
11778             /* would otherwise lose us the PGN.                       */
11779             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11780             /* output during GameEnds should never be fatal anymore   */
11781             if (gameMode == MachinePlaysWhite ||
11782                 gameMode == MachinePlaysBlack ||
11783                 gameMode == TwoMachinesPlay ||
11784                 gameMode == IcsPlayingWhite ||
11785                 gameMode == IcsPlayingBlack ||
11786                 gameMode == BeginningOfGame) {
11787                 char buf[MSG_SIZ];
11788                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11789                         resultDetails);
11790                 if (first.pr != NoProc) {
11791                     SendToProgram(buf, &first);
11792                 }
11793                 if (second.pr != NoProc &&
11794                     gameMode == TwoMachinesPlay) {
11795                     SendToProgram(buf, &second);
11796                 }
11797             }
11798         }
11799
11800         if (appData.icsActive) {
11801             if (appData.quietPlay &&
11802                 (gameMode == IcsPlayingWhite ||
11803                  gameMode == IcsPlayingBlack)) {
11804                 SendToICS(ics_prefix);
11805                 SendToICS("set shout 1\n");
11806             }
11807             nextGameMode = IcsIdle;
11808             ics_user_moved = FALSE;
11809             /* clean up premove.  It's ugly when the game has ended and the
11810              * premove highlights are still on the board.
11811              */
11812             if (gotPremove) {
11813               gotPremove = FALSE;
11814               ClearPremoveHighlights();
11815               DrawPosition(FALSE, boards[currentMove]);
11816             }
11817             if (whosays == GE_ICS) {
11818                 switch (result) {
11819                 case WhiteWins:
11820                     if (gameMode == IcsPlayingWhite)
11821                         PlayIcsWinSound();
11822                     else if(gameMode == IcsPlayingBlack)
11823                         PlayIcsLossSound();
11824                     break;
11825                 case BlackWins:
11826                     if (gameMode == IcsPlayingBlack)
11827                         PlayIcsWinSound();
11828                     else if(gameMode == IcsPlayingWhite)
11829                         PlayIcsLossSound();
11830                     break;
11831                 case GameIsDrawn:
11832                     PlayIcsDrawSound();
11833                     break;
11834                 default:
11835                     PlayIcsUnfinishedSound();
11836                 }
11837             }
11838             if(appData.quitNext) { ExitEvent(0); return; }
11839         } else if (gameMode == EditGame ||
11840                    gameMode == PlayFromGameFile ||
11841                    gameMode == AnalyzeMode ||
11842                    gameMode == AnalyzeFile) {
11843             nextGameMode = gameMode;
11844         } else {
11845             nextGameMode = EndOfGame;
11846         }
11847         pausing = FALSE;
11848         ModeHighlight();
11849     } else {
11850         nextGameMode = gameMode;
11851     }
11852
11853     if (appData.noChessProgram) {
11854         gameMode = nextGameMode;
11855         ModeHighlight();
11856         endingGame = 0; /* [HGM] crash */
11857         return;
11858     }
11859
11860     if (first.reuse) {
11861         /* Put first chess program into idle state */
11862         if (first.pr != NoProc &&
11863             (gameMode == MachinePlaysWhite ||
11864              gameMode == MachinePlaysBlack ||
11865              gameMode == TwoMachinesPlay ||
11866              gameMode == IcsPlayingWhite ||
11867              gameMode == IcsPlayingBlack ||
11868              gameMode == BeginningOfGame)) {
11869             SendToProgram("force\n", &first);
11870             if (first.usePing) {
11871               char buf[MSG_SIZ];
11872               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11873               SendToProgram(buf, &first);
11874             }
11875         }
11876     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11877         /* Kill off first chess program */
11878         if (first.isr != NULL)
11879           RemoveInputSource(first.isr);
11880         first.isr = NULL;
11881
11882         if (first.pr != NoProc) {
11883             ExitAnalyzeMode();
11884             DoSleep( appData.delayBeforeQuit );
11885             SendToProgram("quit\n", &first);
11886             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11887             first.reload = TRUE;
11888         }
11889         first.pr = NoProc;
11890     }
11891     if (second.reuse) {
11892         /* Put second chess program into idle state */
11893         if (second.pr != NoProc &&
11894             gameMode == TwoMachinesPlay) {
11895             SendToProgram("force\n", &second);
11896             if (second.usePing) {
11897               char buf[MSG_SIZ];
11898               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11899               SendToProgram(buf, &second);
11900             }
11901         }
11902     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11903         /* Kill off second chess program */
11904         if (second.isr != NULL)
11905           RemoveInputSource(second.isr);
11906         second.isr = NULL;
11907
11908         if (second.pr != NoProc) {
11909             DoSleep( appData.delayBeforeQuit );
11910             SendToProgram("quit\n", &second);
11911             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11912             second.reload = TRUE;
11913         }
11914         second.pr = NoProc;
11915     }
11916
11917     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11918         char resChar = '=';
11919         switch (result) {
11920         case WhiteWins:
11921           resChar = '+';
11922           if (first.twoMachinesColor[0] == 'w') {
11923             first.matchWins++;
11924           } else {
11925             second.matchWins++;
11926           }
11927           break;
11928         case BlackWins:
11929           resChar = '-';
11930           if (first.twoMachinesColor[0] == 'b') {
11931             first.matchWins++;
11932           } else {
11933             second.matchWins++;
11934           }
11935           break;
11936         case GameUnfinished:
11937           resChar = ' ';
11938         default:
11939           break;
11940         }
11941
11942         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11943         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11944             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11945             ReserveGame(nextGame, resChar); // sets nextGame
11946             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11947             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11948         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11949
11950         if (nextGame <= appData.matchGames && !abortMatch) {
11951             gameMode = nextGameMode;
11952             matchGame = nextGame; // this will be overruled in tourney mode!
11953             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11954             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11955             endingGame = 0; /* [HGM] crash */
11956             return;
11957         } else {
11958             gameMode = nextGameMode;
11959             if(appData.epd) {
11960                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11961                 OutputKibitz(2, buf);
11962                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11963                 OutputKibitz(2, buf);
11964                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11965                 if(second.matchWins) OutputKibitz(2, buf);
11966                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11967                 OutputKibitz(2, buf);
11968             }
11969             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11970                      first.tidy, second.tidy,
11971                      first.matchWins, second.matchWins,
11972                      appData.matchGames - (first.matchWins + second.matchWins));
11973             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11974             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11975             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11976             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11977                 first.twoMachinesColor = "black\n";
11978                 second.twoMachinesColor = "white\n";
11979             } else {
11980                 first.twoMachinesColor = "white\n";
11981                 second.twoMachinesColor = "black\n";
11982             }
11983         }
11984     }
11985     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11986         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11987       ExitAnalyzeMode();
11988     gameMode = nextGameMode;
11989     ModeHighlight();
11990     endingGame = 0;  /* [HGM] crash */
11991     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11992         if(matchMode == TRUE) { // match through command line: exit with or without popup
11993             if(ranking) {
11994                 ToNrEvent(forwardMostMove);
11995                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11996                 else ExitEvent(0);
11997             } else DisplayFatalError(buf, 0, 0);
11998         } else { // match through menu; just stop, with or without popup
11999             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12000             ModeHighlight();
12001             if(ranking){
12002                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12003             } else DisplayNote(buf);
12004       }
12005       if(ranking) free(ranking);
12006     }
12007 }
12008
12009 /* Assumes program was just initialized (initString sent).
12010    Leaves program in force mode. */
12011 void
12012 FeedMovesToProgram (ChessProgramState *cps, int upto)
12013 {
12014     int i;
12015
12016     if (appData.debugMode)
12017       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12018               startedFromSetupPosition ? "position and " : "",
12019               backwardMostMove, upto, cps->which);
12020     if(currentlyInitializedVariant != gameInfo.variant) {
12021       char buf[MSG_SIZ];
12022         // [HGM] variantswitch: make engine aware of new variant
12023         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12024                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12025                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12026         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12027         SendToProgram(buf, cps);
12028         currentlyInitializedVariant = gameInfo.variant;
12029     }
12030     SendToProgram("force\n", cps);
12031     if (startedFromSetupPosition) {
12032         SendBoard(cps, backwardMostMove);
12033     if (appData.debugMode) {
12034         fprintf(debugFP, "feedMoves\n");
12035     }
12036     }
12037     for (i = backwardMostMove; i < upto; i++) {
12038         SendMoveToProgram(i, cps);
12039     }
12040 }
12041
12042
12043 int
12044 ResurrectChessProgram ()
12045 {
12046      /* The chess program may have exited.
12047         If so, restart it and feed it all the moves made so far. */
12048     static int doInit = 0;
12049
12050     if (appData.noChessProgram) return 1;
12051
12052     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12053         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12054         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12055         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12056     } else {
12057         if (first.pr != NoProc) return 1;
12058         StartChessProgram(&first);
12059     }
12060     InitChessProgram(&first, FALSE);
12061     FeedMovesToProgram(&first, currentMove);
12062
12063     if (!first.sendTime) {
12064         /* can't tell gnuchess what its clock should read,
12065            so we bow to its notion. */
12066         ResetClocks();
12067         timeRemaining[0][currentMove] = whiteTimeRemaining;
12068         timeRemaining[1][currentMove] = blackTimeRemaining;
12069     }
12070
12071     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12072                 appData.icsEngineAnalyze) && first.analysisSupport) {
12073       SendToProgram("analyze\n", &first);
12074       first.analyzing = TRUE;
12075     }
12076     return 1;
12077 }
12078
12079 /*
12080  * Button procedures
12081  */
12082 void
12083 Reset (int redraw, int init)
12084 {
12085     int i;
12086
12087     if (appData.debugMode) {
12088         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12089                 redraw, init, gameMode);
12090     }
12091     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12092     deadRanks = 0; // assume entire board is used
12093     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12094     CleanupTail(); // [HGM] vari: delete any stored variations
12095     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12096     pausing = pauseExamInvalid = FALSE;
12097     startedFromSetupPosition = blackPlaysFirst = FALSE;
12098     firstMove = TRUE;
12099     whiteFlag = blackFlag = FALSE;
12100     userOfferedDraw = FALSE;
12101     hintRequested = bookRequested = FALSE;
12102     first.maybeThinking = FALSE;
12103     second.maybeThinking = FALSE;
12104     first.bookSuspend = FALSE; // [HGM] book
12105     second.bookSuspend = FALSE;
12106     thinkOutput[0] = NULLCHAR;
12107     lastHint[0] = NULLCHAR;
12108     ClearGameInfo(&gameInfo);
12109     gameInfo.variant = StringToVariant(appData.variant);
12110     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12111         gameInfo.variant = VariantUnknown;
12112         strncpy(engineVariant, appData.variant, MSG_SIZ);
12113     }
12114     ics_user_moved = ics_clock_paused = FALSE;
12115     ics_getting_history = H_FALSE;
12116     ics_gamenum = -1;
12117     white_holding[0] = black_holding[0] = NULLCHAR;
12118     ClearProgramStats();
12119     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12120
12121     ResetFrontEnd();
12122     ClearHighlights();
12123     flipView = appData.flipView;
12124     ClearPremoveHighlights();
12125     gotPremove = FALSE;
12126     alarmSounded = FALSE;
12127     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12128
12129     GameEnds(EndOfFile, NULL, GE_PLAYER);
12130     if(appData.serverMovesName != NULL) {
12131         /* [HGM] prepare to make moves file for broadcasting */
12132         clock_t t = clock();
12133         if(serverMoves != NULL) fclose(serverMoves);
12134         serverMoves = fopen(appData.serverMovesName, "r");
12135         if(serverMoves != NULL) {
12136             fclose(serverMoves);
12137             /* delay 15 sec before overwriting, so all clients can see end */
12138             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12139         }
12140         serverMoves = fopen(appData.serverMovesName, "w");
12141     }
12142
12143     ExitAnalyzeMode();
12144     gameMode = BeginningOfGame;
12145     ModeHighlight();
12146     if(appData.icsActive) gameInfo.variant = VariantNormal;
12147     currentMove = forwardMostMove = backwardMostMove = 0;
12148     MarkTargetSquares(1);
12149     InitPosition(redraw);
12150     for (i = 0; i < MAX_MOVES; i++) {
12151         if (commentList[i] != NULL) {
12152             free(commentList[i]);
12153             commentList[i] = NULL;
12154         }
12155     }
12156     ResetClocks();
12157     timeRemaining[0][0] = whiteTimeRemaining;
12158     timeRemaining[1][0] = blackTimeRemaining;
12159
12160     if (first.pr == NoProc) {
12161         StartChessProgram(&first);
12162     }
12163     if (init) {
12164             InitChessProgram(&first, startedFromSetupPosition);
12165     }
12166     DisplayTitle("");
12167     DisplayMessage("", "");
12168     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12169     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12170     ClearMap();        // [HGM] exclude: invalidate map
12171 }
12172
12173 void
12174 AutoPlayGameLoop ()
12175 {
12176     for (;;) {
12177         if (!AutoPlayOneMove())
12178           return;
12179         if (matchMode || appData.timeDelay == 0)
12180           continue;
12181         if (appData.timeDelay < 0)
12182           return;
12183         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12184         break;
12185     }
12186 }
12187
12188 void
12189 AnalyzeNextGame()
12190 {
12191     ReloadGame(1); // next game
12192 }
12193
12194 int
12195 AutoPlayOneMove ()
12196 {
12197     int fromX, fromY, toX, toY;
12198
12199     if (appData.debugMode) {
12200       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12201     }
12202
12203     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12204       return FALSE;
12205
12206     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12207       pvInfoList[currentMove].depth = programStats.depth;
12208       pvInfoList[currentMove].score = programStats.score;
12209       pvInfoList[currentMove].time  = 0;
12210       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12211       else { // append analysis of final position as comment
12212         char buf[MSG_SIZ];
12213         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12214         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12215       }
12216       programStats.depth = 0;
12217     }
12218
12219     if (currentMove >= forwardMostMove) {
12220       if(gameMode == AnalyzeFile) {
12221           if(appData.loadGameIndex == -1) {
12222             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12223           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12224           } else {
12225           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12226         }
12227       }
12228 //      gameMode = EndOfGame;
12229 //      ModeHighlight();
12230
12231       /* [AS] Clear current move marker at the end of a game */
12232       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12233
12234       return FALSE;
12235     }
12236
12237     toX = moveList[currentMove][2] - AAA;
12238     toY = moveList[currentMove][3] - ONE;
12239
12240     if (moveList[currentMove][1] == '@') {
12241         if (appData.highlightLastMove) {
12242             SetHighlights(-1, -1, toX, toY);
12243         }
12244     } else {
12245         fromX = moveList[currentMove][0] - AAA;
12246         fromY = moveList[currentMove][1] - ONE;
12247
12248         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12249
12250         if(moveList[currentMove][4] == ';') { // multi-leg
12251             killX = moveList[currentMove][5] - AAA;
12252             killY = moveList[currentMove][6] - ONE;
12253         }
12254         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12255         killX = killY = -1;
12256
12257         if (appData.highlightLastMove) {
12258             SetHighlights(fromX, fromY, toX, toY);
12259         }
12260     }
12261     DisplayMove(currentMove);
12262     SendMoveToProgram(currentMove++, &first);
12263     DisplayBothClocks();
12264     DrawPosition(FALSE, boards[currentMove]);
12265     // [HGM] PV info: always display, routine tests if empty
12266     DisplayComment(currentMove - 1, commentList[currentMove]);
12267     return TRUE;
12268 }
12269
12270
12271 int
12272 LoadGameOneMove (ChessMove readAhead)
12273 {
12274     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12275     char promoChar = NULLCHAR;
12276     ChessMove moveType;
12277     char move[MSG_SIZ];
12278     char *p, *q;
12279
12280     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12281         gameMode != AnalyzeMode && gameMode != Training) {
12282         gameFileFP = NULL;
12283         return FALSE;
12284     }
12285
12286     yyboardindex = forwardMostMove;
12287     if (readAhead != EndOfFile) {
12288       moveType = readAhead;
12289     } else {
12290       if (gameFileFP == NULL)
12291           return FALSE;
12292       moveType = (ChessMove) Myylex();
12293     }
12294
12295     done = FALSE;
12296     switch (moveType) {
12297       case Comment:
12298         if (appData.debugMode)
12299           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12300         p = yy_text;
12301
12302         /* append the comment but don't display it */
12303         AppendComment(currentMove, p, FALSE);
12304         return TRUE;
12305
12306       case WhiteCapturesEnPassant:
12307       case BlackCapturesEnPassant:
12308       case WhitePromotion:
12309       case BlackPromotion:
12310       case WhiteNonPromotion:
12311       case BlackNonPromotion:
12312       case NormalMove:
12313       case FirstLeg:
12314       case WhiteKingSideCastle:
12315       case WhiteQueenSideCastle:
12316       case BlackKingSideCastle:
12317       case BlackQueenSideCastle:
12318       case WhiteKingSideCastleWild:
12319       case WhiteQueenSideCastleWild:
12320       case BlackKingSideCastleWild:
12321       case BlackQueenSideCastleWild:
12322       /* PUSH Fabien */
12323       case WhiteHSideCastleFR:
12324       case WhiteASideCastleFR:
12325       case BlackHSideCastleFR:
12326       case BlackASideCastleFR:
12327       /* POP Fabien */
12328         if (appData.debugMode)
12329           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12330         fromX = currentMoveString[0] - AAA;
12331         fromY = currentMoveString[1] - ONE;
12332         toX = currentMoveString[2] - AAA;
12333         toY = currentMoveString[3] - ONE;
12334         promoChar = currentMoveString[4];
12335         if(promoChar == ';') promoChar = currentMoveString[7];
12336         break;
12337
12338       case WhiteDrop:
12339       case BlackDrop:
12340         if (appData.debugMode)
12341           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12342         fromX = moveType == WhiteDrop ?
12343           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12344         (int) CharToPiece(ToLower(currentMoveString[0]));
12345         fromY = DROP_RANK;
12346         toX = currentMoveString[2] - AAA;
12347         toY = currentMoveString[3] - ONE;
12348         break;
12349
12350       case WhiteWins:
12351       case BlackWins:
12352       case GameIsDrawn:
12353       case GameUnfinished:
12354         if (appData.debugMode)
12355           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12356         p = strchr(yy_text, '{');
12357         if (p == NULL) p = strchr(yy_text, '(');
12358         if (p == NULL) {
12359             p = yy_text;
12360             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12361         } else {
12362             q = strchr(p, *p == '{' ? '}' : ')');
12363             if (q != NULL) *q = NULLCHAR;
12364             p++;
12365         }
12366         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12367         GameEnds(moveType, p, GE_FILE);
12368         done = TRUE;
12369         if (cmailMsgLoaded) {
12370             ClearHighlights();
12371             flipView = WhiteOnMove(currentMove);
12372             if (moveType == GameUnfinished) flipView = !flipView;
12373             if (appData.debugMode)
12374               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12375         }
12376         break;
12377
12378       case EndOfFile:
12379         if (appData.debugMode)
12380           fprintf(debugFP, "Parser hit end of file\n");
12381         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12382           case MT_NONE:
12383           case MT_CHECK:
12384             break;
12385           case MT_CHECKMATE:
12386           case MT_STAINMATE:
12387             if (WhiteOnMove(currentMove)) {
12388                 GameEnds(BlackWins, "Black mates", GE_FILE);
12389             } else {
12390                 GameEnds(WhiteWins, "White mates", GE_FILE);
12391             }
12392             break;
12393           case MT_STALEMATE:
12394             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12395             break;
12396         }
12397         done = TRUE;
12398         break;
12399
12400       case MoveNumberOne:
12401         if (lastLoadGameStart == GNUChessGame) {
12402             /* GNUChessGames have numbers, but they aren't move numbers */
12403             if (appData.debugMode)
12404               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12405                       yy_text, (int) moveType);
12406             return LoadGameOneMove(EndOfFile); /* tail recursion */
12407         }
12408         /* else fall thru */
12409
12410       case XBoardGame:
12411       case GNUChessGame:
12412       case PGNTag:
12413         /* Reached start of next game in file */
12414         if (appData.debugMode)
12415           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12416         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12417           case MT_NONE:
12418           case MT_CHECK:
12419             break;
12420           case MT_CHECKMATE:
12421           case MT_STAINMATE:
12422             if (WhiteOnMove(currentMove)) {
12423                 GameEnds(BlackWins, "Black mates", GE_FILE);
12424             } else {
12425                 GameEnds(WhiteWins, "White mates", GE_FILE);
12426             }
12427             break;
12428           case MT_STALEMATE:
12429             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12430             break;
12431         }
12432         done = TRUE;
12433         break;
12434
12435       case PositionDiagram:     /* should not happen; ignore */
12436       case ElapsedTime:         /* ignore */
12437       case NAG:                 /* ignore */
12438         if (appData.debugMode)
12439           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12440                   yy_text, (int) moveType);
12441         return LoadGameOneMove(EndOfFile); /* tail recursion */
12442
12443       case IllegalMove:
12444         if (appData.testLegality) {
12445             if (appData.debugMode)
12446               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12447             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12448                     (forwardMostMove / 2) + 1,
12449                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12450             DisplayError(move, 0);
12451             done = TRUE;
12452         } else {
12453             if (appData.debugMode)
12454               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12455                       yy_text, currentMoveString);
12456             if(currentMoveString[1] == '@') {
12457                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12458                 fromY = DROP_RANK;
12459             } else {
12460                 fromX = currentMoveString[0] - AAA;
12461                 fromY = currentMoveString[1] - ONE;
12462             }
12463             toX = currentMoveString[2] - AAA;
12464             toY = currentMoveString[3] - ONE;
12465             promoChar = currentMoveString[4];
12466         }
12467         break;
12468
12469       case AmbiguousMove:
12470         if (appData.debugMode)
12471           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12472         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12473                 (forwardMostMove / 2) + 1,
12474                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12475         DisplayError(move, 0);
12476         done = TRUE;
12477         break;
12478
12479       default:
12480       case ImpossibleMove:
12481         if (appData.debugMode)
12482           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12483         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12484                 (forwardMostMove / 2) + 1,
12485                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12486         DisplayError(move, 0);
12487         done = TRUE;
12488         break;
12489     }
12490
12491     if (done) {
12492         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12493             DrawPosition(FALSE, boards[currentMove]);
12494             DisplayBothClocks();
12495             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12496               DisplayComment(currentMove - 1, commentList[currentMove]);
12497         }
12498         (void) StopLoadGameTimer();
12499         gameFileFP = NULL;
12500         cmailOldMove = forwardMostMove;
12501         return FALSE;
12502     } else {
12503         /* currentMoveString is set as a side-effect of yylex */
12504
12505         thinkOutput[0] = NULLCHAR;
12506         MakeMove(fromX, fromY, toX, toY, promoChar);
12507         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12508         currentMove = forwardMostMove;
12509         return TRUE;
12510     }
12511 }
12512
12513 /* Load the nth game from the given file */
12514 int
12515 LoadGameFromFile (char *filename, int n, char *title, int useList)
12516 {
12517     FILE *f;
12518     char buf[MSG_SIZ];
12519
12520     if (strcmp(filename, "-") == 0) {
12521         f = stdin;
12522         title = "stdin";
12523     } else {
12524         f = fopen(filename, "rb");
12525         if (f == NULL) {
12526           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12527             DisplayError(buf, errno);
12528             return FALSE;
12529         }
12530     }
12531     if (fseek(f, 0, 0) == -1) {
12532         /* f is not seekable; probably a pipe */
12533         useList = FALSE;
12534     }
12535     if (useList && n == 0) {
12536         int error = GameListBuild(f);
12537         if (error) {
12538             DisplayError(_("Cannot build game list"), error);
12539         } else if (!ListEmpty(&gameList) &&
12540                    ((ListGame *) gameList.tailPred)->number > 1) {
12541             GameListPopUp(f, title);
12542             return TRUE;
12543         }
12544         GameListDestroy();
12545         n = 1;
12546     }
12547     if (n == 0) n = 1;
12548     return LoadGame(f, n, title, FALSE);
12549 }
12550
12551
12552 void
12553 MakeRegisteredMove ()
12554 {
12555     int fromX, fromY, toX, toY;
12556     char promoChar;
12557     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12558         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12559           case CMAIL_MOVE:
12560           case CMAIL_DRAW:
12561             if (appData.debugMode)
12562               fprintf(debugFP, "Restoring %s for game %d\n",
12563                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12564
12565             thinkOutput[0] = NULLCHAR;
12566             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12567             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12568             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12569             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12570             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12571             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12572             MakeMove(fromX, fromY, toX, toY, promoChar);
12573             ShowMove(fromX, fromY, toX, toY);
12574
12575             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12576               case MT_NONE:
12577               case MT_CHECK:
12578                 break;
12579
12580               case MT_CHECKMATE:
12581               case MT_STAINMATE:
12582                 if (WhiteOnMove(currentMove)) {
12583                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12584                 } else {
12585                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12586                 }
12587                 break;
12588
12589               case MT_STALEMATE:
12590                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12591                 break;
12592             }
12593
12594             break;
12595
12596           case CMAIL_RESIGN:
12597             if (WhiteOnMove(currentMove)) {
12598                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12599             } else {
12600                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12601             }
12602             break;
12603
12604           case CMAIL_ACCEPT:
12605             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12606             break;
12607
12608           default:
12609             break;
12610         }
12611     }
12612
12613     return;
12614 }
12615
12616 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12617 int
12618 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12619 {
12620     int retVal;
12621
12622     if (gameNumber > nCmailGames) {
12623         DisplayError(_("No more games in this message"), 0);
12624         return FALSE;
12625     }
12626     if (f == lastLoadGameFP) {
12627         int offset = gameNumber - lastLoadGameNumber;
12628         if (offset == 0) {
12629             cmailMsg[0] = NULLCHAR;
12630             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12631                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12632                 nCmailMovesRegistered--;
12633             }
12634             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12635             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12636                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12637             }
12638         } else {
12639             if (! RegisterMove()) return FALSE;
12640         }
12641     }
12642
12643     retVal = LoadGame(f, gameNumber, title, useList);
12644
12645     /* Make move registered during previous look at this game, if any */
12646     MakeRegisteredMove();
12647
12648     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12649         commentList[currentMove]
12650           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12651         DisplayComment(currentMove - 1, commentList[currentMove]);
12652     }
12653
12654     return retVal;
12655 }
12656
12657 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12658 int
12659 ReloadGame (int offset)
12660 {
12661     int gameNumber = lastLoadGameNumber + offset;
12662     if (lastLoadGameFP == NULL) {
12663         DisplayError(_("No game has been loaded yet"), 0);
12664         return FALSE;
12665     }
12666     if (gameNumber <= 0) {
12667         DisplayError(_("Can't back up any further"), 0);
12668         return FALSE;
12669     }
12670     if (cmailMsgLoaded) {
12671         return CmailLoadGame(lastLoadGameFP, gameNumber,
12672                              lastLoadGameTitle, lastLoadGameUseList);
12673     } else {
12674         return LoadGame(lastLoadGameFP, gameNumber,
12675                         lastLoadGameTitle, lastLoadGameUseList);
12676     }
12677 }
12678
12679 int keys[EmptySquare+1];
12680
12681 int
12682 PositionMatches (Board b1, Board b2)
12683 {
12684     int r, f, sum=0;
12685     switch(appData.searchMode) {
12686         case 1: return CompareWithRights(b1, b2);
12687         case 2:
12688             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12689                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12690             }
12691             return TRUE;
12692         case 3:
12693             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12694               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12695                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12696             }
12697             return sum==0;
12698         case 4:
12699             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12700                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12701             }
12702             return sum==0;
12703     }
12704     return TRUE;
12705 }
12706
12707 #define Q_PROMO  4
12708 #define Q_EP     3
12709 #define Q_BCASTL 2
12710 #define Q_WCASTL 1
12711
12712 int pieceList[256], quickBoard[256];
12713 ChessSquare pieceType[256] = { EmptySquare };
12714 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12715 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12716 int soughtTotal, turn;
12717 Boolean epOK, flipSearch;
12718
12719 typedef struct {
12720     unsigned char piece, to;
12721 } Move;
12722
12723 #define DSIZE (250000)
12724
12725 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12726 Move *moveDatabase = initialSpace;
12727 unsigned int movePtr, dataSize = DSIZE;
12728
12729 int
12730 MakePieceList (Board board, int *counts)
12731 {
12732     int r, f, n=Q_PROMO, total=0;
12733     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12734     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12735         int sq = f + (r<<4);
12736         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12737             quickBoard[sq] = ++n;
12738             pieceList[n] = sq;
12739             pieceType[n] = board[r][f];
12740             counts[board[r][f]]++;
12741             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12742             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12743             total++;
12744         }
12745     }
12746     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12747     return total;
12748 }
12749
12750 void
12751 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12752 {
12753     int sq = fromX + (fromY<<4);
12754     int piece = quickBoard[sq], rook;
12755     quickBoard[sq] = 0;
12756     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12757     if(piece == pieceList[1] && fromY == toY) {
12758       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12759         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12760         moveDatabase[movePtr++].piece = Q_WCASTL;
12761         quickBoard[sq] = piece;
12762         piece = quickBoard[from]; quickBoard[from] = 0;
12763         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12764       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12765         quickBoard[sq] = 0; // remove Rook
12766         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12767         moveDatabase[movePtr++].piece = Q_WCASTL;
12768         quickBoard[sq] = pieceList[1]; // put King
12769         piece = rook;
12770         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12771       }
12772     } else
12773     if(piece == pieceList[2] && fromY == toY) {
12774       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12775         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12776         moveDatabase[movePtr++].piece = Q_BCASTL;
12777         quickBoard[sq] = piece;
12778         piece = quickBoard[from]; quickBoard[from] = 0;
12779         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12780       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12781         quickBoard[sq] = 0; // remove Rook
12782         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12783         moveDatabase[movePtr++].piece = Q_BCASTL;
12784         quickBoard[sq] = pieceList[2]; // put King
12785         piece = rook;
12786         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12787       }
12788     } else
12789     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12790         quickBoard[(fromY<<4)+toX] = 0;
12791         moveDatabase[movePtr].piece = Q_EP;
12792         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12793         moveDatabase[movePtr].to = sq;
12794     } else
12795     if(promoPiece != pieceType[piece]) {
12796         moveDatabase[movePtr++].piece = Q_PROMO;
12797         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12798     }
12799     moveDatabase[movePtr].piece = piece;
12800     quickBoard[sq] = piece;
12801     movePtr++;
12802 }
12803
12804 int
12805 PackGame (Board board)
12806 {
12807     Move *newSpace = NULL;
12808     moveDatabase[movePtr].piece = 0; // terminate previous game
12809     if(movePtr > dataSize) {
12810         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12811         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12812         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12813         if(newSpace) {
12814             int i;
12815             Move *p = moveDatabase, *q = newSpace;
12816             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12817             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12818             moveDatabase = newSpace;
12819         } else { // calloc failed, we must be out of memory. Too bad...
12820             dataSize = 0; // prevent calloc events for all subsequent games
12821             return 0;     // and signal this one isn't cached
12822         }
12823     }
12824     movePtr++;
12825     MakePieceList(board, counts);
12826     return movePtr;
12827 }
12828
12829 int
12830 QuickCompare (Board board, int *minCounts, int *maxCounts)
12831 {   // compare according to search mode
12832     int r, f;
12833     switch(appData.searchMode)
12834     {
12835       case 1: // exact position match
12836         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12837         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12838             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12839         }
12840         break;
12841       case 2: // can have extra material on empty squares
12842         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12843             if(board[r][f] == EmptySquare) continue;
12844             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12845         }
12846         break;
12847       case 3: // material with exact Pawn structure
12848         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12849             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12850             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12851         } // fall through to material comparison
12852       case 4: // exact material
12853         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12854         break;
12855       case 6: // material range with given imbalance
12856         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12857         // fall through to range comparison
12858       case 5: // material range
12859         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12860     }
12861     return TRUE;
12862 }
12863
12864 int
12865 QuickScan (Board board, Move *move)
12866 {   // reconstruct game,and compare all positions in it
12867     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12868     do {
12869         int piece = move->piece;
12870         int to = move->to, from = pieceList[piece];
12871         if(found < 0) { // if already found just scan to game end for final piece count
12872           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12873            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12874            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12875                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12876             ) {
12877             static int lastCounts[EmptySquare+1];
12878             int i;
12879             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12880             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12881           } else stretch = 0;
12882           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12883           if(found >= 0 && !appData.minPieces) return found;
12884         }
12885         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12886           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12887           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12888             piece = (++move)->piece;
12889             from = pieceList[piece];
12890             counts[pieceType[piece]]--;
12891             pieceType[piece] = (ChessSquare) move->to;
12892             counts[move->to]++;
12893           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12894             counts[pieceType[quickBoard[to]]]--;
12895             quickBoard[to] = 0; total--;
12896             move++;
12897             continue;
12898           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12899             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12900             from  = pieceList[piece]; // so this must be King
12901             quickBoard[from] = 0;
12902             pieceList[piece] = to;
12903             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12904             quickBoard[from] = 0; // rook
12905             quickBoard[to] = piece;
12906             to = move->to; piece = move->piece;
12907             goto aftercastle;
12908           }
12909         }
12910         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12911         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12912         quickBoard[from] = 0;
12913       aftercastle:
12914         quickBoard[to] = piece;
12915         pieceList[piece] = to;
12916         cnt++; turn ^= 3;
12917         move++;
12918     } while(1);
12919 }
12920
12921 void
12922 InitSearch ()
12923 {
12924     int r, f;
12925     flipSearch = FALSE;
12926     CopyBoard(soughtBoard, boards[currentMove]);
12927     soughtTotal = MakePieceList(soughtBoard, maxSought);
12928     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12929     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12930     CopyBoard(reverseBoard, boards[currentMove]);
12931     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12932         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12933         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12934         reverseBoard[r][f] = piece;
12935     }
12936     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12937     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12938     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12939                  || (boards[currentMove][CASTLING][2] == NoRights ||
12940                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12941                  && (boards[currentMove][CASTLING][5] == NoRights ||
12942                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12943       ) {
12944         flipSearch = TRUE;
12945         CopyBoard(flipBoard, soughtBoard);
12946         CopyBoard(rotateBoard, reverseBoard);
12947         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12948             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12949             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12950         }
12951     }
12952     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12953     if(appData.searchMode >= 5) {
12954         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12955         MakePieceList(soughtBoard, minSought);
12956         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12957     }
12958     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12959         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12960 }
12961
12962 GameInfo dummyInfo;
12963 static int creatingBook;
12964
12965 int
12966 GameContainsPosition (FILE *f, ListGame *lg)
12967 {
12968     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12969     int fromX, fromY, toX, toY;
12970     char promoChar;
12971     static int initDone=FALSE;
12972
12973     // weed out games based on numerical tag comparison
12974     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12975     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12976     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12977     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12978     if(!initDone) {
12979         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12980         initDone = TRUE;
12981     }
12982     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12983     else CopyBoard(boards[scratch], initialPosition); // default start position
12984     if(lg->moves) {
12985         turn = btm + 1;
12986         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12987         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12988     }
12989     if(btm) plyNr++;
12990     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12991     fseek(f, lg->offset, 0);
12992     yynewfile(f);
12993     while(1) {
12994         yyboardindex = scratch;
12995         quickFlag = plyNr+1;
12996         next = Myylex();
12997         quickFlag = 0;
12998         switch(next) {
12999             case PGNTag:
13000                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13001             default:
13002                 continue;
13003
13004             case XBoardGame:
13005             case GNUChessGame:
13006                 if(plyNr) return -1; // after we have seen moves, this is for new game
13007               continue;
13008
13009             case AmbiguousMove: // we cannot reconstruct the game beyond these two
13010             case ImpossibleMove:
13011             case WhiteWins: // game ends here with these four
13012             case BlackWins:
13013             case GameIsDrawn:
13014             case GameUnfinished:
13015                 return -1;
13016
13017             case IllegalMove:
13018                 if(appData.testLegality) return -1;
13019             case WhiteCapturesEnPassant:
13020             case BlackCapturesEnPassant:
13021             case WhitePromotion:
13022             case BlackPromotion:
13023             case WhiteNonPromotion:
13024             case BlackNonPromotion:
13025             case NormalMove:
13026             case FirstLeg:
13027             case WhiteKingSideCastle:
13028             case WhiteQueenSideCastle:
13029             case BlackKingSideCastle:
13030             case BlackQueenSideCastle:
13031             case WhiteKingSideCastleWild:
13032             case WhiteQueenSideCastleWild:
13033             case BlackKingSideCastleWild:
13034             case BlackQueenSideCastleWild:
13035             case WhiteHSideCastleFR:
13036             case WhiteASideCastleFR:
13037             case BlackHSideCastleFR:
13038             case BlackASideCastleFR:
13039                 fromX = currentMoveString[0] - AAA;
13040                 fromY = currentMoveString[1] - ONE;
13041                 toX = currentMoveString[2] - AAA;
13042                 toY = currentMoveString[3] - ONE;
13043                 promoChar = currentMoveString[4];
13044                 break;
13045             case WhiteDrop:
13046             case BlackDrop:
13047                 fromX = next == WhiteDrop ?
13048                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
13049                   (int) CharToPiece(ToLower(currentMoveString[0]));
13050                 fromY = DROP_RANK;
13051                 toX = currentMoveString[2] - AAA;
13052                 toY = currentMoveString[3] - ONE;
13053                 promoChar = 0;
13054                 break;
13055         }
13056         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13057         plyNr++;
13058         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13059         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13060         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13061         if(appData.findMirror) {
13062             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13063             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13064         }
13065     }
13066 }
13067
13068 /* Load the nth game from open file f */
13069 int
13070 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13071 {
13072     ChessMove cm;
13073     char buf[MSG_SIZ];
13074     int gn = gameNumber;
13075     ListGame *lg = NULL;
13076     int numPGNTags = 0, i;
13077     int err, pos = -1;
13078     GameMode oldGameMode;
13079     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13080     char oldName[MSG_SIZ];
13081
13082     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13083
13084     if (appData.debugMode)
13085         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13086
13087     if (gameMode == Training )
13088         SetTrainingModeOff();
13089
13090     oldGameMode = gameMode;
13091     if (gameMode != BeginningOfGame) {
13092       Reset(FALSE, TRUE);
13093     }
13094     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13095
13096     gameFileFP = f;
13097     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13098         fclose(lastLoadGameFP);
13099     }
13100
13101     if (useList) {
13102         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13103
13104         if (lg) {
13105             fseek(f, lg->offset, 0);
13106             GameListHighlight(gameNumber);
13107             pos = lg->position;
13108             gn = 1;
13109         }
13110         else {
13111             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13112               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13113             else
13114             DisplayError(_("Game number out of range"), 0);
13115             return FALSE;
13116         }
13117     } else {
13118         GameListDestroy();
13119         if (fseek(f, 0, 0) == -1) {
13120             if (f == lastLoadGameFP ?
13121                 gameNumber == lastLoadGameNumber + 1 :
13122                 gameNumber == 1) {
13123                 gn = 1;
13124             } else {
13125                 DisplayError(_("Can't seek on game file"), 0);
13126                 return FALSE;
13127             }
13128         }
13129     }
13130     lastLoadGameFP = f;
13131     lastLoadGameNumber = gameNumber;
13132     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13133     lastLoadGameUseList = useList;
13134
13135     yynewfile(f);
13136
13137     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13138       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13139                 lg->gameInfo.black);
13140             DisplayTitle(buf);
13141     } else if (*title != NULLCHAR) {
13142         if (gameNumber > 1) {
13143           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13144             DisplayTitle(buf);
13145         } else {
13146             DisplayTitle(title);
13147         }
13148     }
13149
13150     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13151         gameMode = PlayFromGameFile;
13152         ModeHighlight();
13153     }
13154
13155     currentMove = forwardMostMove = backwardMostMove = 0;
13156     CopyBoard(boards[0], initialPosition);
13157     StopClocks();
13158
13159     /*
13160      * Skip the first gn-1 games in the file.
13161      * Also skip over anything that precedes an identifiable
13162      * start of game marker, to avoid being confused by
13163      * garbage at the start of the file.  Currently
13164      * recognized start of game markers are the move number "1",
13165      * the pattern "gnuchess .* game", the pattern
13166      * "^[#;%] [^ ]* game file", and a PGN tag block.
13167      * A game that starts with one of the latter two patterns
13168      * will also have a move number 1, possibly
13169      * following a position diagram.
13170      * 5-4-02: Let's try being more lenient and allowing a game to
13171      * start with an unnumbered move.  Does that break anything?
13172      */
13173     cm = lastLoadGameStart = EndOfFile;
13174     while (gn > 0) {
13175         yyboardindex = forwardMostMove;
13176         cm = (ChessMove) Myylex();
13177         switch (cm) {
13178           case EndOfFile:
13179             if (cmailMsgLoaded) {
13180                 nCmailGames = CMAIL_MAX_GAMES - gn;
13181             } else {
13182                 Reset(TRUE, TRUE);
13183                 DisplayError(_("Game not found in file"), 0);
13184             }
13185             return FALSE;
13186
13187           case GNUChessGame:
13188           case XBoardGame:
13189             gn--;
13190             lastLoadGameStart = cm;
13191             break;
13192
13193           case MoveNumberOne:
13194             switch (lastLoadGameStart) {
13195               case GNUChessGame:
13196               case XBoardGame:
13197               case PGNTag:
13198                 break;
13199               case MoveNumberOne:
13200               case EndOfFile:
13201                 gn--;           /* count this game */
13202                 lastLoadGameStart = cm;
13203                 break;
13204               default:
13205                 /* impossible */
13206                 break;
13207             }
13208             break;
13209
13210           case PGNTag:
13211             switch (lastLoadGameStart) {
13212               case GNUChessGame:
13213               case PGNTag:
13214               case MoveNumberOne:
13215               case EndOfFile:
13216                 gn--;           /* count this game */
13217                 lastLoadGameStart = cm;
13218                 break;
13219               case XBoardGame:
13220                 lastLoadGameStart = cm; /* game counted already */
13221                 break;
13222               default:
13223                 /* impossible */
13224                 break;
13225             }
13226             if (gn > 0) {
13227                 do {
13228                     yyboardindex = forwardMostMove;
13229                     cm = (ChessMove) Myylex();
13230                 } while (cm == PGNTag || cm == Comment);
13231             }
13232             break;
13233
13234           case WhiteWins:
13235           case BlackWins:
13236           case GameIsDrawn:
13237             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13238                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13239                     != CMAIL_OLD_RESULT) {
13240                     nCmailResults ++ ;
13241                     cmailResult[  CMAIL_MAX_GAMES
13242                                 - gn - 1] = CMAIL_OLD_RESULT;
13243                 }
13244             }
13245             break;
13246
13247           case NormalMove:
13248           case FirstLeg:
13249             /* Only a NormalMove can be at the start of a game
13250              * without a position diagram. */
13251             if (lastLoadGameStart == EndOfFile ) {
13252               gn--;
13253               lastLoadGameStart = MoveNumberOne;
13254             }
13255             break;
13256
13257           default:
13258             break;
13259         }
13260     }
13261
13262     if (appData.debugMode)
13263       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13264
13265     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13266
13267     if (cm == XBoardGame) {
13268         /* Skip any header junk before position diagram and/or move 1 */
13269         for (;;) {
13270             yyboardindex = forwardMostMove;
13271             cm = (ChessMove) Myylex();
13272
13273             if (cm == EndOfFile ||
13274                 cm == GNUChessGame || cm == XBoardGame) {
13275                 /* Empty game; pretend end-of-file and handle later */
13276                 cm = EndOfFile;
13277                 break;
13278             }
13279
13280             if (cm == MoveNumberOne || cm == PositionDiagram ||
13281                 cm == PGNTag || cm == Comment)
13282               break;
13283         }
13284     } else if (cm == GNUChessGame) {
13285         if (gameInfo.event != NULL) {
13286             free(gameInfo.event);
13287         }
13288         gameInfo.event = StrSave(yy_text);
13289     }
13290
13291     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13292     while (cm == PGNTag) {
13293         if (appData.debugMode)
13294           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13295         err = ParsePGNTag(yy_text, &gameInfo);
13296         if (!err) numPGNTags++;
13297
13298         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13299         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13300             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13301             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13302             InitPosition(TRUE);
13303             oldVariant = gameInfo.variant;
13304             if (appData.debugMode)
13305               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13306         }
13307
13308
13309         if (gameInfo.fen != NULL) {
13310           Board initial_position;
13311           startedFromSetupPosition = TRUE;
13312           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13313             Reset(TRUE, TRUE);
13314             DisplayError(_("Bad FEN position in file"), 0);
13315             return FALSE;
13316           }
13317           CopyBoard(boards[0], initial_position);
13318           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13319             CopyBoard(initialPosition, initial_position);
13320           if (blackPlaysFirst) {
13321             currentMove = forwardMostMove = backwardMostMove = 1;
13322             CopyBoard(boards[1], initial_position);
13323             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13324             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13325             timeRemaining[0][1] = whiteTimeRemaining;
13326             timeRemaining[1][1] = blackTimeRemaining;
13327             if (commentList[0] != NULL) {
13328               commentList[1] = commentList[0];
13329               commentList[0] = NULL;
13330             }
13331           } else {
13332             currentMove = forwardMostMove = backwardMostMove = 0;
13333           }
13334           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13335           {   int i;
13336               initialRulePlies = FENrulePlies;
13337               for( i=0; i< nrCastlingRights; i++ )
13338                   initialRights[i] = initial_position[CASTLING][i];
13339           }
13340           yyboardindex = forwardMostMove;
13341           free(gameInfo.fen);
13342           gameInfo.fen = NULL;
13343         }
13344
13345         yyboardindex = forwardMostMove;
13346         cm = (ChessMove) Myylex();
13347
13348         /* Handle comments interspersed among the tags */
13349         while (cm == Comment) {
13350             char *p;
13351             if (appData.debugMode)
13352               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13353             p = yy_text;
13354             AppendComment(currentMove, p, FALSE);
13355             yyboardindex = forwardMostMove;
13356             cm = (ChessMove) Myylex();
13357         }
13358     }
13359
13360     /* don't rely on existence of Event tag since if game was
13361      * pasted from clipboard the Event tag may not exist
13362      */
13363     if (numPGNTags > 0){
13364         char *tags;
13365         if (gameInfo.variant == VariantNormal) {
13366           VariantClass v = StringToVariant(gameInfo.event);
13367           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13368           if(v < VariantShogi) gameInfo.variant = v;
13369         }
13370         if (!matchMode) {
13371           if( appData.autoDisplayTags ) {
13372             tags = PGNTags(&gameInfo);
13373             TagsPopUp(tags, CmailMsg());
13374             free(tags);
13375           }
13376         }
13377     } else {
13378         /* Make something up, but don't display it now */
13379         SetGameInfo();
13380         TagsPopDown();
13381     }
13382
13383     if (cm == PositionDiagram) {
13384         int i, j;
13385         char *p;
13386         Board initial_position;
13387
13388         if (appData.debugMode)
13389           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13390
13391         if (!startedFromSetupPosition) {
13392             p = yy_text;
13393             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13394               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13395                 switch (*p) {
13396                   case '{':
13397                   case '[':
13398                   case '-':
13399                   case ' ':
13400                   case '\t':
13401                   case '\n':
13402                   case '\r':
13403                     break;
13404                   default:
13405                     initial_position[i][j++] = CharToPiece(*p);
13406                     break;
13407                 }
13408             while (*p == ' ' || *p == '\t' ||
13409                    *p == '\n' || *p == '\r') p++;
13410
13411             if (strncmp(p, "black", strlen("black"))==0)
13412               blackPlaysFirst = TRUE;
13413             else
13414               blackPlaysFirst = FALSE;
13415             startedFromSetupPosition = TRUE;
13416
13417             CopyBoard(boards[0], initial_position);
13418             if (blackPlaysFirst) {
13419                 currentMove = forwardMostMove = backwardMostMove = 1;
13420                 CopyBoard(boards[1], initial_position);
13421                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13422                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13423                 timeRemaining[0][1] = whiteTimeRemaining;
13424                 timeRemaining[1][1] = blackTimeRemaining;
13425                 if (commentList[0] != NULL) {
13426                     commentList[1] = commentList[0];
13427                     commentList[0] = NULL;
13428                 }
13429             } else {
13430                 currentMove = forwardMostMove = backwardMostMove = 0;
13431             }
13432         }
13433         yyboardindex = forwardMostMove;
13434         cm = (ChessMove) Myylex();
13435     }
13436
13437   if(!creatingBook) {
13438     if (first.pr == NoProc) {
13439         StartChessProgram(&first);
13440     }
13441     InitChessProgram(&first, FALSE);
13442     if(gameInfo.variant == VariantUnknown && *oldName) {
13443         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13444         gameInfo.variant = v;
13445     }
13446     SendToProgram("force\n", &first);
13447     if (startedFromSetupPosition) {
13448         SendBoard(&first, forwardMostMove);
13449     if (appData.debugMode) {
13450         fprintf(debugFP, "Load Game\n");
13451     }
13452         DisplayBothClocks();
13453     }
13454   }
13455
13456     /* [HGM] server: flag to write setup moves in broadcast file as one */
13457     loadFlag = appData.suppressLoadMoves;
13458
13459     while (cm == Comment) {
13460         char *p;
13461         if (appData.debugMode)
13462           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13463         p = yy_text;
13464         AppendComment(currentMove, p, FALSE);
13465         yyboardindex = forwardMostMove;
13466         cm = (ChessMove) Myylex();
13467     }
13468
13469     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13470         cm == WhiteWins || cm == BlackWins ||
13471         cm == GameIsDrawn || cm == GameUnfinished) {
13472         DisplayMessage("", _("No moves in game"));
13473         if (cmailMsgLoaded) {
13474             if (appData.debugMode)
13475               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13476             ClearHighlights();
13477             flipView = FALSE;
13478         }
13479         DrawPosition(FALSE, boards[currentMove]);
13480         DisplayBothClocks();
13481         gameMode = EditGame;
13482         ModeHighlight();
13483         gameFileFP = NULL;
13484         cmailOldMove = 0;
13485         return TRUE;
13486     }
13487
13488     // [HGM] PV info: routine tests if comment empty
13489     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13490         DisplayComment(currentMove - 1, commentList[currentMove]);
13491     }
13492     if (!matchMode && appData.timeDelay != 0)
13493       DrawPosition(FALSE, boards[currentMove]);
13494
13495     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13496       programStats.ok_to_send = 1;
13497     }
13498
13499     /* if the first token after the PGN tags is a move
13500      * and not move number 1, retrieve it from the parser
13501      */
13502     if (cm != MoveNumberOne)
13503         LoadGameOneMove(cm);
13504
13505     /* load the remaining moves from the file */
13506     while (LoadGameOneMove(EndOfFile)) {
13507       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13508       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13509     }
13510
13511     /* rewind to the start of the game */
13512     currentMove = backwardMostMove;
13513
13514     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13515
13516     if (oldGameMode == AnalyzeFile) {
13517       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13518       AnalyzeFileEvent();
13519     } else
13520     if (oldGameMode == AnalyzeMode) {
13521       AnalyzeFileEvent();
13522     }
13523
13524     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13525         long int w, b; // [HGM] adjourn: restore saved clock times
13526         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13527         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13528             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13529             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13530         }
13531     }
13532
13533     if(creatingBook) return TRUE;
13534     if (!matchMode && pos > 0) {
13535         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13536     } else
13537     if (matchMode || appData.timeDelay == 0) {
13538       ToEndEvent();
13539     } else if (appData.timeDelay > 0) {
13540       AutoPlayGameLoop();
13541     }
13542
13543     if (appData.debugMode)
13544         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13545
13546     loadFlag = 0; /* [HGM] true game starts */
13547     return TRUE;
13548 }
13549
13550 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13551 int
13552 ReloadPosition (int offset)
13553 {
13554     int positionNumber = lastLoadPositionNumber + offset;
13555     if (lastLoadPositionFP == NULL) {
13556         DisplayError(_("No position has been loaded yet"), 0);
13557         return FALSE;
13558     }
13559     if (positionNumber <= 0) {
13560         DisplayError(_("Can't back up any further"), 0);
13561         return FALSE;
13562     }
13563     return LoadPosition(lastLoadPositionFP, positionNumber,
13564                         lastLoadPositionTitle);
13565 }
13566
13567 /* Load the nth position from the given file */
13568 int
13569 LoadPositionFromFile (char *filename, int n, char *title)
13570 {
13571     FILE *f;
13572     char buf[MSG_SIZ];
13573
13574     if (strcmp(filename, "-") == 0) {
13575         return LoadPosition(stdin, n, "stdin");
13576     } else {
13577         f = fopen(filename, "rb");
13578         if (f == NULL) {
13579             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13580             DisplayError(buf, errno);
13581             return FALSE;
13582         } else {
13583             return LoadPosition(f, n, title);
13584         }
13585     }
13586 }
13587
13588 /* Load the nth position from the given open file, and close it */
13589 int
13590 LoadPosition (FILE *f, int positionNumber, char *title)
13591 {
13592     char *p, line[MSG_SIZ];
13593     Board initial_position;
13594     int i, j, fenMode, pn;
13595
13596     if (gameMode == Training )
13597         SetTrainingModeOff();
13598
13599     if (gameMode != BeginningOfGame) {
13600         Reset(FALSE, TRUE);
13601     }
13602     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13603         fclose(lastLoadPositionFP);
13604     }
13605     if (positionNumber == 0) positionNumber = 1;
13606     lastLoadPositionFP = f;
13607     lastLoadPositionNumber = positionNumber;
13608     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13609     if (first.pr == NoProc && !appData.noChessProgram) {
13610       StartChessProgram(&first);
13611       InitChessProgram(&first, FALSE);
13612     }
13613     pn = positionNumber;
13614     if (positionNumber < 0) {
13615         /* Negative position number means to seek to that byte offset */
13616         if (fseek(f, -positionNumber, 0) == -1) {
13617             DisplayError(_("Can't seek on position file"), 0);
13618             return FALSE;
13619         };
13620         pn = 1;
13621     } else {
13622         if (fseek(f, 0, 0) == -1) {
13623             if (f == lastLoadPositionFP ?
13624                 positionNumber == lastLoadPositionNumber + 1 :
13625                 positionNumber == 1) {
13626                 pn = 1;
13627             } else {
13628                 DisplayError(_("Can't seek on position file"), 0);
13629                 return FALSE;
13630             }
13631         }
13632     }
13633     /* See if this file is FEN or old-style xboard */
13634     if (fgets(line, MSG_SIZ, f) == NULL) {
13635         DisplayError(_("Position not found in file"), 0);
13636         return FALSE;
13637     }
13638     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13639     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13640
13641     if (pn >= 2) {
13642         if (fenMode || line[0] == '#') pn--;
13643         while (pn > 0) {
13644             /* skip positions before number pn */
13645             if (fgets(line, MSG_SIZ, f) == NULL) {
13646                 Reset(TRUE, TRUE);
13647                 DisplayError(_("Position not found in file"), 0);
13648                 return FALSE;
13649             }
13650             if (fenMode || line[0] == '#') pn--;
13651         }
13652     }
13653
13654     if (fenMode) {
13655         char *p;
13656         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13657             DisplayError(_("Bad FEN position in file"), 0);
13658             return FALSE;
13659         }
13660         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13661             sscanf(p+4, "%[^;]", bestMove);
13662         } else *bestMove = NULLCHAR;
13663         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13664             sscanf(p+4, "%[^;]", avoidMove);
13665         } else *avoidMove = NULLCHAR;
13666     } else {
13667         (void) fgets(line, MSG_SIZ, f);
13668         (void) fgets(line, MSG_SIZ, f);
13669
13670         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13671             (void) fgets(line, MSG_SIZ, f);
13672             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13673                 if (*p == ' ')
13674                   continue;
13675                 initial_position[i][j++] = CharToPiece(*p);
13676             }
13677         }
13678
13679         blackPlaysFirst = FALSE;
13680         if (!feof(f)) {
13681             (void) fgets(line, MSG_SIZ, f);
13682             if (strncmp(line, "black", strlen("black"))==0)
13683               blackPlaysFirst = TRUE;
13684         }
13685     }
13686     startedFromSetupPosition = TRUE;
13687
13688     CopyBoard(boards[0], initial_position);
13689     if (blackPlaysFirst) {
13690         currentMove = forwardMostMove = backwardMostMove = 1;
13691         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13692         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13693         CopyBoard(boards[1], initial_position);
13694         DisplayMessage("", _("Black to play"));
13695     } else {
13696         currentMove = forwardMostMove = backwardMostMove = 0;
13697         DisplayMessage("", _("White to play"));
13698     }
13699     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13700     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13701         SendToProgram("force\n", &first);
13702         SendBoard(&first, forwardMostMove);
13703     }
13704     if (appData.debugMode) {
13705 int i, j;
13706   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13707   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13708         fprintf(debugFP, "Load Position\n");
13709     }
13710
13711     if (positionNumber > 1) {
13712       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13713         DisplayTitle(line);
13714     } else {
13715         DisplayTitle(title);
13716     }
13717     gameMode = EditGame;
13718     ModeHighlight();
13719     ResetClocks();
13720     timeRemaining[0][1] = whiteTimeRemaining;
13721     timeRemaining[1][1] = blackTimeRemaining;
13722     DrawPosition(FALSE, boards[currentMove]);
13723
13724     return TRUE;
13725 }
13726
13727
13728 void
13729 CopyPlayerNameIntoFileName (char **dest, char *src)
13730 {
13731     while (*src != NULLCHAR && *src != ',') {
13732         if (*src == ' ') {
13733             *(*dest)++ = '_';
13734             src++;
13735         } else {
13736             *(*dest)++ = *src++;
13737         }
13738     }
13739 }
13740
13741 char *
13742 DefaultFileName (char *ext)
13743 {
13744     static char def[MSG_SIZ];
13745     char *p;
13746
13747     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13748         p = def;
13749         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13750         *p++ = '-';
13751         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13752         *p++ = '.';
13753         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13754     } else {
13755         def[0] = NULLCHAR;
13756     }
13757     return def;
13758 }
13759
13760 /* Save the current game to the given file */
13761 int
13762 SaveGameToFile (char *filename, int append)
13763 {
13764     FILE *f;
13765     char buf[MSG_SIZ];
13766     int result, i, t,tot=0;
13767
13768     if (strcmp(filename, "-") == 0) {
13769         return SaveGame(stdout, 0, NULL);
13770     } else {
13771         for(i=0; i<10; i++) { // upto 10 tries
13772              f = fopen(filename, append ? "a" : "w");
13773              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13774              if(f || errno != 13) break;
13775              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13776              tot += t;
13777         }
13778         if (f == NULL) {
13779             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13780             DisplayError(buf, errno);
13781             return FALSE;
13782         } else {
13783             safeStrCpy(buf, lastMsg, MSG_SIZ);
13784             DisplayMessage(_("Waiting for access to save file"), "");
13785             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13786             DisplayMessage(_("Saving game"), "");
13787             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13788             result = SaveGame(f, 0, NULL);
13789             DisplayMessage(buf, "");
13790             return result;
13791         }
13792     }
13793 }
13794
13795 char *
13796 SavePart (char *str)
13797 {
13798     static char buf[MSG_SIZ];
13799     char *p;
13800
13801     p = strchr(str, ' ');
13802     if (p == NULL) return str;
13803     strncpy(buf, str, p - str);
13804     buf[p - str] = NULLCHAR;
13805     return buf;
13806 }
13807
13808 #define PGN_MAX_LINE 75
13809
13810 #define PGN_SIDE_WHITE  0
13811 #define PGN_SIDE_BLACK  1
13812
13813 static int
13814 FindFirstMoveOutOfBook (int side)
13815 {
13816     int result = -1;
13817
13818     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13819         int index = backwardMostMove;
13820         int has_book_hit = 0;
13821
13822         if( (index % 2) != side ) {
13823             index++;
13824         }
13825
13826         while( index < forwardMostMove ) {
13827             /* Check to see if engine is in book */
13828             int depth = pvInfoList[index].depth;
13829             int score = pvInfoList[index].score;
13830             int in_book = 0;
13831
13832             if( depth <= 2 ) {
13833                 in_book = 1;
13834             }
13835             else if( score == 0 && depth == 63 ) {
13836                 in_book = 1; /* Zappa */
13837             }
13838             else if( score == 2 && depth == 99 ) {
13839                 in_book = 1; /* Abrok */
13840             }
13841
13842             has_book_hit += in_book;
13843
13844             if( ! in_book ) {
13845                 result = index;
13846
13847                 break;
13848             }
13849
13850             index += 2;
13851         }
13852     }
13853
13854     return result;
13855 }
13856
13857 void
13858 GetOutOfBookInfo (char * buf)
13859 {
13860     int oob[2];
13861     int i;
13862     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13863
13864     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13865     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13866
13867     *buf = '\0';
13868
13869     if( oob[0] >= 0 || oob[1] >= 0 ) {
13870         for( i=0; i<2; i++ ) {
13871             int idx = oob[i];
13872
13873             if( idx >= 0 ) {
13874                 if( i > 0 && oob[0] >= 0 ) {
13875                     strcat( buf, "   " );
13876                 }
13877
13878                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13879                 sprintf( buf+strlen(buf), "%s%.2f",
13880                     pvInfoList[idx].score >= 0 ? "+" : "",
13881                     pvInfoList[idx].score / 100.0 );
13882             }
13883         }
13884     }
13885 }
13886
13887 /* Save game in PGN style */
13888 static void
13889 SaveGamePGN2 (FILE *f)
13890 {
13891     int i, offset, linelen, newblock;
13892 //    char *movetext;
13893     char numtext[32];
13894     int movelen, numlen, blank;
13895     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13896
13897     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13898
13899     PrintPGNTags(f, &gameInfo);
13900
13901     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13902
13903     if (backwardMostMove > 0 || startedFromSetupPosition) {
13904         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13905         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13906         fprintf(f, "\n{--------------\n");
13907         PrintPosition(f, backwardMostMove);
13908         fprintf(f, "--------------}\n");
13909         free(fen);
13910     }
13911     else {
13912         /* [AS] Out of book annotation */
13913         if( appData.saveOutOfBookInfo ) {
13914             char buf[64];
13915
13916             GetOutOfBookInfo( buf );
13917
13918             if( buf[0] != '\0' ) {
13919                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13920             }
13921         }
13922
13923         fprintf(f, "\n");
13924     }
13925
13926     i = backwardMostMove;
13927     linelen = 0;
13928     newblock = TRUE;
13929
13930     while (i < forwardMostMove) {
13931         /* Print comments preceding this move */
13932         if (commentList[i] != NULL) {
13933             if (linelen > 0) fprintf(f, "\n");
13934             fprintf(f, "%s", commentList[i]);
13935             linelen = 0;
13936             newblock = TRUE;
13937         }
13938
13939         /* Format move number */
13940         if ((i % 2) == 0)
13941           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13942         else
13943           if (newblock)
13944             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13945           else
13946             numtext[0] = NULLCHAR;
13947
13948         numlen = strlen(numtext);
13949         newblock = FALSE;
13950
13951         /* Print move number */
13952         blank = linelen > 0 && numlen > 0;
13953         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13954             fprintf(f, "\n");
13955             linelen = 0;
13956             blank = 0;
13957         }
13958         if (blank) {
13959             fprintf(f, " ");
13960             linelen++;
13961         }
13962         fprintf(f, "%s", numtext);
13963         linelen += numlen;
13964
13965         /* Get move */
13966         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13967         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13968
13969         /* Print move */
13970         blank = linelen > 0 && movelen > 0;
13971         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13972             fprintf(f, "\n");
13973             linelen = 0;
13974             blank = 0;
13975         }
13976         if (blank) {
13977             fprintf(f, " ");
13978             linelen++;
13979         }
13980         fprintf(f, "%s", move_buffer);
13981         linelen += movelen;
13982
13983         /* [AS] Add PV info if present */
13984         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13985             /* [HGM] add time */
13986             char buf[MSG_SIZ]; int seconds;
13987
13988             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13989
13990             if( seconds <= 0)
13991               buf[0] = 0;
13992             else
13993               if( seconds < 30 )
13994                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13995               else
13996                 {
13997                   seconds = (seconds + 4)/10; // round to full seconds
13998                   if( seconds < 60 )
13999                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14000                   else
14001                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14002                 }
14003
14004             if(appData.cumulativeTimePGN) {
14005                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14006             }
14007
14008             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14009                       pvInfoList[i].score >= 0 ? "+" : "",
14010                       pvInfoList[i].score / 100.0,
14011                       pvInfoList[i].depth,
14012                       buf );
14013
14014             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14015
14016             /* Print score/depth */
14017             blank = linelen > 0 && movelen > 0;
14018             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14019                 fprintf(f, "\n");
14020                 linelen = 0;
14021                 blank = 0;
14022             }
14023             if (blank) {
14024                 fprintf(f, " ");
14025                 linelen++;
14026             }
14027             fprintf(f, "%s", move_buffer);
14028             linelen += movelen;
14029         }
14030
14031         i++;
14032     }
14033
14034     /* Start a new line */
14035     if (linelen > 0) fprintf(f, "\n");
14036
14037     /* Print comments after last move */
14038     if (commentList[i] != NULL) {
14039         fprintf(f, "%s\n", commentList[i]);
14040     }
14041
14042     /* Print result */
14043     if (gameInfo.resultDetails != NULL &&
14044         gameInfo.resultDetails[0] != NULLCHAR) {
14045         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14046         if(gameInfo.result == GameUnfinished && appData.clockMode &&
14047            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14048             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14049         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14050     } else {
14051         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14052     }
14053 }
14054
14055 /* Save game in PGN style and close the file */
14056 int
14057 SaveGamePGN (FILE *f)
14058 {
14059     SaveGamePGN2(f);
14060     fclose(f);
14061     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14062     return TRUE;
14063 }
14064
14065 /* Save game in old style and close the file */
14066 int
14067 SaveGameOldStyle (FILE *f)
14068 {
14069     int i, offset;
14070     time_t tm;
14071
14072     tm = time((time_t *) NULL);
14073
14074     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14075     PrintOpponents(f);
14076
14077     if (backwardMostMove > 0 || startedFromSetupPosition) {
14078         fprintf(f, "\n[--------------\n");
14079         PrintPosition(f, backwardMostMove);
14080         fprintf(f, "--------------]\n");
14081     } else {
14082         fprintf(f, "\n");
14083     }
14084
14085     i = backwardMostMove;
14086     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14087
14088     while (i < forwardMostMove) {
14089         if (commentList[i] != NULL) {
14090             fprintf(f, "[%s]\n", commentList[i]);
14091         }
14092
14093         if ((i % 2) == 1) {
14094             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14095             i++;
14096         } else {
14097             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14098             i++;
14099             if (commentList[i] != NULL) {
14100                 fprintf(f, "\n");
14101                 continue;
14102             }
14103             if (i >= forwardMostMove) {
14104                 fprintf(f, "\n");
14105                 break;
14106             }
14107             fprintf(f, "%s\n", parseList[i]);
14108             i++;
14109         }
14110     }
14111
14112     if (commentList[i] != NULL) {
14113         fprintf(f, "[%s]\n", commentList[i]);
14114     }
14115
14116     /* This isn't really the old style, but it's close enough */
14117     if (gameInfo.resultDetails != NULL &&
14118         gameInfo.resultDetails[0] != NULLCHAR) {
14119         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14120                 gameInfo.resultDetails);
14121     } else {
14122         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14123     }
14124
14125     fclose(f);
14126     return TRUE;
14127 }
14128
14129 /* Save the current game to open file f and close the file */
14130 int
14131 SaveGame (FILE *f, int dummy, char *dummy2)
14132 {
14133     if (gameMode == EditPosition) EditPositionDone(TRUE);
14134     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14135     if (appData.oldSaveStyle)
14136       return SaveGameOldStyle(f);
14137     else
14138       return SaveGamePGN(f);
14139 }
14140
14141 /* Save the current position to the given file */
14142 int
14143 SavePositionToFile (char *filename)
14144 {
14145     FILE *f;
14146     char buf[MSG_SIZ];
14147
14148     if (strcmp(filename, "-") == 0) {
14149         return SavePosition(stdout, 0, NULL);
14150     } else {
14151         f = fopen(filename, "a");
14152         if (f == NULL) {
14153             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14154             DisplayError(buf, errno);
14155             return FALSE;
14156         } else {
14157             safeStrCpy(buf, lastMsg, MSG_SIZ);
14158             DisplayMessage(_("Waiting for access to save file"), "");
14159             flock(fileno(f), LOCK_EX); // [HGM] lock
14160             DisplayMessage(_("Saving position"), "");
14161             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14162             SavePosition(f, 0, NULL);
14163             DisplayMessage(buf, "");
14164             return TRUE;
14165         }
14166     }
14167 }
14168
14169 /* Save the current position to the given open file and close the file */
14170 int
14171 SavePosition (FILE *f, int dummy, char *dummy2)
14172 {
14173     time_t tm;
14174     char *fen;
14175
14176     if (gameMode == EditPosition) EditPositionDone(TRUE);
14177     if (appData.oldSaveStyle) {
14178         tm = time((time_t *) NULL);
14179
14180         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14181         PrintOpponents(f);
14182         fprintf(f, "[--------------\n");
14183         PrintPosition(f, currentMove);
14184         fprintf(f, "--------------]\n");
14185     } else {
14186         fen = PositionToFEN(currentMove, NULL, 1);
14187         fprintf(f, "%s\n", fen);
14188         free(fen);
14189     }
14190     fclose(f);
14191     return TRUE;
14192 }
14193
14194 void
14195 ReloadCmailMsgEvent (int unregister)
14196 {
14197 #if !WIN32
14198     static char *inFilename = NULL;
14199     static char *outFilename;
14200     int i;
14201     struct stat inbuf, outbuf;
14202     int status;
14203
14204     /* Any registered moves are unregistered if unregister is set, */
14205     /* i.e. invoked by the signal handler */
14206     if (unregister) {
14207         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14208             cmailMoveRegistered[i] = FALSE;
14209             if (cmailCommentList[i] != NULL) {
14210                 free(cmailCommentList[i]);
14211                 cmailCommentList[i] = NULL;
14212             }
14213         }
14214         nCmailMovesRegistered = 0;
14215     }
14216
14217     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14218         cmailResult[i] = CMAIL_NOT_RESULT;
14219     }
14220     nCmailResults = 0;
14221
14222     if (inFilename == NULL) {
14223         /* Because the filenames are static they only get malloced once  */
14224         /* and they never get freed                                      */
14225         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14226         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14227
14228         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14229         sprintf(outFilename, "%s.out", appData.cmailGameName);
14230     }
14231
14232     status = stat(outFilename, &outbuf);
14233     if (status < 0) {
14234         cmailMailedMove = FALSE;
14235     } else {
14236         status = stat(inFilename, &inbuf);
14237         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14238     }
14239
14240     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14241        counts the games, notes how each one terminated, etc.
14242
14243        It would be nice to remove this kludge and instead gather all
14244        the information while building the game list.  (And to keep it
14245        in the game list nodes instead of having a bunch of fixed-size
14246        parallel arrays.)  Note this will require getting each game's
14247        termination from the PGN tags, as the game list builder does
14248        not process the game moves.  --mann
14249        */
14250     cmailMsgLoaded = TRUE;
14251     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14252
14253     /* Load first game in the file or popup game menu */
14254     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14255
14256 #endif /* !WIN32 */
14257     return;
14258 }
14259
14260 int
14261 RegisterMove ()
14262 {
14263     FILE *f;
14264     char string[MSG_SIZ];
14265
14266     if (   cmailMailedMove
14267         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14268         return TRUE;            /* Allow free viewing  */
14269     }
14270
14271     /* Unregister move to ensure that we don't leave RegisterMove        */
14272     /* with the move registered when the conditions for registering no   */
14273     /* longer hold                                                       */
14274     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14275         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14276         nCmailMovesRegistered --;
14277
14278         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14279           {
14280               free(cmailCommentList[lastLoadGameNumber - 1]);
14281               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14282           }
14283     }
14284
14285     if (cmailOldMove == -1) {
14286         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14287         return FALSE;
14288     }
14289
14290     if (currentMove > cmailOldMove + 1) {
14291         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14292         return FALSE;
14293     }
14294
14295     if (currentMove < cmailOldMove) {
14296         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14297         return FALSE;
14298     }
14299
14300     if (forwardMostMove > currentMove) {
14301         /* Silently truncate extra moves */
14302         TruncateGame();
14303     }
14304
14305     if (   (currentMove == cmailOldMove + 1)
14306         || (   (currentMove == cmailOldMove)
14307             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14308                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14309         if (gameInfo.result != GameUnfinished) {
14310             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14311         }
14312
14313         if (commentList[currentMove] != NULL) {
14314             cmailCommentList[lastLoadGameNumber - 1]
14315               = StrSave(commentList[currentMove]);
14316         }
14317         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14318
14319         if (appData.debugMode)
14320           fprintf(debugFP, "Saving %s for game %d\n",
14321                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14322
14323         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14324
14325         f = fopen(string, "w");
14326         if (appData.oldSaveStyle) {
14327             SaveGameOldStyle(f); /* also closes the file */
14328
14329             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14330             f = fopen(string, "w");
14331             SavePosition(f, 0, NULL); /* also closes the file */
14332         } else {
14333             fprintf(f, "{--------------\n");
14334             PrintPosition(f, currentMove);
14335             fprintf(f, "--------------}\n\n");
14336
14337             SaveGame(f, 0, NULL); /* also closes the file*/
14338         }
14339
14340         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14341         nCmailMovesRegistered ++;
14342     } else if (nCmailGames == 1) {
14343         DisplayError(_("You have not made a move yet"), 0);
14344         return FALSE;
14345     }
14346
14347     return TRUE;
14348 }
14349
14350 void
14351 MailMoveEvent ()
14352 {
14353 #if !WIN32
14354     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14355     FILE *commandOutput;
14356     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14357     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14358     int nBuffers;
14359     int i;
14360     int archived;
14361     char *arcDir;
14362
14363     if (! cmailMsgLoaded) {
14364         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14365         return;
14366     }
14367
14368     if (nCmailGames == nCmailResults) {
14369         DisplayError(_("No unfinished games"), 0);
14370         return;
14371     }
14372
14373 #if CMAIL_PROHIBIT_REMAIL
14374     if (cmailMailedMove) {
14375       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);
14376         DisplayError(msg, 0);
14377         return;
14378     }
14379 #endif
14380
14381     if (! (cmailMailedMove || RegisterMove())) return;
14382
14383     if (   cmailMailedMove
14384         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14385       snprintf(string, MSG_SIZ, partCommandString,
14386                appData.debugMode ? " -v" : "", appData.cmailGameName);
14387         commandOutput = popen(string, "r");
14388
14389         if (commandOutput == NULL) {
14390             DisplayError(_("Failed to invoke cmail"), 0);
14391         } else {
14392             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14393                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14394             }
14395             if (nBuffers > 1) {
14396                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14397                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14398                 nBytes = MSG_SIZ - 1;
14399             } else {
14400                 (void) memcpy(msg, buffer, nBytes);
14401             }
14402             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14403
14404             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14405                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14406
14407                 archived = TRUE;
14408                 for (i = 0; i < nCmailGames; i ++) {
14409                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14410                         archived = FALSE;
14411                     }
14412                 }
14413                 if (   archived
14414                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14415                         != NULL)) {
14416                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14417                            arcDir,
14418                            appData.cmailGameName,
14419                            gameInfo.date);
14420                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14421                     cmailMsgLoaded = FALSE;
14422                 }
14423             }
14424
14425             DisplayInformation(msg);
14426             pclose(commandOutput);
14427         }
14428     } else {
14429         if ((*cmailMsg) != '\0') {
14430             DisplayInformation(cmailMsg);
14431         }
14432     }
14433
14434     return;
14435 #endif /* !WIN32 */
14436 }
14437
14438 char *
14439 CmailMsg ()
14440 {
14441 #if WIN32
14442     return NULL;
14443 #else
14444     int  prependComma = 0;
14445     char number[5];
14446     char string[MSG_SIZ];       /* Space for game-list */
14447     int  i;
14448
14449     if (!cmailMsgLoaded) return "";
14450
14451     if (cmailMailedMove) {
14452       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14453     } else {
14454         /* Create a list of games left */
14455       snprintf(string, MSG_SIZ, "[");
14456         for (i = 0; i < nCmailGames; i ++) {
14457             if (! (   cmailMoveRegistered[i]
14458                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14459                 if (prependComma) {
14460                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14461                 } else {
14462                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14463                     prependComma = 1;
14464                 }
14465
14466                 strcat(string, number);
14467             }
14468         }
14469         strcat(string, "]");
14470
14471         if (nCmailMovesRegistered + nCmailResults == 0) {
14472             switch (nCmailGames) {
14473               case 1:
14474                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14475                 break;
14476
14477               case 2:
14478                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14479                 break;
14480
14481               default:
14482                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14483                          nCmailGames);
14484                 break;
14485             }
14486         } else {
14487             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14488               case 1:
14489                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14490                          string);
14491                 break;
14492
14493               case 0:
14494                 if (nCmailResults == nCmailGames) {
14495                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14496                 } else {
14497                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14498                 }
14499                 break;
14500
14501               default:
14502                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14503                          string);
14504             }
14505         }
14506     }
14507     return cmailMsg;
14508 #endif /* WIN32 */
14509 }
14510
14511 void
14512 ResetGameEvent ()
14513 {
14514     if (gameMode == Training)
14515       SetTrainingModeOff();
14516
14517     Reset(TRUE, TRUE);
14518     cmailMsgLoaded = FALSE;
14519     if (appData.icsActive) {
14520       SendToICS(ics_prefix);
14521       SendToICS("refresh\n");
14522     }
14523 }
14524
14525 void
14526 ExitEvent (int status)
14527 {
14528     exiting++;
14529     if (exiting > 2) {
14530       /* Give up on clean exit */
14531       exit(status);
14532     }
14533     if (exiting > 1) {
14534       /* Keep trying for clean exit */
14535       return;
14536     }
14537
14538     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14539     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14540
14541     if (telnetISR != NULL) {
14542       RemoveInputSource(telnetISR);
14543     }
14544     if (icsPR != NoProc) {
14545       DestroyChildProcess(icsPR, TRUE);
14546     }
14547
14548     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14549     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14550
14551     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14552     /* make sure this other one finishes before killing it!                  */
14553     if(endingGame) { int count = 0;
14554         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14555         while(endingGame && count++ < 10) DoSleep(1);
14556         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14557     }
14558
14559     /* Kill off chess programs */
14560     if (first.pr != NoProc) {
14561         ExitAnalyzeMode();
14562
14563         DoSleep( appData.delayBeforeQuit );
14564         SendToProgram("quit\n", &first);
14565         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14566     }
14567     if (second.pr != NoProc) {
14568         DoSleep( appData.delayBeforeQuit );
14569         SendToProgram("quit\n", &second);
14570         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14571     }
14572     if (first.isr != NULL) {
14573         RemoveInputSource(first.isr);
14574     }
14575     if (second.isr != NULL) {
14576         RemoveInputSource(second.isr);
14577     }
14578
14579     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14580     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14581
14582     ShutDownFrontEnd();
14583     exit(status);
14584 }
14585
14586 void
14587 PauseEngine (ChessProgramState *cps)
14588 {
14589     SendToProgram("pause\n", cps);
14590     cps->pause = 2;
14591 }
14592
14593 void
14594 UnPauseEngine (ChessProgramState *cps)
14595 {
14596     SendToProgram("resume\n", cps);
14597     cps->pause = 1;
14598 }
14599
14600 void
14601 PauseEvent ()
14602 {
14603     if (appData.debugMode)
14604         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14605     if (pausing) {
14606         pausing = FALSE;
14607         ModeHighlight();
14608         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14609             StartClocks();
14610             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14611                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14612                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14613             }
14614             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14615             HandleMachineMove(stashedInputMove, stalledEngine);
14616             stalledEngine = NULL;
14617             return;
14618         }
14619         if (gameMode == MachinePlaysWhite ||
14620             gameMode == TwoMachinesPlay   ||
14621             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14622             if(first.pause)  UnPauseEngine(&first);
14623             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14624             if(second.pause) UnPauseEngine(&second);
14625             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14626             StartClocks();
14627         } else {
14628             DisplayBothClocks();
14629         }
14630         if (gameMode == PlayFromGameFile) {
14631             if (appData.timeDelay >= 0)
14632                 AutoPlayGameLoop();
14633         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14634             Reset(FALSE, TRUE);
14635             SendToICS(ics_prefix);
14636             SendToICS("refresh\n");
14637         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14638             ForwardInner(forwardMostMove);
14639         }
14640         pauseExamInvalid = FALSE;
14641     } else {
14642         switch (gameMode) {
14643           default:
14644             return;
14645           case IcsExamining:
14646             pauseExamForwardMostMove = forwardMostMove;
14647             pauseExamInvalid = FALSE;
14648             /* fall through */
14649           case IcsObserving:
14650           case IcsPlayingWhite:
14651           case IcsPlayingBlack:
14652             pausing = TRUE;
14653             ModeHighlight();
14654             return;
14655           case PlayFromGameFile:
14656             (void) StopLoadGameTimer();
14657             pausing = TRUE;
14658             ModeHighlight();
14659             break;
14660           case BeginningOfGame:
14661             if (appData.icsActive) return;
14662             /* else fall through */
14663           case MachinePlaysWhite:
14664           case MachinePlaysBlack:
14665           case TwoMachinesPlay:
14666             if (forwardMostMove == 0)
14667               return;           /* don't pause if no one has moved */
14668             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14669                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14670                 if(onMove->pause) {           // thinking engine can be paused
14671                     PauseEngine(onMove);      // do it
14672                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14673                         PauseEngine(onMove->other);
14674                     else
14675                         SendToProgram("easy\n", onMove->other);
14676                     StopClocks();
14677                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14678             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14679                 if(first.pause) {
14680                     PauseEngine(&first);
14681                     StopClocks();
14682                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14683             } else { // human on move, pause pondering by either method
14684                 if(first.pause)
14685                     PauseEngine(&first);
14686                 else if(appData.ponderNextMove)
14687                     SendToProgram("easy\n", &first);
14688                 StopClocks();
14689             }
14690             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14691           case AnalyzeMode:
14692             pausing = TRUE;
14693             ModeHighlight();
14694             break;
14695         }
14696     }
14697 }
14698
14699 void
14700 EditCommentEvent ()
14701 {
14702     char title[MSG_SIZ];
14703
14704     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14705       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14706     } else {
14707       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14708                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14709                parseList[currentMove - 1]);
14710     }
14711
14712     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14713 }
14714
14715
14716 void
14717 EditTagsEvent ()
14718 {
14719     char *tags = PGNTags(&gameInfo);
14720     bookUp = FALSE;
14721     EditTagsPopUp(tags, NULL);
14722     free(tags);
14723 }
14724
14725 void
14726 ToggleSecond ()
14727 {
14728   if(second.analyzing) {
14729     SendToProgram("exit\n", &second);
14730     second.analyzing = FALSE;
14731   } else {
14732     if (second.pr == NoProc) StartChessProgram(&second);
14733     InitChessProgram(&second, FALSE);
14734     FeedMovesToProgram(&second, currentMove);
14735
14736     SendToProgram("analyze\n", &second);
14737     second.analyzing = TRUE;
14738   }
14739 }
14740
14741 /* Toggle ShowThinking */
14742 void
14743 ToggleShowThinking()
14744 {
14745   appData.showThinking = !appData.showThinking;
14746   ShowThinkingEvent();
14747 }
14748
14749 int
14750 AnalyzeModeEvent ()
14751 {
14752     char buf[MSG_SIZ];
14753
14754     if (!first.analysisSupport) {
14755       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14756       DisplayError(buf, 0);
14757       return 0;
14758     }
14759     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14760     if (appData.icsActive) {
14761         if (gameMode != IcsObserving) {
14762           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14763             DisplayError(buf, 0);
14764             /* secure check */
14765             if (appData.icsEngineAnalyze) {
14766                 if (appData.debugMode)
14767                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14768                 ExitAnalyzeMode();
14769                 ModeHighlight();
14770             }
14771             return 0;
14772         }
14773         /* if enable, user wants to disable icsEngineAnalyze */
14774         if (appData.icsEngineAnalyze) {
14775                 ExitAnalyzeMode();
14776                 ModeHighlight();
14777                 return 0;
14778         }
14779         appData.icsEngineAnalyze = TRUE;
14780         if (appData.debugMode)
14781             fprintf(debugFP, "ICS engine analyze starting... \n");
14782     }
14783
14784     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14785     if (appData.noChessProgram || gameMode == AnalyzeMode)
14786       return 0;
14787
14788     if (gameMode != AnalyzeFile) {
14789         if (!appData.icsEngineAnalyze) {
14790                EditGameEvent();
14791                if (gameMode != EditGame) return 0;
14792         }
14793         if (!appData.showThinking) ToggleShowThinking();
14794         ResurrectChessProgram();
14795         SendToProgram("analyze\n", &first);
14796         first.analyzing = TRUE;
14797         /*first.maybeThinking = TRUE;*/
14798         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14799         EngineOutputPopUp();
14800     }
14801     if (!appData.icsEngineAnalyze) {
14802         gameMode = AnalyzeMode;
14803         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14804     }
14805     pausing = FALSE;
14806     ModeHighlight();
14807     SetGameInfo();
14808
14809     StartAnalysisClock();
14810     GetTimeMark(&lastNodeCountTime);
14811     lastNodeCount = 0;
14812     return 1;
14813 }
14814
14815 void
14816 AnalyzeFileEvent ()
14817 {
14818     if (appData.noChessProgram || gameMode == AnalyzeFile)
14819       return;
14820
14821     if (!first.analysisSupport) {
14822       char buf[MSG_SIZ];
14823       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14824       DisplayError(buf, 0);
14825       return;
14826     }
14827
14828     if (gameMode != AnalyzeMode) {
14829         keepInfo = 1; // mere annotating should not alter PGN tags
14830         EditGameEvent();
14831         keepInfo = 0;
14832         if (gameMode != EditGame) return;
14833         if (!appData.showThinking) ToggleShowThinking();
14834         ResurrectChessProgram();
14835         SendToProgram("analyze\n", &first);
14836         first.analyzing = TRUE;
14837         /*first.maybeThinking = TRUE;*/
14838         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14839         EngineOutputPopUp();
14840     }
14841     gameMode = AnalyzeFile;
14842     pausing = FALSE;
14843     ModeHighlight();
14844
14845     StartAnalysisClock();
14846     GetTimeMark(&lastNodeCountTime);
14847     lastNodeCount = 0;
14848     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14849     AnalysisPeriodicEvent(1);
14850 }
14851
14852 void
14853 MachineWhiteEvent ()
14854 {
14855     char buf[MSG_SIZ];
14856     char *bookHit = NULL;
14857
14858     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14859       return;
14860
14861
14862     if (gameMode == PlayFromGameFile ||
14863         gameMode == TwoMachinesPlay  ||
14864         gameMode == Training         ||
14865         gameMode == AnalyzeMode      ||
14866         gameMode == EndOfGame)
14867         EditGameEvent();
14868
14869     if (gameMode == EditPosition)
14870         EditPositionDone(TRUE);
14871
14872     if (!WhiteOnMove(currentMove)) {
14873         DisplayError(_("It is not White's turn"), 0);
14874         return;
14875     }
14876
14877     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14878       ExitAnalyzeMode();
14879
14880     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14881         gameMode == AnalyzeFile)
14882         TruncateGame();
14883
14884     ResurrectChessProgram();    /* in case it isn't running */
14885     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14886         gameMode = MachinePlaysWhite;
14887         ResetClocks();
14888     } else
14889     gameMode = MachinePlaysWhite;
14890     pausing = FALSE;
14891     ModeHighlight();
14892     SetGameInfo();
14893     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14894     DisplayTitle(buf);
14895     if (first.sendName) {
14896       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14897       SendToProgram(buf, &first);
14898     }
14899     if (first.sendTime) {
14900       if (first.useColors) {
14901         SendToProgram("black\n", &first); /*gnu kludge*/
14902       }
14903       SendTimeRemaining(&first, TRUE);
14904     }
14905     if (first.useColors) {
14906       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14907     }
14908     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14909     SetMachineThinkingEnables();
14910     first.maybeThinking = TRUE;
14911     StartClocks();
14912     firstMove = FALSE;
14913
14914     if (appData.autoFlipView && !flipView) {
14915       flipView = !flipView;
14916       DrawPosition(FALSE, NULL);
14917       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14918     }
14919
14920     if(bookHit) { // [HGM] book: simulate book reply
14921         static char bookMove[MSG_SIZ]; // a bit generous?
14922
14923         programStats.nodes = programStats.depth = programStats.time =
14924         programStats.score = programStats.got_only_move = 0;
14925         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14926
14927         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14928         strcat(bookMove, bookHit);
14929         savedMessage = bookMove; // args for deferred call
14930         savedState = &first;
14931         ScheduleDelayedEvent(DeferredBookMove, 1);
14932     }
14933 }
14934
14935 void
14936 MachineBlackEvent ()
14937 {
14938   char buf[MSG_SIZ];
14939   char *bookHit = NULL;
14940
14941     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14942         return;
14943
14944
14945     if (gameMode == PlayFromGameFile ||
14946         gameMode == TwoMachinesPlay  ||
14947         gameMode == Training         ||
14948         gameMode == AnalyzeMode      ||
14949         gameMode == EndOfGame)
14950         EditGameEvent();
14951
14952     if (gameMode == EditPosition)
14953         EditPositionDone(TRUE);
14954
14955     if (WhiteOnMove(currentMove)) {
14956         DisplayError(_("It is not Black's turn"), 0);
14957         return;
14958     }
14959
14960     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14961       ExitAnalyzeMode();
14962
14963     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14964         gameMode == AnalyzeFile)
14965         TruncateGame();
14966
14967     ResurrectChessProgram();    /* in case it isn't running */
14968     gameMode = MachinePlaysBlack;
14969     pausing = FALSE;
14970     ModeHighlight();
14971     SetGameInfo();
14972     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14973     DisplayTitle(buf);
14974     if (first.sendName) {
14975       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14976       SendToProgram(buf, &first);
14977     }
14978     if (first.sendTime) {
14979       if (first.useColors) {
14980         SendToProgram("white\n", &first); /*gnu kludge*/
14981       }
14982       SendTimeRemaining(&first, FALSE);
14983     }
14984     if (first.useColors) {
14985       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14986     }
14987     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14988     SetMachineThinkingEnables();
14989     first.maybeThinking = TRUE;
14990     StartClocks();
14991
14992     if (appData.autoFlipView && flipView) {
14993       flipView = !flipView;
14994       DrawPosition(FALSE, NULL);
14995       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14996     }
14997     if(bookHit) { // [HGM] book: simulate book reply
14998         static char bookMove[MSG_SIZ]; // a bit generous?
14999
15000         programStats.nodes = programStats.depth = programStats.time =
15001         programStats.score = programStats.got_only_move = 0;
15002         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15003
15004         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15005         strcat(bookMove, bookHit);
15006         savedMessage = bookMove; // args for deferred call
15007         savedState = &first;
15008         ScheduleDelayedEvent(DeferredBookMove, 1);
15009     }
15010 }
15011
15012
15013 void
15014 DisplayTwoMachinesTitle ()
15015 {
15016     char buf[MSG_SIZ];
15017     if (appData.matchGames > 0) {
15018         if(appData.tourneyFile[0]) {
15019           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15020                    gameInfo.white, _("vs."), gameInfo.black,
15021                    nextGame+1, appData.matchGames+1,
15022                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15023         } else
15024         if (first.twoMachinesColor[0] == 'w') {
15025           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15026                    gameInfo.white, _("vs."),  gameInfo.black,
15027                    first.matchWins, second.matchWins,
15028                    matchGame - 1 - (first.matchWins + second.matchWins));
15029         } else {
15030           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15031                    gameInfo.white, _("vs."), gameInfo.black,
15032                    second.matchWins, first.matchWins,
15033                    matchGame - 1 - (first.matchWins + second.matchWins));
15034         }
15035     } else {
15036       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15037     }
15038     DisplayTitle(buf);
15039 }
15040
15041 void
15042 SettingsMenuIfReady ()
15043 {
15044   if (second.lastPing != second.lastPong) {
15045     DisplayMessage("", _("Waiting for second chess program"));
15046     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15047     return;
15048   }
15049   ThawUI();
15050   DisplayMessage("", "");
15051   SettingsPopUp(&second);
15052 }
15053
15054 int
15055 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15056 {
15057     char buf[MSG_SIZ];
15058     if (cps->pr == NoProc) {
15059         StartChessProgram(cps);
15060         if (cps->protocolVersion == 1) {
15061           retry();
15062           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15063         } else {
15064           /* kludge: allow timeout for initial "feature" command */
15065           if(retry != TwoMachinesEventIfReady) FreezeUI();
15066           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15067           DisplayMessage("", buf);
15068           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15069         }
15070         return 1;
15071     }
15072     return 0;
15073 }
15074
15075 void
15076 TwoMachinesEvent P((void))
15077 {
15078     int i, move = forwardMostMove;
15079     char buf[MSG_SIZ];
15080     ChessProgramState *onmove;
15081     char *bookHit = NULL;
15082     static int stalling = 0;
15083     TimeMark now;
15084     long wait;
15085
15086     if (appData.noChessProgram) return;
15087
15088     switch (gameMode) {
15089       case TwoMachinesPlay:
15090         return;
15091       case MachinePlaysWhite:
15092       case MachinePlaysBlack:
15093         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15094             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15095             return;
15096         }
15097         /* fall through */
15098       case BeginningOfGame:
15099       case PlayFromGameFile:
15100       case EndOfGame:
15101         EditGameEvent();
15102         if (gameMode != EditGame) return;
15103         break;
15104       case EditPosition:
15105         EditPositionDone(TRUE);
15106         break;
15107       case AnalyzeMode:
15108       case AnalyzeFile:
15109         ExitAnalyzeMode();
15110         break;
15111       case EditGame:
15112       default:
15113         break;
15114     }
15115
15116 //    forwardMostMove = currentMove;
15117     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15118     startingEngine = TRUE;
15119
15120     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15121
15122     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15123     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15124       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15125       return;
15126     }
15127   if(!appData.epd) {
15128     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15129
15130     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15131                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15132         startingEngine = matchMode = FALSE;
15133         DisplayError("second engine does not play this", 0);
15134         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15135         EditGameEvent(); // switch back to EditGame mode
15136         return;
15137     }
15138
15139     if(!stalling) {
15140       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15141       SendToProgram("force\n", &second);
15142       stalling = 1;
15143       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15144       return;
15145     }
15146   }
15147     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15148     if(appData.matchPause>10000 || appData.matchPause<10)
15149                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15150     wait = SubtractTimeMarks(&now, &pauseStart);
15151     if(wait < appData.matchPause) {
15152         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15153         return;
15154     }
15155     // we are now committed to starting the game
15156     stalling = 0;
15157     DisplayMessage("", "");
15158   if(!appData.epd) {
15159     if (startedFromSetupPosition) {
15160         SendBoard(&second, backwardMostMove);
15161     if (appData.debugMode) {
15162         fprintf(debugFP, "Two Machines\n");
15163     }
15164     }
15165     for (i = backwardMostMove; i < forwardMostMove; i++) {
15166         SendMoveToProgram(i, &second);
15167     }
15168   }
15169
15170     gameMode = TwoMachinesPlay;
15171     pausing = startingEngine = FALSE;
15172     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15173     SetGameInfo();
15174     DisplayTwoMachinesTitle();
15175     firstMove = TRUE;
15176     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15177         onmove = &first;
15178     } else {
15179         onmove = &second;
15180     }
15181     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15182     SendToProgram(first.computerString, &first);
15183     if (first.sendName) {
15184       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15185       SendToProgram(buf, &first);
15186     }
15187   if(!appData.epd) {
15188     SendToProgram(second.computerString, &second);
15189     if (second.sendName) {
15190       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15191       SendToProgram(buf, &second);
15192     }
15193   }
15194
15195     if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15196         ResetClocks();
15197         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15198         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15199     }
15200     if (onmove->sendTime) {
15201       if (onmove->useColors) {
15202         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15203       }
15204       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15205     }
15206     if (onmove->useColors) {
15207       SendToProgram(onmove->twoMachinesColor, onmove);
15208     }
15209     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15210 //    SendToProgram("go\n", onmove);
15211     onmove->maybeThinking = TRUE;
15212     SetMachineThinkingEnables();
15213
15214     StartClocks();
15215
15216     if(bookHit) { // [HGM] book: simulate book reply
15217         static char bookMove[MSG_SIZ]; // a bit generous?
15218
15219         programStats.nodes = programStats.depth = programStats.time =
15220         programStats.score = programStats.got_only_move = 0;
15221         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15222
15223         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15224         strcat(bookMove, bookHit);
15225         savedMessage = bookMove; // args for deferred call
15226         savedState = onmove;
15227         ScheduleDelayedEvent(DeferredBookMove, 1);
15228     }
15229 }
15230
15231 void
15232 TrainingEvent ()
15233 {
15234     if (gameMode == Training) {
15235       SetTrainingModeOff();
15236       gameMode = PlayFromGameFile;
15237       DisplayMessage("", _("Training mode off"));
15238     } else {
15239       gameMode = Training;
15240       animateTraining = appData.animate;
15241
15242       /* make sure we are not already at the end of the game */
15243       if (currentMove < forwardMostMove) {
15244         SetTrainingModeOn();
15245         DisplayMessage("", _("Training mode on"));
15246       } else {
15247         gameMode = PlayFromGameFile;
15248         DisplayError(_("Already at end of game"), 0);
15249       }
15250     }
15251     ModeHighlight();
15252 }
15253
15254 void
15255 IcsClientEvent ()
15256 {
15257     if (!appData.icsActive) return;
15258     switch (gameMode) {
15259       case IcsPlayingWhite:
15260       case IcsPlayingBlack:
15261       case IcsObserving:
15262       case IcsIdle:
15263       case BeginningOfGame:
15264       case IcsExamining:
15265         return;
15266
15267       case EditGame:
15268         break;
15269
15270       case EditPosition:
15271         EditPositionDone(TRUE);
15272         break;
15273
15274       case AnalyzeMode:
15275       case AnalyzeFile:
15276         ExitAnalyzeMode();
15277         break;
15278
15279       default:
15280         EditGameEvent();
15281         break;
15282     }
15283
15284     gameMode = IcsIdle;
15285     ModeHighlight();
15286     return;
15287 }
15288
15289 void
15290 EditGameEvent ()
15291 {
15292     int i;
15293
15294     switch (gameMode) {
15295       case Training:
15296         SetTrainingModeOff();
15297         break;
15298       case MachinePlaysWhite:
15299       case MachinePlaysBlack:
15300       case BeginningOfGame:
15301         SendToProgram("force\n", &first);
15302         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15303             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15304                 char buf[MSG_SIZ];
15305                 abortEngineThink = TRUE;
15306                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15307                 SendToProgram(buf, &first);
15308                 DisplayMessage("Aborting engine think", "");
15309                 FreezeUI();
15310             }
15311         }
15312         SetUserThinkingEnables();
15313         break;
15314       case PlayFromGameFile:
15315         (void) StopLoadGameTimer();
15316         if (gameFileFP != NULL) {
15317             gameFileFP = NULL;
15318         }
15319         break;
15320       case EditPosition:
15321         EditPositionDone(TRUE);
15322         break;
15323       case AnalyzeMode:
15324       case AnalyzeFile:
15325         ExitAnalyzeMode();
15326         SendToProgram("force\n", &first);
15327         break;
15328       case TwoMachinesPlay:
15329         GameEnds(EndOfFile, NULL, GE_PLAYER);
15330         ResurrectChessProgram();
15331         SetUserThinkingEnables();
15332         break;
15333       case EndOfGame:
15334         ResurrectChessProgram();
15335         break;
15336       case IcsPlayingBlack:
15337       case IcsPlayingWhite:
15338         DisplayError(_("Warning: You are still playing a game"), 0);
15339         break;
15340       case IcsObserving:
15341         DisplayError(_("Warning: You are still observing a game"), 0);
15342         break;
15343       case IcsExamining:
15344         DisplayError(_("Warning: You are still examining a game"), 0);
15345         break;
15346       case IcsIdle:
15347         break;
15348       case EditGame:
15349       default:
15350         return;
15351     }
15352
15353     pausing = FALSE;
15354     StopClocks();
15355     first.offeredDraw = second.offeredDraw = 0;
15356
15357     if (gameMode == PlayFromGameFile) {
15358         whiteTimeRemaining = timeRemaining[0][currentMove];
15359         blackTimeRemaining = timeRemaining[1][currentMove];
15360         DisplayTitle("");
15361     }
15362
15363     if (gameMode == MachinePlaysWhite ||
15364         gameMode == MachinePlaysBlack ||
15365         gameMode == TwoMachinesPlay ||
15366         gameMode == EndOfGame) {
15367         i = forwardMostMove;
15368         while (i > currentMove) {
15369             SendToProgram("undo\n", &first);
15370             i--;
15371         }
15372         if(!adjustedClock) {
15373         whiteTimeRemaining = timeRemaining[0][currentMove];
15374         blackTimeRemaining = timeRemaining[1][currentMove];
15375         DisplayBothClocks();
15376         }
15377         if (whiteFlag || blackFlag) {
15378             whiteFlag = blackFlag = 0;
15379         }
15380         DisplayTitle("");
15381     }
15382
15383     gameMode = EditGame;
15384     ModeHighlight();
15385     SetGameInfo();
15386 }
15387
15388 void
15389 EditPositionEvent ()
15390 {
15391     int i;
15392     if (gameMode == EditPosition) {
15393         EditGameEvent();
15394         return;
15395     }
15396
15397     EditGameEvent();
15398     if (gameMode != EditGame) return;
15399
15400     gameMode = EditPosition;
15401     ModeHighlight();
15402     SetGameInfo();
15403     CopyBoard(rightsBoard, nullBoard);
15404     if (currentMove > 0)
15405       CopyBoard(boards[0], boards[currentMove]);
15406     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15407       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15408
15409     blackPlaysFirst = !WhiteOnMove(currentMove);
15410     ResetClocks();
15411     currentMove = forwardMostMove = backwardMostMove = 0;
15412     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15413     DisplayMove(-1);
15414     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15415 }
15416
15417 void
15418 ExitAnalyzeMode ()
15419 {
15420     /* [DM] icsEngineAnalyze - possible call from other functions */
15421     if (appData.icsEngineAnalyze) {
15422         appData.icsEngineAnalyze = FALSE;
15423
15424         DisplayMessage("",_("Close ICS engine analyze..."));
15425     }
15426     if (first.analysisSupport && first.analyzing) {
15427       SendToBoth("exit\n");
15428       first.analyzing = second.analyzing = FALSE;
15429     }
15430     thinkOutput[0] = NULLCHAR;
15431 }
15432
15433 void
15434 EditPositionDone (Boolean fakeRights)
15435 {
15436     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15437
15438     startedFromSetupPosition = TRUE;
15439     InitChessProgram(&first, FALSE);
15440     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15441       int r, f;
15442       boards[0][EP_STATUS] = EP_NONE;
15443       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15444       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15445         if(rightsBoard[r][f]) {
15446           ChessSquare p = boards[0][r][f];
15447           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15448           else if(p == king) boards[0][CASTLING][2] = f;
15449           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15450           else rightsBoard[r][f] = 2; // mark for second pass
15451         }
15452       }
15453       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15454         if(rightsBoard[r][f] == 2) {
15455           ChessSquare p = boards[0][r][f];
15456           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15457           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15458         }
15459       }
15460     }
15461     SendToProgram("force\n", &first);
15462     if (blackPlaysFirst) {
15463         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15464         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15465         currentMove = forwardMostMove = backwardMostMove = 1;
15466         CopyBoard(boards[1], boards[0]);
15467     } else {
15468         currentMove = forwardMostMove = backwardMostMove = 0;
15469     }
15470     SendBoard(&first, forwardMostMove);
15471     if (appData.debugMode) {
15472         fprintf(debugFP, "EditPosDone\n");
15473     }
15474     DisplayTitle("");
15475     DisplayMessage("", "");
15476     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15477     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15478     gameMode = EditGame;
15479     ModeHighlight();
15480     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15481     ClearHighlights(); /* [AS] */
15482 }
15483
15484 /* Pause for `ms' milliseconds */
15485 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15486 void
15487 TimeDelay (long ms)
15488 {
15489     TimeMark m1, m2;
15490
15491     GetTimeMark(&m1);
15492     do {
15493         GetTimeMark(&m2);
15494     } while (SubtractTimeMarks(&m2, &m1) < ms);
15495 }
15496
15497 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15498 void
15499 SendMultiLineToICS (char *buf)
15500 {
15501     char temp[MSG_SIZ+1], *p;
15502     int len;
15503
15504     len = strlen(buf);
15505     if (len > MSG_SIZ)
15506       len = MSG_SIZ;
15507
15508     strncpy(temp, buf, len);
15509     temp[len] = 0;
15510
15511     p = temp;
15512     while (*p) {
15513         if (*p == '\n' || *p == '\r')
15514           *p = ' ';
15515         ++p;
15516     }
15517
15518     strcat(temp, "\n");
15519     SendToICS(temp);
15520     SendToPlayer(temp, strlen(temp));
15521 }
15522
15523 void
15524 SetWhiteToPlayEvent ()
15525 {
15526     if (gameMode == EditPosition) {
15527         blackPlaysFirst = FALSE;
15528         DisplayBothClocks();    /* works because currentMove is 0 */
15529     } else if (gameMode == IcsExamining) {
15530         SendToICS(ics_prefix);
15531         SendToICS("tomove white\n");
15532     }
15533 }
15534
15535 void
15536 SetBlackToPlayEvent ()
15537 {
15538     if (gameMode == EditPosition) {
15539         blackPlaysFirst = TRUE;
15540         currentMove = 1;        /* kludge */
15541         DisplayBothClocks();
15542         currentMove = 0;
15543     } else if (gameMode == IcsExamining) {
15544         SendToICS(ics_prefix);
15545         SendToICS("tomove black\n");
15546     }
15547 }
15548
15549 void
15550 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15551 {
15552     char buf[MSG_SIZ];
15553     ChessSquare piece = boards[0][y][x];
15554     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15555     static int lastVariant;
15556     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15557
15558     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15559
15560     switch (selection) {
15561       case ClearBoard:
15562         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15563         MarkTargetSquares(1);
15564         CopyBoard(currentBoard, boards[0]);
15565         CopyBoard(menuBoard, initialPosition);
15566         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15567             SendToICS(ics_prefix);
15568             SendToICS("bsetup clear\n");
15569         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15570             SendToICS(ics_prefix);
15571             SendToICS("clearboard\n");
15572         } else {
15573             int nonEmpty = 0;
15574             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15575                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15576                 for (y = 0; y < BOARD_HEIGHT; y++) {
15577                     if (gameMode == IcsExamining) {
15578                         if (boards[currentMove][y][x] != EmptySquare) {
15579                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15580                                     AAA + x, ONE + y);
15581                             SendToICS(buf);
15582                         }
15583                     } else if(boards[0][y][x] != DarkSquare) {
15584                         if(boards[0][y][x] != p) nonEmpty++;
15585                         boards[0][y][x] = p;
15586                     }
15587                 }
15588             }
15589             CopyBoard(rightsBoard, nullBoard);
15590             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15591                 int r, i;
15592                 for(r = 0; r < BOARD_HEIGHT; r++) {
15593                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15594                     ChessSquare p = menuBoard[r][x];
15595                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15596                   }
15597                 }
15598                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15599                 DisplayMessage("Clicking clock again restores position", "");
15600                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15601                 if(!nonEmpty) { // asked to clear an empty board
15602                     CopyBoard(boards[0], menuBoard);
15603                 } else
15604                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15605                     CopyBoard(boards[0], initialPosition);
15606                 } else
15607                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15608                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15609                     CopyBoard(boards[0], erasedBoard);
15610                 } else
15611                     CopyBoard(erasedBoard, currentBoard);
15612
15613                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15614                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15615             }
15616         }
15617         if (gameMode == EditPosition) {
15618             DrawPosition(FALSE, boards[0]);
15619         }
15620         break;
15621
15622       case WhitePlay:
15623         SetWhiteToPlayEvent();
15624         break;
15625
15626       case BlackPlay:
15627         SetBlackToPlayEvent();
15628         break;
15629
15630       case EmptySquare:
15631         if (gameMode == IcsExamining) {
15632             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15633             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15634             SendToICS(buf);
15635         } else {
15636             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15637                 if(x == BOARD_LEFT-2) {
15638                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15639                     boards[0][y][1] = 0;
15640                 } else
15641                 if(x == BOARD_RGHT+1) {
15642                     if(y >= gameInfo.holdingsSize) break;
15643                     boards[0][y][BOARD_WIDTH-2] = 0;
15644                 } else break;
15645             }
15646             boards[0][y][x] = EmptySquare;
15647             DrawPosition(FALSE, boards[0]);
15648         }
15649         break;
15650
15651       case PromotePiece:
15652         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15653            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15654             selection = (ChessSquare) (PROMOTED(piece));
15655         } else if(piece == EmptySquare) selection = WhiteSilver;
15656         else selection = (ChessSquare)((int)piece - 1);
15657         goto defaultlabel;
15658
15659       case DemotePiece:
15660         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15661            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15662             selection = (ChessSquare) (DEMOTED(piece));
15663         } else if(piece == EmptySquare) selection = BlackSilver;
15664         else selection = (ChessSquare)((int)piece + 1);
15665         goto defaultlabel;
15666
15667       case WhiteQueen:
15668       case BlackQueen:
15669         if(gameInfo.variant == VariantShatranj ||
15670            gameInfo.variant == VariantXiangqi  ||
15671            gameInfo.variant == VariantCourier  ||
15672            gameInfo.variant == VariantASEAN    ||
15673            gameInfo.variant == VariantMakruk     )
15674             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15675         goto defaultlabel;
15676
15677       case WhiteRook:
15678         baseRank = 0;
15679       case BlackRook:
15680         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15681         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15682         goto defaultlabel;
15683
15684       case WhiteKing:
15685         baseRank = 0;
15686       case BlackKing:
15687         if(gameInfo.variant == VariantXiangqi)
15688             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15689         if(gameInfo.variant == VariantKnightmate)
15690             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15691         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15692       default:
15693         defaultlabel:
15694         if (gameMode == IcsExamining) {
15695             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15696             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15697                      PieceToChar(selection), AAA + x, ONE + y);
15698             SendToICS(buf);
15699         } else {
15700             rightsBoard[y][x] = hasRights;
15701             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15702                 int n;
15703                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15704                     n = PieceToNumber(selection - BlackPawn);
15705                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15706                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15707                     boards[0][BOARD_HEIGHT-1-n][1]++;
15708                 } else
15709                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15710                     n = PieceToNumber(selection);
15711                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15712                     boards[0][n][BOARD_WIDTH-1] = selection;
15713                     boards[0][n][BOARD_WIDTH-2]++;
15714                 }
15715             } else
15716             boards[0][y][x] = selection;
15717             DrawPosition(TRUE, boards[0]);
15718             ClearHighlights();
15719             fromX = fromY = -1;
15720         }
15721         break;
15722     }
15723 }
15724
15725
15726 void
15727 DropMenuEvent (ChessSquare selection, int x, int y)
15728 {
15729     ChessMove moveType;
15730
15731     switch (gameMode) {
15732       case IcsPlayingWhite:
15733       case MachinePlaysBlack:
15734         if (!WhiteOnMove(currentMove)) {
15735             DisplayMoveError(_("It is Black's turn"));
15736             return;
15737         }
15738         moveType = WhiteDrop;
15739         break;
15740       case IcsPlayingBlack:
15741       case MachinePlaysWhite:
15742         if (WhiteOnMove(currentMove)) {
15743             DisplayMoveError(_("It is White's turn"));
15744             return;
15745         }
15746         moveType = BlackDrop;
15747         break;
15748       case EditGame:
15749         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15750         break;
15751       default:
15752         return;
15753     }
15754
15755     if (moveType == BlackDrop && selection < BlackPawn) {
15756       selection = (ChessSquare) ((int) selection
15757                                  + (int) BlackPawn - (int) WhitePawn);
15758     }
15759     if (boards[currentMove][y][x] != EmptySquare) {
15760         DisplayMoveError(_("That square is occupied"));
15761         return;
15762     }
15763
15764     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15765 }
15766
15767 void
15768 AcceptEvent ()
15769 {
15770     /* Accept a pending offer of any kind from opponent */
15771
15772     if (appData.icsActive) {
15773         SendToICS(ics_prefix);
15774         SendToICS("accept\n");
15775     } else if (cmailMsgLoaded) {
15776         if (currentMove == cmailOldMove &&
15777             commentList[cmailOldMove] != NULL &&
15778             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15779                    "Black offers a draw" : "White offers a draw")) {
15780             TruncateGame();
15781             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15782             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15783         } else {
15784             DisplayError(_("There is no pending offer on this move"), 0);
15785             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15786         }
15787     } else {
15788         /* Not used for offers from chess program */
15789     }
15790 }
15791
15792 void
15793 DeclineEvent ()
15794 {
15795     /* Decline a pending offer of any kind from opponent */
15796
15797     if (appData.icsActive) {
15798         SendToICS(ics_prefix);
15799         SendToICS("decline\n");
15800     } else if (cmailMsgLoaded) {
15801         if (currentMove == cmailOldMove &&
15802             commentList[cmailOldMove] != NULL &&
15803             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15804                    "Black offers a draw" : "White offers a draw")) {
15805 #ifdef NOTDEF
15806             AppendComment(cmailOldMove, "Draw declined", TRUE);
15807             DisplayComment(cmailOldMove - 1, "Draw declined");
15808 #endif /*NOTDEF*/
15809         } else {
15810             DisplayError(_("There is no pending offer on this move"), 0);
15811         }
15812     } else {
15813         /* Not used for offers from chess program */
15814     }
15815 }
15816
15817 void
15818 RematchEvent ()
15819 {
15820     /* Issue ICS rematch command */
15821     if (appData.icsActive) {
15822         SendToICS(ics_prefix);
15823         SendToICS("rematch\n");
15824     }
15825 }
15826
15827 void
15828 CallFlagEvent ()
15829 {
15830     /* Call your opponent's flag (claim a win on time) */
15831     if (appData.icsActive) {
15832         SendToICS(ics_prefix);
15833         SendToICS("flag\n");
15834     } else {
15835         switch (gameMode) {
15836           default:
15837             return;
15838           case MachinePlaysWhite:
15839             if (whiteFlag) {
15840                 if (blackFlag)
15841                   GameEnds(GameIsDrawn, "Both players ran out of time",
15842                            GE_PLAYER);
15843                 else
15844                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15845             } else {
15846                 DisplayError(_("Your opponent is not out of time"), 0);
15847             }
15848             break;
15849           case MachinePlaysBlack:
15850             if (blackFlag) {
15851                 if (whiteFlag)
15852                   GameEnds(GameIsDrawn, "Both players ran out of time",
15853                            GE_PLAYER);
15854                 else
15855                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15856             } else {
15857                 DisplayError(_("Your opponent is not out of time"), 0);
15858             }
15859             break;
15860         }
15861     }
15862 }
15863
15864 void
15865 ClockClick (int which)
15866 {       // [HGM] code moved to back-end from winboard.c
15867         if(which) { // black clock
15868           if (gameMode == EditPosition || gameMode == IcsExamining) {
15869             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15870             SetBlackToPlayEvent();
15871           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15872                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15873           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15874           } else if (shiftKey) {
15875             AdjustClock(which, -1);
15876           } else if (gameMode == IcsPlayingWhite ||
15877                      gameMode == MachinePlaysBlack) {
15878             CallFlagEvent();
15879           }
15880         } else { // white clock
15881           if (gameMode == EditPosition || gameMode == IcsExamining) {
15882             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15883             SetWhiteToPlayEvent();
15884           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15885                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15886           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15887           } else if (shiftKey) {
15888             AdjustClock(which, -1);
15889           } else if (gameMode == IcsPlayingBlack ||
15890                    gameMode == MachinePlaysWhite) {
15891             CallFlagEvent();
15892           }
15893         }
15894 }
15895
15896 void
15897 DrawEvent ()
15898 {
15899     /* Offer draw or accept pending draw offer from opponent */
15900
15901     if (appData.icsActive) {
15902         /* Note: tournament rules require draw offers to be
15903            made after you make your move but before you punch
15904            your clock.  Currently ICS doesn't let you do that;
15905            instead, you immediately punch your clock after making
15906            a move, but you can offer a draw at any time. */
15907
15908         SendToICS(ics_prefix);
15909         SendToICS("draw\n");
15910         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15911     } else if (cmailMsgLoaded) {
15912         if (currentMove == cmailOldMove &&
15913             commentList[cmailOldMove] != NULL &&
15914             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15915                    "Black offers a draw" : "White offers a draw")) {
15916             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15917             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15918         } else if (currentMove == cmailOldMove + 1) {
15919             char *offer = WhiteOnMove(cmailOldMove) ?
15920               "White offers a draw" : "Black offers a draw";
15921             AppendComment(currentMove, offer, TRUE);
15922             DisplayComment(currentMove - 1, offer);
15923             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15924         } else {
15925             DisplayError(_("You must make your move before offering a draw"), 0);
15926             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15927         }
15928     } else if (first.offeredDraw) {
15929         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15930     } else {
15931         if (first.sendDrawOffers) {
15932             SendToProgram("draw\n", &first);
15933             userOfferedDraw = TRUE;
15934         }
15935     }
15936 }
15937
15938 void
15939 AdjournEvent ()
15940 {
15941     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15942
15943     if (appData.icsActive) {
15944         SendToICS(ics_prefix);
15945         SendToICS("adjourn\n");
15946     } else {
15947         /* Currently GNU Chess doesn't offer or accept Adjourns */
15948     }
15949 }
15950
15951
15952 void
15953 AbortEvent ()
15954 {
15955     /* Offer Abort or accept pending Abort offer from opponent */
15956
15957     if (appData.icsActive) {
15958         SendToICS(ics_prefix);
15959         SendToICS("abort\n");
15960     } else {
15961         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15962     }
15963 }
15964
15965 void
15966 ResignEvent ()
15967 {
15968     /* Resign.  You can do this even if it's not your turn. */
15969
15970     if (appData.icsActive) {
15971         SendToICS(ics_prefix);
15972         SendToICS("resign\n");
15973     } else {
15974         switch (gameMode) {
15975           case MachinePlaysWhite:
15976             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15977             break;
15978           case MachinePlaysBlack:
15979             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15980             break;
15981           case EditGame:
15982             if (cmailMsgLoaded) {
15983                 TruncateGame();
15984                 if (WhiteOnMove(cmailOldMove)) {
15985                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15986                 } else {
15987                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15988                 }
15989                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15990             }
15991             break;
15992           default:
15993             break;
15994         }
15995     }
15996 }
15997
15998
15999 void
16000 StopObservingEvent ()
16001 {
16002     /* Stop observing current games */
16003     SendToICS(ics_prefix);
16004     SendToICS("unobserve\n");
16005 }
16006
16007 void
16008 StopExaminingEvent ()
16009 {
16010     /* Stop observing current game */
16011     SendToICS(ics_prefix);
16012     SendToICS("unexamine\n");
16013 }
16014
16015 void
16016 ForwardInner (int target)
16017 {
16018     int limit; int oldSeekGraphUp = seekGraphUp;
16019
16020     if (appData.debugMode)
16021         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16022                 target, currentMove, forwardMostMove);
16023
16024     if (gameMode == EditPosition)
16025       return;
16026
16027     seekGraphUp = FALSE;
16028     MarkTargetSquares(1);
16029     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16030
16031     if (gameMode == PlayFromGameFile && !pausing)
16032       PauseEvent();
16033
16034     if (gameMode == IcsExamining && pausing)
16035       limit = pauseExamForwardMostMove;
16036     else
16037       limit = forwardMostMove;
16038
16039     if (target > limit) target = limit;
16040
16041     if (target > 0 && moveList[target - 1][0]) {
16042         int fromX, fromY, toX, toY;
16043         toX = moveList[target - 1][2] - AAA;
16044         toY = moveList[target - 1][3] - ONE;
16045         if (moveList[target - 1][1] == '@') {
16046             if (appData.highlightLastMove) {
16047                 SetHighlights(-1, -1, toX, toY);
16048             }
16049         } else {
16050             fromX = moveList[target - 1][0] - AAA;
16051             fromY = moveList[target - 1][1] - ONE;
16052             if (target == currentMove + 1) {
16053                 if(moveList[target - 1][4] == ';') { // multi-leg
16054                     killX = moveList[target - 1][5] - AAA;
16055                     killY = moveList[target - 1][6] - ONE;
16056                 }
16057                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16058                 killX = killY = -1;
16059             }
16060             if (appData.highlightLastMove) {
16061                 SetHighlights(fromX, fromY, toX, toY);
16062             }
16063         }
16064     }
16065     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16066         gameMode == Training || gameMode == PlayFromGameFile ||
16067         gameMode == AnalyzeFile) {
16068         while (currentMove < target) {
16069             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16070             SendMoveToProgram(currentMove++, &first);
16071         }
16072     } else {
16073         currentMove = target;
16074     }
16075
16076     if (gameMode == EditGame || gameMode == EndOfGame) {
16077         whiteTimeRemaining = timeRemaining[0][currentMove];
16078         blackTimeRemaining = timeRemaining[1][currentMove];
16079     }
16080     DisplayBothClocks();
16081     DisplayMove(currentMove - 1);
16082     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16083     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16084     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16085         DisplayComment(currentMove - 1, commentList[currentMove]);
16086     }
16087     ClearMap(); // [HGM] exclude: invalidate map
16088 }
16089
16090
16091 void
16092 ForwardEvent ()
16093 {
16094     if (gameMode == IcsExamining && !pausing) {
16095         SendToICS(ics_prefix);
16096         SendToICS("forward\n");
16097     } else {
16098         ForwardInner(currentMove + 1);
16099     }
16100 }
16101
16102 void
16103 ToEndEvent ()
16104 {
16105     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16106         /* to optimze, we temporarily turn off analysis mode while we feed
16107          * the remaining moves to the engine. Otherwise we get analysis output
16108          * after each move.
16109          */
16110         if (first.analysisSupport) {
16111           SendToProgram("exit\nforce\n", &first);
16112           first.analyzing = FALSE;
16113         }
16114     }
16115
16116     if (gameMode == IcsExamining && !pausing) {
16117         SendToICS(ics_prefix);
16118         SendToICS("forward 999999\n");
16119     } else {
16120         ForwardInner(forwardMostMove);
16121     }
16122
16123     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16124         /* we have fed all the moves, so reactivate analysis mode */
16125         SendToProgram("analyze\n", &first);
16126         first.analyzing = TRUE;
16127         /*first.maybeThinking = TRUE;*/
16128         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16129     }
16130 }
16131
16132 void
16133 BackwardInner (int target)
16134 {
16135     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16136
16137     if (appData.debugMode)
16138         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16139                 target, currentMove, forwardMostMove);
16140
16141     if (gameMode == EditPosition) return;
16142     seekGraphUp = FALSE;
16143     MarkTargetSquares(1);
16144     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16145     if (currentMove <= backwardMostMove) {
16146         ClearHighlights();
16147         DrawPosition(full_redraw, boards[currentMove]);
16148         return;
16149     }
16150     if (gameMode == PlayFromGameFile && !pausing)
16151       PauseEvent();
16152
16153     if (moveList[target][0]) {
16154         int fromX, fromY, toX, toY;
16155         toX = moveList[target][2] - AAA;
16156         toY = moveList[target][3] - ONE;
16157         if (moveList[target][1] == '@') {
16158             if (appData.highlightLastMove) {
16159                 SetHighlights(-1, -1, toX, toY);
16160             }
16161         } else {
16162             fromX = moveList[target][0] - AAA;
16163             fromY = moveList[target][1] - ONE;
16164             if (target == currentMove - 1) {
16165                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16166             }
16167             if (appData.highlightLastMove) {
16168                 SetHighlights(fromX, fromY, toX, toY);
16169             }
16170         }
16171     }
16172     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16173         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16174         while (currentMove > target) {
16175             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16176                 // null move cannot be undone. Reload program with move history before it.
16177                 int i;
16178                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16179                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16180                 }
16181                 SendBoard(&first, i);
16182               if(second.analyzing) SendBoard(&second, i);
16183                 for(currentMove=i; currentMove<target; currentMove++) {
16184                     SendMoveToProgram(currentMove, &first);
16185                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16186                 }
16187                 break;
16188             }
16189             SendToBoth("undo\n");
16190             currentMove--;
16191         }
16192     } else {
16193         currentMove = target;
16194     }
16195
16196     if (gameMode == EditGame || gameMode == EndOfGame) {
16197         whiteTimeRemaining = timeRemaining[0][currentMove];
16198         blackTimeRemaining = timeRemaining[1][currentMove];
16199     }
16200     DisplayBothClocks();
16201     DisplayMove(currentMove - 1);
16202     DrawPosition(full_redraw, boards[currentMove]);
16203     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16204     // [HGM] PV info: routine tests if comment empty
16205     DisplayComment(currentMove - 1, commentList[currentMove]);
16206     ClearMap(); // [HGM] exclude: invalidate map
16207 }
16208
16209 void
16210 BackwardEvent ()
16211 {
16212     if (gameMode == IcsExamining && !pausing) {
16213         SendToICS(ics_prefix);
16214         SendToICS("backward\n");
16215     } else {
16216         BackwardInner(currentMove - 1);
16217     }
16218 }
16219
16220 void
16221 ToStartEvent ()
16222 {
16223     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16224         /* to optimize, we temporarily turn off analysis mode while we undo
16225          * all the moves. Otherwise we get analysis output after each undo.
16226          */
16227         if (first.analysisSupport) {
16228           SendToProgram("exit\nforce\n", &first);
16229           first.analyzing = FALSE;
16230         }
16231     }
16232
16233     if (gameMode == IcsExamining && !pausing) {
16234         SendToICS(ics_prefix);
16235         SendToICS("backward 999999\n");
16236     } else {
16237         BackwardInner(backwardMostMove);
16238     }
16239
16240     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16241         /* we have fed all the moves, so reactivate analysis mode */
16242         SendToProgram("analyze\n", &first);
16243         first.analyzing = TRUE;
16244         /*first.maybeThinking = TRUE;*/
16245         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16246     }
16247 }
16248
16249 void
16250 ToNrEvent (int to)
16251 {
16252   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16253   if (to >= forwardMostMove) to = forwardMostMove;
16254   if (to <= backwardMostMove) to = backwardMostMove;
16255   if (to < currentMove) {
16256     BackwardInner(to);
16257   } else {
16258     ForwardInner(to);
16259   }
16260 }
16261
16262 void
16263 RevertEvent (Boolean annotate)
16264 {
16265     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16266         return;
16267     }
16268     if (gameMode != IcsExamining) {
16269         DisplayError(_("You are not examining a game"), 0);
16270         return;
16271     }
16272     if (pausing) {
16273         DisplayError(_("You can't revert while pausing"), 0);
16274         return;
16275     }
16276     SendToICS(ics_prefix);
16277     SendToICS("revert\n");
16278 }
16279
16280 void
16281 RetractMoveEvent ()
16282 {
16283     switch (gameMode) {
16284       case MachinePlaysWhite:
16285       case MachinePlaysBlack:
16286         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16287             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16288             return;
16289         }
16290         if (forwardMostMove < 2) return;
16291         currentMove = forwardMostMove = forwardMostMove - 2;
16292         whiteTimeRemaining = timeRemaining[0][currentMove];
16293         blackTimeRemaining = timeRemaining[1][currentMove];
16294         DisplayBothClocks();
16295         DisplayMove(currentMove - 1);
16296         ClearHighlights();/*!! could figure this out*/
16297         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16298         SendToProgram("remove\n", &first);
16299         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16300         break;
16301
16302       case BeginningOfGame:
16303       default:
16304         break;
16305
16306       case IcsPlayingWhite:
16307       case IcsPlayingBlack:
16308         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16309             SendToICS(ics_prefix);
16310             SendToICS("takeback 2\n");
16311         } else {
16312             SendToICS(ics_prefix);
16313             SendToICS("takeback 1\n");
16314         }
16315         break;
16316     }
16317 }
16318
16319 void
16320 MoveNowEvent ()
16321 {
16322     ChessProgramState *cps;
16323
16324     switch (gameMode) {
16325       case MachinePlaysWhite:
16326         if (!WhiteOnMove(forwardMostMove)) {
16327             DisplayError(_("It is your turn"), 0);
16328             return;
16329         }
16330         cps = &first;
16331         break;
16332       case MachinePlaysBlack:
16333         if (WhiteOnMove(forwardMostMove)) {
16334             DisplayError(_("It is your turn"), 0);
16335             return;
16336         }
16337         cps = &first;
16338         break;
16339       case TwoMachinesPlay:
16340         if (WhiteOnMove(forwardMostMove) ==
16341             (first.twoMachinesColor[0] == 'w')) {
16342             cps = &first;
16343         } else {
16344             cps = &second;
16345         }
16346         break;
16347       case BeginningOfGame:
16348       default:
16349         return;
16350     }
16351     SendToProgram("?\n", cps);
16352 }
16353
16354 void
16355 TruncateGameEvent ()
16356 {
16357     EditGameEvent();
16358     if (gameMode != EditGame) return;
16359     TruncateGame();
16360 }
16361
16362 void
16363 TruncateGame ()
16364 {
16365     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16366     if (forwardMostMove > currentMove) {
16367         if (gameInfo.resultDetails != NULL) {
16368             free(gameInfo.resultDetails);
16369             gameInfo.resultDetails = NULL;
16370             gameInfo.result = GameUnfinished;
16371         }
16372         forwardMostMove = currentMove;
16373         HistorySet(parseList, backwardMostMove, forwardMostMove,
16374                    currentMove-1);
16375     }
16376 }
16377
16378 void
16379 HintEvent ()
16380 {
16381     if (appData.noChessProgram) return;
16382     switch (gameMode) {
16383       case MachinePlaysWhite:
16384         if (WhiteOnMove(forwardMostMove)) {
16385             DisplayError(_("Wait until your turn."), 0);
16386             return;
16387         }
16388         break;
16389       case BeginningOfGame:
16390       case MachinePlaysBlack:
16391         if (!WhiteOnMove(forwardMostMove)) {
16392             DisplayError(_("Wait until your turn."), 0);
16393             return;
16394         }
16395         break;
16396       default:
16397         DisplayError(_("No hint available"), 0);
16398         return;
16399     }
16400     SendToProgram("hint\n", &first);
16401     hintRequested = TRUE;
16402 }
16403
16404 int
16405 SaveSelected (FILE *g, int dummy, char *dummy2)
16406 {
16407     ListGame * lg = (ListGame *) gameList.head;
16408     int nItem, cnt=0;
16409     FILE *f;
16410
16411     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16412         DisplayError(_("Game list not loaded or empty"), 0);
16413         return 0;
16414     }
16415
16416     creatingBook = TRUE; // suppresses stuff during load game
16417
16418     /* Get list size */
16419     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16420         if(lg->position >= 0) { // selected?
16421             LoadGame(f, nItem, "", TRUE);
16422             SaveGamePGN2(g); // leaves g open
16423             cnt++; DoEvents();
16424         }
16425         lg = (ListGame *) lg->node.succ;
16426     }
16427
16428     fclose(g);
16429     creatingBook = FALSE;
16430
16431     return cnt;
16432 }
16433
16434 void
16435 CreateBookEvent ()
16436 {
16437     ListGame * lg = (ListGame *) gameList.head;
16438     FILE *f, *g;
16439     int nItem;
16440     static int secondTime = FALSE;
16441
16442     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16443         DisplayError(_("Game list not loaded or empty"), 0);
16444         return;
16445     }
16446
16447     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16448         fclose(g);
16449         secondTime++;
16450         DisplayNote(_("Book file exists! Try again for overwrite."));
16451         return;
16452     }
16453
16454     creatingBook = TRUE;
16455     secondTime = FALSE;
16456
16457     /* Get list size */
16458     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16459         if(lg->position >= 0) {
16460             LoadGame(f, nItem, "", TRUE);
16461             AddGameToBook(TRUE);
16462             DoEvents();
16463         }
16464         lg = (ListGame *) lg->node.succ;
16465     }
16466
16467     creatingBook = FALSE;
16468     FlushBook();
16469 }
16470
16471 void
16472 BookEvent ()
16473 {
16474     if (appData.noChessProgram) return;
16475     switch (gameMode) {
16476       case MachinePlaysWhite:
16477         if (WhiteOnMove(forwardMostMove)) {
16478             DisplayError(_("Wait until your turn."), 0);
16479             return;
16480         }
16481         break;
16482       case BeginningOfGame:
16483       case MachinePlaysBlack:
16484         if (!WhiteOnMove(forwardMostMove)) {
16485             DisplayError(_("Wait until your turn."), 0);
16486             return;
16487         }
16488         break;
16489       case EditPosition:
16490         EditPositionDone(TRUE);
16491         break;
16492       case TwoMachinesPlay:
16493         return;
16494       default:
16495         break;
16496     }
16497     SendToProgram("bk\n", &first);
16498     bookOutput[0] = NULLCHAR;
16499     bookRequested = TRUE;
16500 }
16501
16502 void
16503 AboutGameEvent ()
16504 {
16505     char *tags = PGNTags(&gameInfo);
16506     TagsPopUp(tags, CmailMsg());
16507     free(tags);
16508 }
16509
16510 /* end button procedures */
16511
16512 void
16513 PrintPosition (FILE *fp, int move)
16514 {
16515     int i, j;
16516
16517     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16518         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16519             char c = PieceToChar(boards[move][i][j]);
16520             fputc(c == '?' ? '.' : c, fp);
16521             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16522         }
16523     }
16524     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16525       fprintf(fp, "white to play\n");
16526     else
16527       fprintf(fp, "black to play\n");
16528 }
16529
16530 void
16531 PrintOpponents (FILE *fp)
16532 {
16533     if (gameInfo.white != NULL) {
16534         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16535     } else {
16536         fprintf(fp, "\n");
16537     }
16538 }
16539
16540 /* Find last component of program's own name, using some heuristics */
16541 void
16542 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16543 {
16544     char *p, *q, c;
16545     int local = (strcmp(host, "localhost") == 0);
16546     while (!local && (p = strchr(prog, ';')) != NULL) {
16547         p++;
16548         while (*p == ' ') p++;
16549         prog = p;
16550     }
16551     if (*prog == '"' || *prog == '\'') {
16552         q = strchr(prog + 1, *prog);
16553     } else {
16554         q = strchr(prog, ' ');
16555     }
16556     if (q == NULL) q = prog + strlen(prog);
16557     p = q;
16558     while (p >= prog && *p != '/' && *p != '\\') p--;
16559     p++;
16560     if(p == prog && *p == '"') p++;
16561     c = *q; *q = 0;
16562     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16563     memcpy(buf, p, q - p);
16564     buf[q - p] = NULLCHAR;
16565     if (!local) {
16566         strcat(buf, "@");
16567         strcat(buf, host);
16568     }
16569 }
16570
16571 char *
16572 TimeControlTagValue ()
16573 {
16574     char buf[MSG_SIZ];
16575     if (!appData.clockMode) {
16576       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16577     } else if (movesPerSession > 0) {
16578       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16579     } else if (timeIncrement == 0) {
16580       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16581     } else {
16582       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16583     }
16584     return StrSave(buf);
16585 }
16586
16587 void
16588 SetGameInfo ()
16589 {
16590     /* This routine is used only for certain modes */
16591     VariantClass v = gameInfo.variant;
16592     ChessMove r = GameUnfinished;
16593     char *p = NULL;
16594
16595     if(keepInfo) return;
16596
16597     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16598         r = gameInfo.result;
16599         p = gameInfo.resultDetails;
16600         gameInfo.resultDetails = NULL;
16601     }
16602     ClearGameInfo(&gameInfo);
16603     gameInfo.variant = v;
16604
16605     switch (gameMode) {
16606       case MachinePlaysWhite:
16607         gameInfo.event = StrSave( appData.pgnEventHeader );
16608         gameInfo.site = StrSave(HostName());
16609         gameInfo.date = PGNDate();
16610         gameInfo.round = StrSave("-");
16611         gameInfo.white = StrSave(first.tidy);
16612         gameInfo.black = StrSave(UserName());
16613         gameInfo.timeControl = TimeControlTagValue();
16614         break;
16615
16616       case MachinePlaysBlack:
16617         gameInfo.event = StrSave( appData.pgnEventHeader );
16618         gameInfo.site = StrSave(HostName());
16619         gameInfo.date = PGNDate();
16620         gameInfo.round = StrSave("-");
16621         gameInfo.white = StrSave(UserName());
16622         gameInfo.black = StrSave(first.tidy);
16623         gameInfo.timeControl = TimeControlTagValue();
16624         break;
16625
16626       case TwoMachinesPlay:
16627         gameInfo.event = StrSave( appData.pgnEventHeader );
16628         gameInfo.site = StrSave(HostName());
16629         gameInfo.date = PGNDate();
16630         if (roundNr > 0) {
16631             char buf[MSG_SIZ];
16632             snprintf(buf, MSG_SIZ, "%d", roundNr);
16633             gameInfo.round = StrSave(buf);
16634         } else {
16635             gameInfo.round = StrSave("-");
16636         }
16637         if (first.twoMachinesColor[0] == 'w') {
16638             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16639             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16640         } else {
16641             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16642             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16643         }
16644         gameInfo.timeControl = TimeControlTagValue();
16645         break;
16646
16647       case EditGame:
16648         gameInfo.event = StrSave("Edited game");
16649         gameInfo.site = StrSave(HostName());
16650         gameInfo.date = PGNDate();
16651         gameInfo.round = StrSave("-");
16652         gameInfo.white = StrSave("-");
16653         gameInfo.black = StrSave("-");
16654         gameInfo.result = r;
16655         gameInfo.resultDetails = p;
16656         break;
16657
16658       case EditPosition:
16659         gameInfo.event = StrSave("Edited position");
16660         gameInfo.site = StrSave(HostName());
16661         gameInfo.date = PGNDate();
16662         gameInfo.round = StrSave("-");
16663         gameInfo.white = StrSave("-");
16664         gameInfo.black = StrSave("-");
16665         break;
16666
16667       case IcsPlayingWhite:
16668       case IcsPlayingBlack:
16669       case IcsObserving:
16670       case IcsExamining:
16671         break;
16672
16673       case PlayFromGameFile:
16674         gameInfo.event = StrSave("Game from non-PGN file");
16675         gameInfo.site = StrSave(HostName());
16676         gameInfo.date = PGNDate();
16677         gameInfo.round = StrSave("-");
16678         gameInfo.white = StrSave("?");
16679         gameInfo.black = StrSave("?");
16680         break;
16681
16682       default:
16683         break;
16684     }
16685 }
16686
16687 void
16688 ReplaceComment (int index, char *text)
16689 {
16690     int len;
16691     char *p;
16692     float score;
16693
16694     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16695        pvInfoList[index-1].depth == len &&
16696        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16697        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16698     while (*text == '\n') text++;
16699     len = strlen(text);
16700     while (len > 0 && text[len - 1] == '\n') len--;
16701
16702     if (commentList[index] != NULL)
16703       free(commentList[index]);
16704
16705     if (len == 0) {
16706         commentList[index] = NULL;
16707         return;
16708     }
16709   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16710       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16711       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16712     commentList[index] = (char *) malloc(len + 2);
16713     strncpy(commentList[index], text, len);
16714     commentList[index][len] = '\n';
16715     commentList[index][len + 1] = NULLCHAR;
16716   } else {
16717     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16718     char *p;
16719     commentList[index] = (char *) malloc(len + 7);
16720     safeStrCpy(commentList[index], "{\n", 3);
16721     safeStrCpy(commentList[index]+2, text, len+1);
16722     commentList[index][len+2] = NULLCHAR;
16723     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16724     strcat(commentList[index], "\n}\n");
16725   }
16726 }
16727
16728 void
16729 CrushCRs (char *text)
16730 {
16731   char *p = text;
16732   char *q = text;
16733   char ch;
16734
16735   do {
16736     ch = *p++;
16737     if (ch == '\r') continue;
16738     *q++ = ch;
16739   } while (ch != '\0');
16740 }
16741
16742 void
16743 AppendComment (int index, char *text, Boolean addBraces)
16744 /* addBraces  tells if we should add {} */
16745 {
16746     int oldlen, len;
16747     char *old;
16748
16749 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16750     if(addBraces == 3) addBraces = 0; else // force appending literally
16751     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16752
16753     CrushCRs(text);
16754     while (*text == '\n') text++;
16755     len = strlen(text);
16756     while (len > 0 && text[len - 1] == '\n') len--;
16757     text[len] = NULLCHAR;
16758
16759     if (len == 0) return;
16760
16761     if (commentList[index] != NULL) {
16762       Boolean addClosingBrace = addBraces;
16763         old = commentList[index];
16764         oldlen = strlen(old);
16765         while(commentList[index][oldlen-1] ==  '\n')
16766           commentList[index][--oldlen] = NULLCHAR;
16767         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16768         safeStrCpy(commentList[index], old, oldlen + len + 6);
16769         free(old);
16770         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16771         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16772           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16773           while (*text == '\n') { text++; len--; }
16774           commentList[index][--oldlen] = NULLCHAR;
16775       }
16776         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16777         else          strcat(commentList[index], "\n");
16778         strcat(commentList[index], text);
16779         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16780         else          strcat(commentList[index], "\n");
16781     } else {
16782         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16783         if(addBraces)
16784           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16785         else commentList[index][0] = NULLCHAR;
16786         strcat(commentList[index], text);
16787         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16788         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16789     }
16790 }
16791
16792 static char *
16793 FindStr (char * text, char * sub_text)
16794 {
16795     char * result = strstr( text, sub_text );
16796
16797     if( result != NULL ) {
16798         result += strlen( sub_text );
16799     }
16800
16801     return result;
16802 }
16803
16804 /* [AS] Try to extract PV info from PGN comment */
16805 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16806 char *
16807 GetInfoFromComment (int index, char * text)
16808 {
16809     char * sep = text, *p;
16810
16811     if( text != NULL && index > 0 ) {
16812         int score = 0;
16813         int depth = 0;
16814         int time = -1, sec = 0, deci;
16815         char * s_eval = FindStr( text, "[%eval " );
16816         char * s_emt = FindStr( text, "[%emt " );
16817 #if 0
16818         if( s_eval != NULL || s_emt != NULL ) {
16819 #else
16820         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16821 #endif
16822             /* New style */
16823             char delim;
16824
16825             if( s_eval != NULL ) {
16826                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16827                     return text;
16828                 }
16829
16830                 if( delim != ']' ) {
16831                     return text;
16832                 }
16833             }
16834
16835             if( s_emt != NULL ) {
16836             }
16837                 return text;
16838         }
16839         else {
16840             /* We expect something like: [+|-]nnn.nn/dd */
16841             int score_lo = 0;
16842
16843             if(*text != '{') return text; // [HGM] braces: must be normal comment
16844
16845             sep = strchr( text, '/' );
16846             if( sep == NULL || sep < (text+4) ) {
16847                 return text;
16848             }
16849
16850             p = text;
16851             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16852             if(p[1] == '(') { // comment starts with PV
16853                p = strchr(p, ')'); // locate end of PV
16854                if(p == NULL || sep < p+5) return text;
16855                // at this point we have something like "{(.*) +0.23/6 ..."
16856                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16857                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16858                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16859             }
16860             time = -1; sec = -1; deci = -1;
16861             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16862                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16863                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16864                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16865                 return text;
16866             }
16867
16868             if( score_lo < 0 || score_lo >= 100 ) {
16869                 return text;
16870             }
16871
16872             if(sec >= 0) time = 600*time + 10*sec; else
16873             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16874
16875             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16876
16877             /* [HGM] PV time: now locate end of PV info */
16878             while( *++sep >= '0' && *sep <= '9'); // strip depth
16879             if(time >= 0)
16880             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16881             if(sec >= 0)
16882             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16883             if(deci >= 0)
16884             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16885             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16886         }
16887
16888         if( depth <= 0 ) {
16889             return text;
16890         }
16891
16892         if( time < 0 ) {
16893             time = -1;
16894         }
16895
16896         pvInfoList[index-1].depth = depth;
16897         pvInfoList[index-1].score = score;
16898         pvInfoList[index-1].time  = 10*time; // centi-sec
16899         if(*sep == '}') *sep = 0; else *--sep = '{';
16900         if(p != text) {
16901             while(*p++ = *sep++)
16902                                 ;
16903             sep = text;
16904         } // squeeze out space between PV and comment, and return both
16905     }
16906     return sep;
16907 }
16908
16909 void
16910 SendToProgram (char *message, ChessProgramState *cps)
16911 {
16912     int count, outCount, error;
16913     char buf[MSG_SIZ];
16914
16915     if (cps->pr == NoProc) return;
16916     Attention(cps);
16917
16918     if (appData.debugMode) {
16919         TimeMark now;
16920         GetTimeMark(&now);
16921         fprintf(debugFP, "%ld >%-6s: %s",
16922                 SubtractTimeMarks(&now, &programStartTime),
16923                 cps->which, message);
16924         if(serverFP)
16925             fprintf(serverFP, "%ld >%-6s: %s",
16926                 SubtractTimeMarks(&now, &programStartTime),
16927                 cps->which, message), fflush(serverFP);
16928     }
16929
16930     count = strlen(message);
16931     outCount = OutputToProcess(cps->pr, message, count, &error);
16932     if (outCount < count && !exiting
16933                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16934       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16935       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16936         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16937             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16938                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16939                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16940                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16941             } else {
16942                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16943                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16944                 gameInfo.result = res;
16945             }
16946             gameInfo.resultDetails = StrSave(buf);
16947         }
16948         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16949         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16950     }
16951 }
16952
16953 void
16954 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16955 {
16956     char *end_str;
16957     char buf[MSG_SIZ];
16958     ChessProgramState *cps = (ChessProgramState *)closure;
16959
16960     if (isr != cps->isr) return; /* Killed intentionally */
16961     if (count <= 0) {
16962         if (count == 0) {
16963             RemoveInputSource(cps->isr);
16964             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16965                     _(cps->which), cps->program);
16966             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16967             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16968                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16969                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16970                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16971                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16972                 } else {
16973                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16974                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16975                     gameInfo.result = res;
16976                 }
16977                 gameInfo.resultDetails = StrSave(buf);
16978             }
16979             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16980             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16981         } else {
16982             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16983                     _(cps->which), cps->program);
16984             RemoveInputSource(cps->isr);
16985
16986             /* [AS] Program is misbehaving badly... kill it */
16987             if( count == -2 ) {
16988                 DestroyChildProcess( cps->pr, 9 );
16989                 cps->pr = NoProc;
16990             }
16991
16992             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16993         }
16994         return;
16995     }
16996
16997     if ((end_str = strchr(message, '\r')) != NULL)
16998       *end_str = NULLCHAR;
16999     if ((end_str = strchr(message, '\n')) != NULL)
17000       *end_str = NULLCHAR;
17001
17002     if (appData.debugMode) {
17003         TimeMark now; int print = 1;
17004         char *quote = ""; char c; int i;
17005
17006         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17007                 char start = message[0];
17008                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17009                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17010                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
17011                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17012                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17013                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
17014                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17015                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
17016                    sscanf(message, "hint: %c", &c)!=1 &&
17017                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
17018                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17019                     print = (appData.engineComments >= 2);
17020                 }
17021                 message[0] = start; // restore original message
17022         }
17023         if(print) {
17024                 GetTimeMark(&now);
17025                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17026                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17027                         quote,
17028                         message);
17029                 if(serverFP)
17030                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
17031                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17032                         quote,
17033                         message), fflush(serverFP);
17034         }
17035     }
17036
17037     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17038     if (appData.icsEngineAnalyze) {
17039         if (strstr(message, "whisper") != NULL ||
17040              strstr(message, "kibitz") != NULL ||
17041             strstr(message, "tellics") != NULL) return;
17042     }
17043
17044     HandleMachineMove(message, cps);
17045 }
17046
17047
17048 void
17049 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17050 {
17051     char buf[MSG_SIZ];
17052     int seconds;
17053
17054     if( timeControl_2 > 0 ) {
17055         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17056             tc = timeControl_2;
17057         }
17058     }
17059     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17060     inc /= cps->timeOdds;
17061     st  /= cps->timeOdds;
17062
17063     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17064
17065     if (st > 0) {
17066       /* Set exact time per move, normally using st command */
17067       if (cps->stKludge) {
17068         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17069         seconds = st % 60;
17070         if (seconds == 0) {
17071           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17072         } else {
17073           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17074         }
17075       } else {
17076         snprintf(buf, MSG_SIZ, "st %d\n", st);
17077       }
17078     } else {
17079       /* Set conventional or incremental time control, using level command */
17080       if (seconds == 0) {
17081         /* Note old gnuchess bug -- minutes:seconds used to not work.
17082            Fixed in later versions, but still avoid :seconds
17083            when seconds is 0. */
17084         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17085       } else {
17086         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17087                  seconds, inc/1000.);
17088       }
17089     }
17090     SendToProgram(buf, cps);
17091
17092     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17093     /* Orthogonally, limit search to given depth */
17094     if (sd > 0) {
17095       if (cps->sdKludge) {
17096         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17097       } else {
17098         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17099       }
17100       SendToProgram(buf, cps);
17101     }
17102
17103     if(cps->nps >= 0) { /* [HGM] nps */
17104         if(cps->supportsNPS == FALSE)
17105           cps->nps = -1; // don't use if engine explicitly says not supported!
17106         else {
17107           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17108           SendToProgram(buf, cps);
17109         }
17110     }
17111 }
17112
17113 ChessProgramState *
17114 WhitePlayer ()
17115 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17116 {
17117     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17118        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17119         return &second;
17120     return &first;
17121 }
17122
17123 void
17124 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17125 {
17126     char message[MSG_SIZ];
17127     long time, otime;
17128
17129     /* Note: this routine must be called when the clocks are stopped
17130        or when they have *just* been set or switched; otherwise
17131        it will be off by the time since the current tick started.
17132     */
17133     if (machineWhite) {
17134         time = whiteTimeRemaining / 10;
17135         otime = blackTimeRemaining / 10;
17136     } else {
17137         time = blackTimeRemaining / 10;
17138         otime = whiteTimeRemaining / 10;
17139     }
17140     /* [HGM] translate opponent's time by time-odds factor */
17141     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17142
17143     if (time <= 0) time = 1;
17144     if (otime <= 0) otime = 1;
17145
17146     snprintf(message, MSG_SIZ, "time %ld\n", time);
17147     SendToProgram(message, cps);
17148
17149     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17150     SendToProgram(message, cps);
17151 }
17152
17153 char *
17154 EngineDefinedVariant (ChessProgramState *cps, int n)
17155 {   // return name of n-th unknown variant that engine supports
17156     static char buf[MSG_SIZ];
17157     char *p, *s = cps->variants;
17158     if(!s) return NULL;
17159     do { // parse string from variants feature
17160       VariantClass v;
17161         p = strchr(s, ',');
17162         if(p) *p = NULLCHAR;
17163       v = StringToVariant(s);
17164       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17165         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17166             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17167                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17168                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17169                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17170             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17171         }
17172         if(p) *p++ = ',';
17173         if(n < 0) return buf;
17174     } while(s = p);
17175     return NULL;
17176 }
17177
17178 int
17179 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17180 {
17181   char buf[MSG_SIZ];
17182   int len = strlen(name);
17183   int val;
17184
17185   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17186     (*p) += len + 1;
17187     sscanf(*p, "%d", &val);
17188     *loc = (val != 0);
17189     while (**p && **p != ' ')
17190       (*p)++;
17191     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17192     SendToProgram(buf, cps);
17193     return TRUE;
17194   }
17195   return FALSE;
17196 }
17197
17198 int
17199 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17200 {
17201   char buf[MSG_SIZ];
17202   int len = strlen(name);
17203   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17204     (*p) += len + 1;
17205     sscanf(*p, "%d", loc);
17206     while (**p && **p != ' ') (*p)++;
17207     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17208     SendToProgram(buf, cps);
17209     return TRUE;
17210   }
17211   return FALSE;
17212 }
17213
17214 int
17215 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17216 {
17217   char buf[MSG_SIZ];
17218   int len = strlen(name);
17219   if (strncmp((*p), name, len) == 0
17220       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17221     (*p) += len + 2;
17222     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
17223     FREE(*loc); *loc = malloc(len);
17224     strncpy(*loc, *p, len);
17225     sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17226     while (**p && **p != '\"') (*p)++;
17227     if (**p == '\"') (*p)++;
17228     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17229     SendToProgram(buf, cps);
17230     return TRUE;
17231   }
17232   return FALSE;
17233 }
17234
17235 int
17236 ParseOption (Option *opt, ChessProgramState *cps)
17237 // [HGM] options: process the string that defines an engine option, and determine
17238 // name, type, default value, and allowed value range
17239 {
17240         char *p, *q, buf[MSG_SIZ];
17241         int n, min = (-1)<<31, max = 1<<31, def;
17242
17243         opt->target = &opt->value;   // OK for spin/slider and checkbox
17244         if(p = strstr(opt->name, " -spin ")) {
17245             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17246             if(max < min) max = min; // enforce consistency
17247             if(def < min) def = min;
17248             if(def > max) def = max;
17249             opt->value = def;
17250             opt->min = min;
17251             opt->max = max;
17252             opt->type = Spin;
17253         } else if((p = strstr(opt->name, " -slider "))) {
17254             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17255             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17256             if(max < min) max = min; // enforce consistency
17257             if(def < min) def = min;
17258             if(def > max) def = max;
17259             opt->value = def;
17260             opt->min = min;
17261             opt->max = max;
17262             opt->type = Spin; // Slider;
17263         } else if((p = strstr(opt->name, " -string "))) {
17264             opt->textValue = p+9;
17265             opt->type = TextBox;
17266             opt->target = &opt->textValue;
17267         } else if((p = strstr(opt->name, " -file "))) {
17268             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17269             opt->target = opt->textValue = p+7;
17270             opt->type = FileName; // FileName;
17271             opt->target = &opt->textValue;
17272         } else if((p = strstr(opt->name, " -path "))) {
17273             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17274             opt->target = opt->textValue = p+7;
17275             opt->type = PathName; // PathName;
17276             opt->target = &opt->textValue;
17277         } else if(p = strstr(opt->name, " -check ")) {
17278             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17279             opt->value = (def != 0);
17280             opt->type = CheckBox;
17281         } else if(p = strstr(opt->name, " -combo ")) {
17282             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17283             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17284             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17285             opt->value = n = 0;
17286             while(q = StrStr(q, " /// ")) {
17287                 n++; *q = 0;    // count choices, and null-terminate each of them
17288                 q += 5;
17289                 if(*q == '*') { // remember default, which is marked with * prefix
17290                     q++;
17291                     opt->value = n;
17292                 }
17293                 cps->comboList[cps->comboCnt++] = q;
17294             }
17295             cps->comboList[cps->comboCnt++] = NULL;
17296             opt->max = n + 1;
17297             opt->type = ComboBox;
17298         } else if(p = strstr(opt->name, " -button")) {
17299             opt->type = Button;
17300         } else if(p = strstr(opt->name, " -save")) {
17301             opt->type = SaveButton;
17302         } else return FALSE;
17303         *p = 0; // terminate option name
17304         // now look if the command-line options define a setting for this engine option.
17305         if(cps->optionSettings && cps->optionSettings[0])
17306             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17307         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17308           snprintf(buf, MSG_SIZ, "option %s", p);
17309                 if(p = strstr(buf, ",")) *p = 0;
17310                 if(q = strchr(buf, '=')) switch(opt->type) {
17311                     case ComboBox:
17312                         for(n=0; n<opt->max; n++)
17313                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17314                         break;
17315                     case TextBox:
17316                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17317                         break;
17318                     case Spin:
17319                     case CheckBox:
17320                         opt->value = atoi(q+1);
17321                     default:
17322                         break;
17323                 }
17324                 strcat(buf, "\n");
17325                 SendToProgram(buf, cps);
17326         }
17327         *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17328         if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17329         return TRUE;
17330 }
17331
17332 void
17333 FeatureDone (ChessProgramState *cps, int val)
17334 {
17335   DelayedEventCallback cb = GetDelayedEvent();
17336   if ((cb == InitBackEnd3 && cps == &first) ||
17337       (cb == SettingsMenuIfReady && cps == &second) ||
17338       (cb == LoadEngine) ||
17339       (cb == TwoMachinesEventIfReady)) {
17340     CancelDelayedEvent();
17341     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17342   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17343   cps->initDone = val;
17344   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17345 }
17346
17347 /* Parse feature command from engine */
17348 void
17349 ParseFeatures (char *args, ChessProgramState *cps)
17350 {
17351   char *p = args;
17352   char *q = NULL;
17353   int val;
17354   char buf[MSG_SIZ];
17355
17356   for (;;) {
17357     while (*p == ' ') p++;
17358     if (*p == NULLCHAR) return;
17359
17360     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17361     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17362     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17363     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17364     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17365     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17366     if (BoolFeature(&p, "reuse", &val, cps)) {
17367       /* Engine can disable reuse, but can't enable it if user said no */
17368       if (!val) cps->reuse = FALSE;
17369       continue;
17370     }
17371     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17372     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17373       if (gameMode == TwoMachinesPlay) {
17374         DisplayTwoMachinesTitle();
17375       } else {
17376         DisplayTitle("");
17377       }
17378       continue;
17379     }
17380     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17381     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17382     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17383     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17384     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17385     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17386     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17387     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17388     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17389     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17390     if (IntFeature(&p, "done", &val, cps)) {
17391       FeatureDone(cps, val);
17392       continue;
17393     }
17394     /* Added by Tord: */
17395     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17396     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17397     /* End of additions by Tord */
17398
17399     /* [HGM] added features: */
17400     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17401     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17402     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17403     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17404     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17405     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17406     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17407     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17408         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17409         if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17410         FREE(cps->option[cps->nrOptions].name);
17411         cps->option[cps->nrOptions].name = q; q = NULL;
17412         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17413           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17414             SendToProgram(buf, cps);
17415             continue;
17416         }
17417         if(cps->nrOptions >= MAX_OPTIONS) {
17418             cps->nrOptions--;
17419             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17420             DisplayError(buf, 0);
17421         }
17422         continue;
17423     }
17424     /* End of additions by HGM */
17425
17426     /* unknown feature: complain and skip */
17427     q = p;
17428     while (*q && *q != '=') q++;
17429     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17430     SendToProgram(buf, cps);
17431     p = q;
17432     if (*p == '=') {
17433       p++;
17434       if (*p == '\"') {
17435         p++;
17436         while (*p && *p != '\"') p++;
17437         if (*p == '\"') p++;
17438       } else {
17439         while (*p && *p != ' ') p++;
17440       }
17441     }
17442   }
17443
17444 }
17445
17446 void
17447 PeriodicUpdatesEvent (int newState)
17448 {
17449     if (newState == appData.periodicUpdates)
17450       return;
17451
17452     appData.periodicUpdates=newState;
17453
17454     /* Display type changes, so update it now */
17455 //    DisplayAnalysis();
17456
17457     /* Get the ball rolling again... */
17458     if (newState) {
17459         AnalysisPeriodicEvent(1);
17460         StartAnalysisClock();
17461     }
17462 }
17463
17464 void
17465 PonderNextMoveEvent (int newState)
17466 {
17467     if (newState == appData.ponderNextMove) return;
17468     if (gameMode == EditPosition) EditPositionDone(TRUE);
17469     if (newState) {
17470         SendToProgram("hard\n", &first);
17471         if (gameMode == TwoMachinesPlay) {
17472             SendToProgram("hard\n", &second);
17473         }
17474     } else {
17475         SendToProgram("easy\n", &first);
17476         thinkOutput[0] = NULLCHAR;
17477         if (gameMode == TwoMachinesPlay) {
17478             SendToProgram("easy\n", &second);
17479         }
17480     }
17481     appData.ponderNextMove = newState;
17482 }
17483
17484 void
17485 NewSettingEvent (int option, int *feature, char *command, int value)
17486 {
17487     char buf[MSG_SIZ];
17488
17489     if (gameMode == EditPosition) EditPositionDone(TRUE);
17490     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17491     if(feature == NULL || *feature) SendToProgram(buf, &first);
17492     if (gameMode == TwoMachinesPlay) {
17493         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17494     }
17495 }
17496
17497 void
17498 ShowThinkingEvent ()
17499 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17500 {
17501     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17502     int newState = appData.showThinking
17503         // [HGM] thinking: other features now need thinking output as well
17504         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17505
17506     if (oldState == newState) return;
17507     oldState = newState;
17508     if (gameMode == EditPosition) EditPositionDone(TRUE);
17509     if (oldState) {
17510         SendToProgram("post\n", &first);
17511         if (gameMode == TwoMachinesPlay) {
17512             SendToProgram("post\n", &second);
17513         }
17514     } else {
17515         SendToProgram("nopost\n", &first);
17516         thinkOutput[0] = NULLCHAR;
17517         if (gameMode == TwoMachinesPlay) {
17518             SendToProgram("nopost\n", &second);
17519         }
17520     }
17521 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17522 }
17523
17524 void
17525 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17526 {
17527   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17528   if (pr == NoProc) return;
17529   AskQuestion(title, question, replyPrefix, pr);
17530 }
17531
17532 void
17533 TypeInEvent (char firstChar)
17534 {
17535     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17536         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17537         gameMode == AnalyzeMode || gameMode == EditGame ||
17538         gameMode == EditPosition || gameMode == IcsExamining ||
17539         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17540         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17541                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17542                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17543         gameMode == Training) PopUpMoveDialog(firstChar);
17544 }
17545
17546 void
17547 TypeInDoneEvent (char *move)
17548 {
17549         Board board;
17550         int n, fromX, fromY, toX, toY;
17551         char promoChar;
17552         ChessMove moveType;
17553
17554         // [HGM] FENedit
17555         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17556                 EditPositionPasteFEN(move);
17557                 return;
17558         }
17559         // [HGM] movenum: allow move number to be typed in any mode
17560         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17561           ToNrEvent(2*n-1);
17562           return;
17563         }
17564         // undocumented kludge: allow command-line option to be typed in!
17565         // (potentially fatal, and does not implement the effect of the option.)
17566         // should only be used for options that are values on which future decisions will be made,
17567         // and definitely not on options that would be used during initialization.
17568         if(strstr(move, "!!! -") == move) {
17569             ParseArgsFromString(move+4);
17570             return;
17571         }
17572
17573       if (gameMode != EditGame && currentMove != forwardMostMove &&
17574         gameMode != Training) {
17575         DisplayMoveError(_("Displayed move is not current"));
17576       } else {
17577         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17578           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17579         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17580         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17581           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17582           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17583         } else {
17584           DisplayMoveError(_("Could not parse move"));
17585         }
17586       }
17587 }
17588
17589 void
17590 DisplayMove (int moveNumber)
17591 {
17592     char message[MSG_SIZ];
17593     char res[MSG_SIZ];
17594     char cpThinkOutput[MSG_SIZ];
17595
17596     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17597
17598     if (moveNumber == forwardMostMove - 1 ||
17599         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17600
17601         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17602
17603         if (strchr(cpThinkOutput, '\n')) {
17604             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17605         }
17606     } else {
17607         *cpThinkOutput = NULLCHAR;
17608     }
17609
17610     /* [AS] Hide thinking from human user */
17611     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17612         *cpThinkOutput = NULLCHAR;
17613         if( thinkOutput[0] != NULLCHAR ) {
17614             int i;
17615
17616             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17617                 cpThinkOutput[i] = '.';
17618             }
17619             cpThinkOutput[i] = NULLCHAR;
17620             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17621         }
17622     }
17623
17624     if (moveNumber == forwardMostMove - 1 &&
17625         gameInfo.resultDetails != NULL) {
17626         if (gameInfo.resultDetails[0] == NULLCHAR) {
17627           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17628         } else {
17629           snprintf(res, MSG_SIZ, " {%s} %s",
17630                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17631         }
17632     } else {
17633         res[0] = NULLCHAR;
17634     }
17635
17636     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17637         DisplayMessage(res, cpThinkOutput);
17638     } else {
17639       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17640                 WhiteOnMove(moveNumber) ? " " : ".. ",
17641                 parseList[moveNumber], res);
17642         DisplayMessage(message, cpThinkOutput);
17643     }
17644 }
17645
17646 void
17647 DisplayComment (int moveNumber, char *text)
17648 {
17649     char title[MSG_SIZ];
17650
17651     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17652       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17653     } else {
17654       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17655               WhiteOnMove(moveNumber) ? " " : ".. ",
17656               parseList[moveNumber]);
17657     }
17658     if (text != NULL && (appData.autoDisplayComment || commentUp))
17659         CommentPopUp(title, text);
17660 }
17661
17662 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17663  * might be busy thinking or pondering.  It can be omitted if your
17664  * gnuchess is configured to stop thinking immediately on any user
17665  * input.  However, that gnuchess feature depends on the FIONREAD
17666  * ioctl, which does not work properly on some flavors of Unix.
17667  */
17668 void
17669 Attention (ChessProgramState *cps)
17670 {
17671 #if ATTENTION
17672     if (!cps->useSigint) return;
17673     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17674     switch (gameMode) {
17675       case MachinePlaysWhite:
17676       case MachinePlaysBlack:
17677       case TwoMachinesPlay:
17678       case IcsPlayingWhite:
17679       case IcsPlayingBlack:
17680       case AnalyzeMode:
17681       case AnalyzeFile:
17682         /* Skip if we know it isn't thinking */
17683         if (!cps->maybeThinking) return;
17684         if (appData.debugMode)
17685           fprintf(debugFP, "Interrupting %s\n", cps->which);
17686         InterruptChildProcess(cps->pr);
17687         cps->maybeThinking = FALSE;
17688         break;
17689       default:
17690         break;
17691     }
17692 #endif /*ATTENTION*/
17693 }
17694
17695 int
17696 CheckFlags ()
17697 {
17698     if (whiteTimeRemaining <= 0) {
17699         if (!whiteFlag) {
17700             whiteFlag = TRUE;
17701             if (appData.icsActive) {
17702                 if (appData.autoCallFlag &&
17703                     gameMode == IcsPlayingBlack && !blackFlag) {
17704                   SendToICS(ics_prefix);
17705                   SendToICS("flag\n");
17706                 }
17707             } else {
17708                 if (blackFlag) {
17709                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17710                 } else {
17711                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17712                     if (appData.autoCallFlag) {
17713                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17714                         return TRUE;
17715                     }
17716                 }
17717             }
17718         }
17719     }
17720     if (blackTimeRemaining <= 0) {
17721         if (!blackFlag) {
17722             blackFlag = TRUE;
17723             if (appData.icsActive) {
17724                 if (appData.autoCallFlag &&
17725                     gameMode == IcsPlayingWhite && !whiteFlag) {
17726                   SendToICS(ics_prefix);
17727                   SendToICS("flag\n");
17728                 }
17729             } else {
17730                 if (whiteFlag) {
17731                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17732                 } else {
17733                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17734                     if (appData.autoCallFlag) {
17735                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17736                         return TRUE;
17737                     }
17738                 }
17739             }
17740         }
17741     }
17742     return FALSE;
17743 }
17744
17745 void
17746 CheckTimeControl ()
17747 {
17748     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17749         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17750
17751     /*
17752      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17753      */
17754     if ( !WhiteOnMove(forwardMostMove) ) {
17755         /* White made time control */
17756         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17757         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17758         /* [HGM] time odds: correct new time quota for time odds! */
17759                                             / WhitePlayer()->timeOdds;
17760         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17761     } else {
17762         lastBlack -= blackTimeRemaining;
17763         /* Black made time control */
17764         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17765                                             / WhitePlayer()->other->timeOdds;
17766         lastWhite = whiteTimeRemaining;
17767     }
17768 }
17769
17770 void
17771 DisplayBothClocks ()
17772 {
17773     int wom = gameMode == EditPosition ?
17774       !blackPlaysFirst : WhiteOnMove(currentMove);
17775     DisplayWhiteClock(whiteTimeRemaining, wom);
17776     DisplayBlackClock(blackTimeRemaining, !wom);
17777 }
17778
17779
17780 /* Timekeeping seems to be a portability nightmare.  I think everyone
17781    has ftime(), but I'm really not sure, so I'm including some ifdefs
17782    to use other calls if you don't.  Clocks will be less accurate if
17783    you have neither ftime nor gettimeofday.
17784 */
17785
17786 /* VS 2008 requires the #include outside of the function */
17787 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17788 #include <sys/timeb.h>
17789 #endif
17790
17791 /* Get the current time as a TimeMark */
17792 void
17793 GetTimeMark (TimeMark *tm)
17794 {
17795 #if HAVE_GETTIMEOFDAY
17796
17797     struct timeval timeVal;
17798     struct timezone timeZone;
17799
17800     gettimeofday(&timeVal, &timeZone);
17801     tm->sec = (long) timeVal.tv_sec;
17802     tm->ms = (int) (timeVal.tv_usec / 1000L);
17803
17804 #else /*!HAVE_GETTIMEOFDAY*/
17805 #if HAVE_FTIME
17806
17807 // include <sys/timeb.h> / moved to just above start of function
17808     struct timeb timeB;
17809
17810     ftime(&timeB);
17811     tm->sec = (long) timeB.time;
17812     tm->ms = (int) timeB.millitm;
17813
17814 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17815     tm->sec = (long) time(NULL);
17816     tm->ms = 0;
17817 #endif
17818 #endif
17819 }
17820
17821 /* Return the difference in milliseconds between two
17822    time marks.  We assume the difference will fit in a long!
17823 */
17824 long
17825 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17826 {
17827     return 1000L*(tm2->sec - tm1->sec) +
17828            (long) (tm2->ms - tm1->ms);
17829 }
17830
17831
17832 /*
17833  * Code to manage the game clocks.
17834  *
17835  * In tournament play, black starts the clock and then white makes a move.
17836  * We give the human user a slight advantage if he is playing white---the
17837  * clocks don't run until he makes his first move, so it takes zero time.
17838  * Also, we don't account for network lag, so we could get out of sync
17839  * with GNU Chess's clock -- but then, referees are always right.
17840  */
17841
17842 static TimeMark tickStartTM;
17843 static long intendedTickLength;
17844
17845 long
17846 NextTickLength (long timeRemaining)
17847 {
17848     long nominalTickLength, nextTickLength;
17849
17850     if (timeRemaining > 0L && timeRemaining <= 10000L)
17851       nominalTickLength = 100L;
17852     else
17853       nominalTickLength = 1000L;
17854     nextTickLength = timeRemaining % nominalTickLength;
17855     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17856
17857     return nextTickLength;
17858 }
17859
17860 /* Adjust clock one minute up or down */
17861 void
17862 AdjustClock (Boolean which, int dir)
17863 {
17864     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17865     if(which) blackTimeRemaining += 60000*dir;
17866     else      whiteTimeRemaining += 60000*dir;
17867     DisplayBothClocks();
17868     adjustedClock = TRUE;
17869 }
17870
17871 /* Stop clocks and reset to a fresh time control */
17872 void
17873 ResetClocks ()
17874 {
17875     (void) StopClockTimer();
17876     if (appData.icsActive) {
17877         whiteTimeRemaining = blackTimeRemaining = 0;
17878     } else if (searchTime) {
17879         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17880         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17881     } else { /* [HGM] correct new time quote for time odds */
17882         whiteTC = blackTC = fullTimeControlString;
17883         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17884         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17885     }
17886     if (whiteFlag || blackFlag) {
17887         DisplayTitle("");
17888         whiteFlag = blackFlag = FALSE;
17889     }
17890     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17891     DisplayBothClocks();
17892     adjustedClock = FALSE;
17893 }
17894
17895 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17896
17897 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17898
17899 /* Decrement running clock by amount of time that has passed */
17900 void
17901 DecrementClocks ()
17902 {
17903     long tRemaining;
17904     long lastTickLength, fudge;
17905     TimeMark now;
17906
17907     if (!appData.clockMode) return;
17908     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17909
17910     GetTimeMark(&now);
17911
17912     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17913
17914     /* Fudge if we woke up a little too soon */
17915     fudge = intendedTickLength - lastTickLength;
17916     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17917
17918     if (WhiteOnMove(forwardMostMove)) {
17919         if(whiteNPS >= 0) lastTickLength = 0;
17920          tRemaining = whiteTimeRemaining -= lastTickLength;
17921         if( tRemaining < 0 && !appData.icsActive) {
17922             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17923             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17924                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17925                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17926             }
17927         }
17928         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
17929         DisplayWhiteClock(whiteTimeRemaining - fudge,
17930                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17931         timeSuffix = 0;
17932     } else {
17933         if(blackNPS >= 0) lastTickLength = 0;
17934          tRemaining = blackTimeRemaining -= lastTickLength;
17935         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17936             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17937             if(suddenDeath) {
17938                 blackStartMove = forwardMostMove;
17939                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17940             }
17941         }
17942         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
17943         DisplayBlackClock(blackTimeRemaining - fudge,
17944                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17945         timeSuffix = 0;
17946     }
17947     if (CheckFlags()) return;
17948
17949     if(twoBoards) { // count down secondary board's clocks as well
17950         activePartnerTime -= lastTickLength;
17951         partnerUp = 1;
17952         if(activePartner == 'W')
17953             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17954         else
17955             DisplayBlackClock(activePartnerTime, TRUE);
17956         partnerUp = 0;
17957     }
17958
17959     tickStartTM = now;
17960     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
17961     StartClockTimer(intendedTickLength);
17962
17963     /* if the time remaining has fallen below the alarm threshold, sound the
17964      * alarm. if the alarm has sounded and (due to a takeback or time control
17965      * with increment) the time remaining has increased to a level above the
17966      * threshold, reset the alarm so it can sound again.
17967      */
17968
17969     if (appData.icsActive && appData.icsAlarm) {
17970
17971         /* make sure we are dealing with the user's clock */
17972         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17973                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17974            )) return;
17975
17976         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
17977             alarmSounded = FALSE;
17978         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
17979             PlayAlarmSound();
17980             alarmSounded = TRUE;
17981         }
17982     }
17983 }
17984
17985
17986 /* A player has just moved, so stop the previously running
17987    clock and (if in clock mode) start the other one.
17988    We redisplay both clocks in case we're in ICS mode, because
17989    ICS gives us an update to both clocks after every move.
17990    Note that this routine is called *after* forwardMostMove
17991    is updated, so the last fractional tick must be subtracted
17992    from the color that is *not* on move now.
17993 */
17994 void
17995 SwitchClocks (int newMoveNr)
17996 {
17997     long lastTickLength;
17998     TimeMark now;
17999     int flagged = FALSE;
18000
18001     GetTimeMark(&now);
18002
18003     if (StopClockTimer() && appData.clockMode) {
18004         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18005         if (!WhiteOnMove(forwardMostMove)) {
18006             if(blackNPS >= 0) lastTickLength = 0;
18007             blackTimeRemaining -= lastTickLength;
18008            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18009 //         if(pvInfoList[forwardMostMove].time == -1)
18010                  pvInfoList[forwardMostMove].time =               // use GUI time
18011                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18012         } else {
18013            if(whiteNPS >= 0) lastTickLength = 0;
18014            whiteTimeRemaining -= 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 =
18018                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18019         }
18020         flagged = CheckFlags();
18021     }
18022     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18023     CheckTimeControl();
18024
18025     if (flagged || !appData.clockMode) return;
18026
18027     switch (gameMode) {
18028       case MachinePlaysBlack:
18029       case MachinePlaysWhite:
18030       case BeginningOfGame:
18031         if (pausing) return;
18032         break;
18033
18034       case EditGame:
18035       case PlayFromGameFile:
18036       case IcsExamining:
18037         return;
18038
18039       default:
18040         break;
18041     }
18042
18043     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18044         if(WhiteOnMove(forwardMostMove))
18045              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18046         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18047     }
18048
18049     tickStartTM = now;
18050     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18051       whiteTimeRemaining : blackTimeRemaining);
18052     StartClockTimer(intendedTickLength);
18053 }
18054
18055
18056 /* Stop both clocks */
18057 void
18058 StopClocks ()
18059 {
18060     long lastTickLength;
18061     TimeMark now;
18062
18063     if (!StopClockTimer()) return;
18064     if (!appData.clockMode) return;
18065
18066     GetTimeMark(&now);
18067
18068     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18069     if (WhiteOnMove(forwardMostMove)) {
18070         if(whiteNPS >= 0) lastTickLength = 0;
18071         whiteTimeRemaining -= lastTickLength;
18072         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18073     } else {
18074         if(blackNPS >= 0) lastTickLength = 0;
18075         blackTimeRemaining -= lastTickLength;
18076         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18077     }
18078     CheckFlags();
18079 }
18080
18081 /* Start clock of player on move.  Time may have been reset, so
18082    if clock is already running, stop and restart it. */
18083 void
18084 StartClocks ()
18085 {
18086     (void) StopClockTimer(); /* in case it was running already */
18087     DisplayBothClocks();
18088     if (CheckFlags()) return;
18089
18090     if (!appData.clockMode) return;
18091     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18092
18093     GetTimeMark(&tickStartTM);
18094     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18095       whiteTimeRemaining : blackTimeRemaining);
18096
18097    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18098     whiteNPS = blackNPS = -1;
18099     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18100        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18101         whiteNPS = first.nps;
18102     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18103        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18104         blackNPS = first.nps;
18105     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18106         whiteNPS = second.nps;
18107     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18108         blackNPS = second.nps;
18109     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18110
18111     StartClockTimer(intendedTickLength);
18112 }
18113
18114 char *
18115 TimeString (long ms)
18116 {
18117     long second, minute, hour, day;
18118     char *sign = "";
18119     static char buf[40], moveTime[8];
18120
18121     if (ms > 0 && ms <= 9900) {
18122       /* convert milliseconds to tenths, rounding up */
18123       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18124
18125       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18126       return buf;
18127     }
18128
18129     /* convert milliseconds to seconds, rounding up */
18130     /* use floating point to avoid strangeness of integer division
18131        with negative dividends on many machines */
18132     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18133
18134     if (second < 0) {
18135         sign = "-";
18136         second = -second;
18137     }
18138
18139     day = second / (60 * 60 * 24);
18140     second = second % (60 * 60 * 24);
18141     hour = second / (60 * 60);
18142     second = second % (60 * 60);
18143     minute = second / 60;
18144     second = second % 60;
18145
18146     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18147     else *moveTime = NULLCHAR;
18148
18149     if (day > 0)
18150       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18151               sign, day, hour, minute, second, moveTime);
18152     else if (hour > 0)
18153       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18154     else
18155       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18156
18157     return buf;
18158 }
18159
18160
18161 /*
18162  * This is necessary because some C libraries aren't ANSI C compliant yet.
18163  */
18164 char *
18165 StrStr (char *string, char *match)
18166 {
18167     int i, length;
18168
18169     length = strlen(match);
18170
18171     for (i = strlen(string) - length; i >= 0; i--, string++)
18172       if (!strncmp(match, string, length))
18173         return string;
18174
18175     return NULL;
18176 }
18177
18178 char *
18179 StrCaseStr (char *string, char *match)
18180 {
18181     int i, j, length;
18182
18183     length = strlen(match);
18184
18185     for (i = strlen(string) - length; i >= 0; i--, string++) {
18186         for (j = 0; j < length; j++) {
18187             if (ToLower(match[j]) != ToLower(string[j]))
18188               break;
18189         }
18190         if (j == length) return string;
18191     }
18192
18193     return NULL;
18194 }
18195
18196 #ifndef _amigados
18197 int
18198 StrCaseCmp (char *s1, char *s2)
18199 {
18200     char c1, c2;
18201
18202     for (;;) {
18203         c1 = ToLower(*s1++);
18204         c2 = ToLower(*s2++);
18205         if (c1 > c2) return 1;
18206         if (c1 < c2) return -1;
18207         if (c1 == NULLCHAR) return 0;
18208     }
18209 }
18210
18211
18212 int
18213 ToLower (int c)
18214 {
18215     return isupper(c) ? tolower(c) : c;
18216 }
18217
18218
18219 int
18220 ToUpper (int c)
18221 {
18222     return islower(c) ? toupper(c) : c;
18223 }
18224 #endif /* !_amigados    */
18225
18226 char *
18227 StrSave (char *s)
18228 {
18229   char *ret;
18230
18231   if ((ret = (char *) malloc(strlen(s) + 1)))
18232     {
18233       safeStrCpy(ret, s, strlen(s)+1);
18234     }
18235   return ret;
18236 }
18237
18238 char *
18239 StrSavePtr (char *s, char **savePtr)
18240 {
18241     if (*savePtr) {
18242         free(*savePtr);
18243     }
18244     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18245       safeStrCpy(*savePtr, s, strlen(s)+1);
18246     }
18247     return(*savePtr);
18248 }
18249
18250 char *
18251 PGNDate ()
18252 {
18253     time_t clock;
18254     struct tm *tm;
18255     char buf[MSG_SIZ];
18256
18257     clock = time((time_t *)NULL);
18258     tm = localtime(&clock);
18259     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18260             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18261     return StrSave(buf);
18262 }
18263
18264
18265 char *
18266 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18267 {
18268     int i, j, fromX, fromY, toX, toY;
18269     int whiteToPlay, haveRights = nrCastlingRights;
18270     char buf[MSG_SIZ];
18271     char *p, *q;
18272     int emptycount;
18273     ChessSquare piece;
18274
18275     whiteToPlay = (gameMode == EditPosition) ?
18276       !blackPlaysFirst : (move % 2 == 0);
18277     p = buf;
18278
18279     /* Piece placement data */
18280     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18281         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18282         emptycount = 0;
18283         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18284             if (boards[move][i][j] == EmptySquare) {
18285                 emptycount++;
18286             } else { ChessSquare piece = boards[move][i][j];
18287                 if (emptycount > 0) {
18288                     if(emptycount<10) /* [HGM] can be >= 10 */
18289                         *p++ = '0' + emptycount;
18290                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18291                     emptycount = 0;
18292                 }
18293                 if(PieceToChar(piece) == '+') {
18294                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18295                     *p++ = '+';
18296                     piece = (ChessSquare)(CHUDEMOTED(piece));
18297                 }
18298                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18299                 if(*p = PieceSuffix(piece)) p++;
18300                 if(p[-1] == '~') {
18301                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18302                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18303                     *p++ = '~';
18304                 }
18305             }
18306         }
18307         if (emptycount > 0) {
18308             if(emptycount<10) /* [HGM] can be >= 10 */
18309                 *p++ = '0' + emptycount;
18310             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18311             emptycount = 0;
18312         }
18313         *p++ = '/';
18314     }
18315     *(p - 1) = ' ';
18316
18317     /* [HGM] print Crazyhouse or Shogi holdings */
18318     if( gameInfo.holdingsWidth ) {
18319         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18320         q = p;
18321         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18322             piece = boards[move][i][BOARD_WIDTH-1];
18323             if( piece != EmptySquare )
18324               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18325                   *p++ = PieceToChar(piece);
18326         }
18327         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18328             piece = boards[move][BOARD_HEIGHT-i-1][0];
18329             if( piece != EmptySquare )
18330               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18331                   *p++ = PieceToChar(piece);
18332         }
18333
18334         if( q == p ) *p++ = '-';
18335         *p++ = ']';
18336         *p++ = ' ';
18337     }
18338
18339     /* Active color */
18340     *p++ = whiteToPlay ? 'w' : 'b';
18341     *p++ = ' ';
18342
18343   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18344     haveRights = 0; q = p;
18345     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18346       piece = boards[move][0][i];
18347       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18348         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18349       }
18350     }
18351     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18352       piece = boards[move][BOARD_HEIGHT-1][i];
18353       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18354         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18355       }
18356     }
18357     if(p == q) *p++ = '-';
18358     *p++ = ' ';
18359   }
18360
18361   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18362     while(*p++ = *q++)
18363                       ;
18364     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18365   } else {
18366   if(haveRights) {
18367      int handW=0, handB=0;
18368      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18369         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18370         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18371      }
18372      q = p;
18373      if(appData.fischerCastling) {
18374         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18375            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18376                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18377         } else {
18378        /* [HGM] write directly from rights */
18379            if(boards[move][CASTLING][2] != NoRights &&
18380               boards[move][CASTLING][0] != NoRights   )
18381                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18382            if(boards[move][CASTLING][2] != NoRights &&
18383               boards[move][CASTLING][1] != NoRights   )
18384                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18385         }
18386         if(handB) {
18387            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18388                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18389         } else {
18390            if(boards[move][CASTLING][5] != NoRights &&
18391               boards[move][CASTLING][3] != NoRights   )
18392                 *p++ = boards[move][CASTLING][3] + AAA;
18393            if(boards[move][CASTLING][5] != NoRights &&
18394               boards[move][CASTLING][4] != NoRights   )
18395                 *p++ = boards[move][CASTLING][4] + AAA;
18396         }
18397      } else {
18398
18399         /* [HGM] write true castling rights */
18400         if( nrCastlingRights == 6 ) {
18401             int q, k=0;
18402             if(boards[move][CASTLING][0] != NoRights &&
18403                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18404             q = (boards[move][CASTLING][1] != NoRights &&
18405                  boards[move][CASTLING][2] != NoRights  );
18406             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18407                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18408                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18409                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18410             }
18411             if(q) *p++ = 'Q';
18412             k = 0;
18413             if(boards[move][CASTLING][3] != NoRights &&
18414                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18415             q = (boards[move][CASTLING][4] != NoRights &&
18416                  boards[move][CASTLING][5] != NoRights  );
18417             if(handB) {
18418                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18419                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18420                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18421             }
18422             if(q) *p++ = 'q';
18423         }
18424      }
18425      if (q == p) *p++ = '-'; /* No castling rights */
18426      *p++ = ' ';
18427   }
18428
18429   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18430      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18431      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18432     /* En passant target square */
18433     if (move > backwardMostMove) {
18434         fromX = moveList[move - 1][0] - AAA;
18435         fromY = moveList[move - 1][1] - ONE;
18436         toX = moveList[move - 1][2] - AAA;
18437         toY = moveList[move - 1][3] - ONE;
18438         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18439             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18440             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18441             fromX == toX) {
18442             /* 2-square pawn move just happened */
18443             *p++ = toX + AAA;
18444             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18445         } else {
18446             *p++ = '-';
18447         }
18448     } else if(move == backwardMostMove) {
18449         // [HGM] perhaps we should always do it like this, and forget the above?
18450         if((signed char)boards[move][EP_STATUS] >= 0) {
18451             *p++ = boards[move][EP_STATUS] + AAA;
18452             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18453         } else {
18454             *p++ = '-';
18455         }
18456     } else {
18457         *p++ = '-';
18458     }
18459     *p++ = ' ';
18460   }
18461   }
18462
18463     if(moveCounts)
18464     {   int i = 0, j=move;
18465
18466         /* [HGM] find reversible plies */
18467         if (appData.debugMode) { int k;
18468             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18469             for(k=backwardMostMove; k<=forwardMostMove; k++)
18470                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18471
18472         }
18473
18474         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18475         if( j == backwardMostMove ) i += initialRulePlies;
18476         sprintf(p, "%d ", i);
18477         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18478
18479         /* Fullmove number */
18480         sprintf(p, "%d", (move / 2) + 1);
18481     } else *--p = NULLCHAR;
18482
18483     return StrSave(buf);
18484 }
18485
18486 Boolean
18487 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18488 {
18489     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18490     char *p, c;
18491     int emptycount, virgin[BOARD_FILES];
18492     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18493
18494     p = fen;
18495
18496     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18497
18498     /* Piece placement data */
18499     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18500         j = 0;
18501         for (;;) {
18502             if (*p == '/' || *p == ' ' || *p == '[' ) {
18503                 if(j > w) w = j;
18504                 emptycount = gameInfo.boardWidth - j;
18505                 while (emptycount--)
18506                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18507                 if (*p == '/') p++;
18508                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18509                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18510                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18511                     }
18512                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18513                 }
18514                 break;
18515 #if(BOARD_FILES >= 10)*0
18516             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18517                 p++; emptycount=10;
18518                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18519                 while (emptycount--)
18520                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18521 #endif
18522             } else if (*p == '*') {
18523                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18524             } else if (isdigit(*p)) {
18525                 emptycount = *p++ - '0';
18526                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18527                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18528                 while (emptycount--)
18529                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18530             } else if (*p == '<') {
18531                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18532                 else if (i != 0 || !shuffle) return FALSE;
18533                 p++;
18534             } else if (shuffle && *p == '>') {
18535                 p++; // for now ignore closing shuffle range, and assume rank-end
18536             } else if (*p == '?') {
18537                 if (j >= gameInfo.boardWidth) return FALSE;
18538                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18539                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18540             } else if (*p == '+' || isalpha(*p)) {
18541                 char *q, *s = SUFFIXES;
18542                 if (j >= gameInfo.boardWidth) return FALSE;
18543                 if(*p=='+') {
18544                     char c = *++p;
18545                     if(q = strchr(s, p[1])) p++;
18546                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18547                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18548                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18549                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18550                 } else {
18551                     char c = *p++;
18552                     if(q = strchr(s, *p)) p++;
18553                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18554                 }
18555
18556                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18557                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18558                     piece = (ChessSquare) (PROMOTED(piece));
18559                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18560                     p++;
18561                 }
18562                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18563                 if(piece == king) wKingRank = i;
18564                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18565             } else {
18566                 return FALSE;
18567             }
18568         }
18569     }
18570     while (*p == '/' || *p == ' ') p++;
18571
18572     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18573
18574     /* [HGM] by default clear Crazyhouse holdings, if present */
18575     if(gameInfo.holdingsWidth) {
18576        for(i=0; i<BOARD_HEIGHT; i++) {
18577            board[i][0]             = EmptySquare; /* black holdings */
18578            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18579            board[i][1]             = (ChessSquare) 0; /* black counts */
18580            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18581        }
18582     }
18583
18584     /* [HGM] look for Crazyhouse holdings here */
18585     while(*p==' ') p++;
18586     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18587         int swap=0, wcnt=0, bcnt=0;
18588         if(*p == '[') p++;
18589         if(*p == '<') swap++, p++;
18590         if(*p == '-' ) p++; /* empty holdings */ else {
18591             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18592             /* if we would allow FEN reading to set board size, we would   */
18593             /* have to add holdings and shift the board read so far here   */
18594             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18595                 p++;
18596                 if((int) piece >= (int) BlackPawn ) {
18597                     i = (int)piece - (int)BlackPawn;
18598                     i = PieceToNumber((ChessSquare)i);
18599                     if( i >= gameInfo.holdingsSize ) return FALSE;
18600                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18601                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18602                     bcnt++;
18603                 } else {
18604                     i = (int)piece - (int)WhitePawn;
18605                     i = PieceToNumber((ChessSquare)i);
18606                     if( i >= gameInfo.holdingsSize ) return FALSE;
18607                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18608                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18609                     wcnt++;
18610                 }
18611             }
18612             if(subst) { // substitute back-rank question marks by holdings pieces
18613                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18614                     int k, m, n = bcnt + 1;
18615                     if(board[0][j] == ClearBoard) {
18616                         if(!wcnt) return FALSE;
18617                         n = rand() % wcnt;
18618                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18619                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18620                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18621                             break;
18622                         }
18623                     }
18624                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18625                         if(!bcnt) return FALSE;
18626                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18627                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18628                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18629                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18630                             break;
18631                         }
18632                     }
18633                 }
18634                 subst = 0;
18635             }
18636         }
18637         if(*p == ']') p++;
18638     }
18639
18640     if(subst) return FALSE; // substitution requested, but no holdings
18641
18642     while(*p == ' ') p++;
18643
18644     /* Active color */
18645     c = *p++;
18646     if(appData.colorNickNames) {
18647       if( c == appData.colorNickNames[0] ) c = 'w'; else
18648       if( c == appData.colorNickNames[1] ) c = 'b';
18649     }
18650     switch (c) {
18651       case 'w':
18652         *blackPlaysFirst = FALSE;
18653         break;
18654       case 'b':
18655         *blackPlaysFirst = TRUE;
18656         break;
18657       default:
18658         return FALSE;
18659     }
18660
18661     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18662     /* return the extra info in global variiables             */
18663
18664     while(*p==' ') p++;
18665
18666     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18667         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18668         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18669     }
18670
18671     /* set defaults in case FEN is incomplete */
18672     board[EP_STATUS] = EP_UNKNOWN;
18673     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18674     for(i=0; i<nrCastlingRights; i++ ) {
18675         board[CASTLING][i] =
18676             appData.fischerCastling ? NoRights : initialRights[i];
18677     }   /* assume possible unless obviously impossible */
18678     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18679     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18680     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18681                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18682     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18683     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18684     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18685                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18686     FENrulePlies = 0;
18687
18688     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18689       char *q = p;
18690       int w=0, b=0;
18691       while(isalpha(*p)) {
18692         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18693         if(islower(*p)) b |= 1 << (*p++ - 'a');
18694       }
18695       if(*p == '-') p++;
18696       if(p != q) {
18697         board[TOUCHED_W] = ~w;
18698         board[TOUCHED_B] = ~b;
18699         while(*p == ' ') p++;
18700       }
18701     } else
18702
18703     if(nrCastlingRights) {
18704       int fischer = 0;
18705       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18706       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18707           /* castling indicator present, so default becomes no castlings */
18708           for(i=0; i<nrCastlingRights; i++ ) {
18709                  board[CASTLING][i] = NoRights;
18710           }
18711       }
18712       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18713              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18714              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18715              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18716         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18717
18718         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18719             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18720             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18721         }
18722         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18723             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18724         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18725                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18726         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18727                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18728         switch(c) {
18729           case'K':
18730               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18731               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18732               board[CASTLING][2] = whiteKingFile;
18733               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18734               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18735               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18736               break;
18737           case'Q':
18738               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18739               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18740               board[CASTLING][2] = whiteKingFile;
18741               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18742               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18743               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18744               break;
18745           case'k':
18746               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18747               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18748               board[CASTLING][5] = blackKingFile;
18749               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18750               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18751               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18752               break;
18753           case'q':
18754               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18755               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18756               board[CASTLING][5] = blackKingFile;
18757               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18758               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18759               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18760           case '-':
18761               break;
18762           default: /* FRC castlings */
18763               if(c >= 'a') { /* black rights */
18764                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18765                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18766                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18767                   if(i == BOARD_RGHT) break;
18768                   board[CASTLING][5] = i;
18769                   c -= AAA;
18770                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18771                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18772                   if(c > i)
18773                       board[CASTLING][3] = c;
18774                   else
18775                       board[CASTLING][4] = c;
18776               } else { /* white rights */
18777                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18778                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18779                     if(board[0][i] == WhiteKing) break;
18780                   if(i == BOARD_RGHT) break;
18781                   board[CASTLING][2] = i;
18782                   c -= AAA - 'a' + 'A';
18783                   if(board[0][c] >= WhiteKing) break;
18784                   if(c > i)
18785                       board[CASTLING][0] = c;
18786                   else
18787                       board[CASTLING][1] = c;
18788               }
18789         }
18790       }
18791       for(i=0; i<nrCastlingRights; i++)
18792         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18793       if(gameInfo.variant == VariantSChess)
18794         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18795       if(fischer && shuffle) appData.fischerCastling = TRUE;
18796     if (appData.debugMode) {
18797         fprintf(debugFP, "FEN castling rights:");
18798         for(i=0; i<nrCastlingRights; i++)
18799         fprintf(debugFP, " %d", board[CASTLING][i]);
18800         fprintf(debugFP, "\n");
18801     }
18802
18803       while(*p==' ') p++;
18804     }
18805
18806     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18807
18808     /* read e.p. field in games that know e.p. capture */
18809     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18810        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18811        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18812       if(*p=='-') {
18813         p++; board[EP_STATUS] = EP_NONE;
18814       } else {
18815          char c = *p++ - AAA;
18816
18817          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18818          if(*p >= '0' && *p <='9') p++;
18819          board[EP_STATUS] = c;
18820       }
18821     }
18822
18823
18824     if(sscanf(p, "%d", &i) == 1) {
18825         FENrulePlies = i; /* 50-move ply counter */
18826         /* (The move number is still ignored)    */
18827     }
18828
18829     return TRUE;
18830 }
18831
18832 void
18833 EditPositionPasteFEN (char *fen)
18834 {
18835   if (fen != NULL) {
18836     Board initial_position;
18837
18838     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18839       DisplayError(_("Bad FEN position in clipboard"), 0);
18840       return ;
18841     } else {
18842       int savedBlackPlaysFirst = blackPlaysFirst;
18843       EditPositionEvent();
18844       blackPlaysFirst = savedBlackPlaysFirst;
18845       CopyBoard(boards[0], initial_position);
18846       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18847       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18848       DisplayBothClocks();
18849       DrawPosition(FALSE, boards[currentMove]);
18850     }
18851   }
18852 }
18853
18854 static char cseq[12] = "\\   ";
18855
18856 Boolean
18857 set_cont_sequence (char *new_seq)
18858 {
18859     int len;
18860     Boolean ret;
18861
18862     // handle bad attempts to set the sequence
18863         if (!new_seq)
18864                 return 0; // acceptable error - no debug
18865
18866     len = strlen(new_seq);
18867     ret = (len > 0) && (len < sizeof(cseq));
18868     if (ret)
18869       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18870     else if (appData.debugMode)
18871       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18872     return ret;
18873 }
18874
18875 /*
18876     reformat a source message so words don't cross the width boundary.  internal
18877     newlines are not removed.  returns the wrapped size (no null character unless
18878     included in source message).  If dest is NULL, only calculate the size required
18879     for the dest buffer.  lp argument indicats line position upon entry, and it's
18880     passed back upon exit.
18881 */
18882 int
18883 wrap (char *dest, char *src, int count, int width, int *lp)
18884 {
18885     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18886
18887     cseq_len = strlen(cseq);
18888     old_line = line = *lp;
18889     ansi = len = clen = 0;
18890
18891     for (i=0; i < count; i++)
18892     {
18893         if (src[i] == '\033')
18894             ansi = 1;
18895
18896         // if we hit the width, back up
18897         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18898         {
18899             // store i & len in case the word is too long
18900             old_i = i, old_len = len;
18901
18902             // find the end of the last word
18903             while (i && src[i] != ' ' && src[i] != '\n')
18904             {
18905                 i--;
18906                 len--;
18907             }
18908
18909             // word too long?  restore i & len before splitting it
18910             if ((old_i-i+clen) >= width)
18911             {
18912                 i = old_i;
18913                 len = old_len;
18914             }
18915
18916             // extra space?
18917             if (i && src[i-1] == ' ')
18918                 len--;
18919
18920             if (src[i] != ' ' && src[i] != '\n')
18921             {
18922                 i--;
18923                 if (len)
18924                     len--;
18925             }
18926
18927             // now append the newline and continuation sequence
18928             if (dest)
18929                 dest[len] = '\n';
18930             len++;
18931             if (dest)
18932                 strncpy(dest+len, cseq, cseq_len);
18933             len += cseq_len;
18934             line = cseq_len;
18935             clen = cseq_len;
18936             continue;
18937         }
18938
18939         if (dest)
18940             dest[len] = src[i];
18941         len++;
18942         if (!ansi)
18943             line++;
18944         if (src[i] == '\n')
18945             line = 0;
18946         if (src[i] == 'm')
18947             ansi = 0;
18948     }
18949     if (dest && appData.debugMode)
18950     {
18951         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18952             count, width, line, len, *lp);
18953         show_bytes(debugFP, src, count);
18954         fprintf(debugFP, "\ndest: ");
18955         show_bytes(debugFP, dest, len);
18956         fprintf(debugFP, "\n");
18957     }
18958     *lp = dest ? line : old_line;
18959
18960     return len;
18961 }
18962
18963 // [HGM] vari: routines for shelving variations
18964 Boolean modeRestore = FALSE;
18965
18966 void
18967 PushInner (int firstMove, int lastMove)
18968 {
18969         int i, j, nrMoves = lastMove - firstMove;
18970
18971         // push current tail of game on stack
18972         savedResult[storedGames] = gameInfo.result;
18973         savedDetails[storedGames] = gameInfo.resultDetails;
18974         gameInfo.resultDetails = NULL;
18975         savedFirst[storedGames] = firstMove;
18976         savedLast [storedGames] = lastMove;
18977         savedFramePtr[storedGames] = framePtr;
18978         framePtr -= nrMoves; // reserve space for the boards
18979         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18980             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18981             for(j=0; j<MOVE_LEN; j++)
18982                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18983             for(j=0; j<2*MOVE_LEN; j++)
18984                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18985             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18986             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18987             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18988             pvInfoList[firstMove+i-1].depth = 0;
18989             commentList[framePtr+i] = commentList[firstMove+i];
18990             commentList[firstMove+i] = NULL;
18991         }
18992
18993         storedGames++;
18994         forwardMostMove = firstMove; // truncate game so we can start variation
18995 }
18996
18997 void
18998 PushTail (int firstMove, int lastMove)
18999 {
19000         if(appData.icsActive) { // only in local mode
19001                 forwardMostMove = currentMove; // mimic old ICS behavior
19002                 return;
19003         }
19004         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19005
19006         PushInner(firstMove, lastMove);
19007         if(storedGames == 1) GreyRevert(FALSE);
19008         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19009 }
19010
19011 void
19012 PopInner (Boolean annotate)
19013 {
19014         int i, j, nrMoves;
19015         char buf[8000], moveBuf[20];
19016
19017         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19018         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19019         nrMoves = savedLast[storedGames] - currentMove;
19020         if(annotate) {
19021                 int cnt = 10;
19022                 if(!WhiteOnMove(currentMove))
19023                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19024                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19025                 for(i=currentMove; i<forwardMostMove; i++) {
19026                         if(WhiteOnMove(i))
19027                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19028                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19029                         strcat(buf, moveBuf);
19030                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19031                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19032                 }
19033                 strcat(buf, ")");
19034         }
19035         for(i=1; i<=nrMoves; i++) { // copy last variation back
19036             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19037             for(j=0; j<MOVE_LEN; j++)
19038                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19039             for(j=0; j<2*MOVE_LEN; j++)
19040                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19041             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19042             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19043             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19044             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19045             commentList[currentMove+i] = commentList[framePtr+i];
19046             commentList[framePtr+i] = NULL;
19047         }
19048         if(annotate) AppendComment(currentMove+1, buf, FALSE);
19049         framePtr = savedFramePtr[storedGames];
19050         gameInfo.result = savedResult[storedGames];
19051         if(gameInfo.resultDetails != NULL) {
19052             free(gameInfo.resultDetails);
19053       }
19054         gameInfo.resultDetails = savedDetails[storedGames];
19055         forwardMostMove = currentMove + nrMoves;
19056 }
19057
19058 Boolean
19059 PopTail (Boolean annotate)
19060 {
19061         if(appData.icsActive) return FALSE; // only in local mode
19062         if(!storedGames) return FALSE; // sanity
19063         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19064
19065         PopInner(annotate);
19066         if(currentMove < forwardMostMove) ForwardEvent(); else
19067         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19068
19069         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19070         return TRUE;
19071 }
19072
19073 void
19074 CleanupTail ()
19075 {       // remove all shelved variations
19076         int i;
19077         for(i=0; i<storedGames; i++) {
19078             if(savedDetails[i])
19079                 free(savedDetails[i]);
19080             savedDetails[i] = NULL;
19081         }
19082         for(i=framePtr; i<MAX_MOVES; i++) {
19083                 if(commentList[i]) free(commentList[i]);
19084                 commentList[i] = NULL;
19085         }
19086         framePtr = MAX_MOVES-1;
19087         storedGames = 0;
19088 }
19089
19090 void
19091 LoadVariation (int index, char *text)
19092 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19093         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19094         int level = 0, move;
19095
19096         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19097         // first find outermost bracketing variation
19098         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19099             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19100                 if(*p == '{') wait = '}'; else
19101                 if(*p == '[') wait = ']'; else
19102                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19103                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19104             }
19105             if(*p == wait) wait = NULLCHAR; // closing ]} found
19106             p++;
19107         }
19108         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19109         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19110         end[1] = NULLCHAR; // clip off comment beyond variation
19111         ToNrEvent(currentMove-1);
19112         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19113         // kludge: use ParsePV() to append variation to game
19114         move = currentMove;
19115         ParsePV(start, TRUE, TRUE);
19116         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19117         ClearPremoveHighlights();
19118         CommentPopDown();
19119         ToNrEvent(currentMove+1);
19120 }
19121
19122 int transparency[2];
19123
19124 void
19125 LoadTheme ()
19126 {
19127 #define BUF_SIZ (2*MSG_SIZ)
19128     char *p, *q, buf[BUF_SIZ];
19129     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19130         snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19131         ParseArgsFromString(buf);
19132         ActivateTheme(TRUE); // also redo colors
19133         return;
19134     }
19135     p = nickName;
19136     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19137     {
19138         int len;
19139         q = appData.themeNames;
19140         snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19141       if(appData.useBitmaps) {
19142         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19143                 Shorten(appData.liteBackTextureFile));
19144         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19145                 Shorten(appData.darkBackTextureFile),
19146                 appData.liteBackTextureMode,
19147                 appData.darkBackTextureMode );
19148       } else {
19149         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19150       }
19151       if(!appData.useBitmaps || transparency[0]) {
19152         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19153       }
19154       if(!appData.useBitmaps || transparency[1]) {
19155         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19156       }
19157       if(appData.useBorder) {
19158         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19159                 appData.border);
19160       } else {
19161         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19162       }
19163       if(appData.useFont) {
19164         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19165                 appData.renderPiecesWithFont,
19166                 appData.fontToPieceTable,
19167                 Col2Text(9),    // appData.fontBackColorWhite
19168                 Col2Text(10) ); // appData.fontForeColorBlack
19169       } else {
19170         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19171         if(appData.pieceDirectory[0]) {
19172           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19173           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19174             snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19175         }
19176         if(!appData.pieceDirectory[0] || !appData.trueColors)
19177           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19178                 Col2Text(0),   // whitePieceColor
19179                 Col2Text(1) ); // blackPieceColor
19180       }
19181       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19182                 Col2Text(4),   // highlightSquareColor
19183                 Col2Text(5) ); // premoveHighlightColor
19184         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19185         if(insert != q) insert[-1] = NULLCHAR;
19186         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19187         if(q)   free(q);
19188     }
19189     ActivateTheme(FALSE);
19190 }