Decouple board height from hand size
[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, handSize;
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 = handSize-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_RANKS-1; 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                         new_piece[0] = NULLCHAR;
4213                         sscanf(parse, "game %d white [%s black [%s <- %s",
4214                                &gamenum, white_holding, black_holding,
4215                                new_piece);
4216                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4217                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4218                         if (gameInfo.variant == VariantNormal) {
4219                           /* [HGM] We seem to switch variant during a game!
4220                            * Presumably no holdings were displayed, so we have
4221                            * to move the position two files to the right to
4222                            * create room for them!
4223                            */
4224                           VariantClass newVariant;
4225                           switch(gameInfo.boardWidth) { // base guess on board width
4226                                 case 9:  newVariant = VariantShogi; break;
4227                                 case 10: newVariant = VariantGreat; break;
4228                                 default: newVariant = VariantCrazyhouse;
4229                                      if(strchr(white_holding, 'E') || strchr(black_holding, 'E') || 
4230                                         strchr(white_holding, 'H') || strchr(black_holding, 'H')   )
4231                                          newVariant = VariantSChess;
4232                           }
4233                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4234                           /* Get a move list just to see the header, which
4235                              will tell us whether this is really bug or zh */
4236                           if (ics_getting_history == H_FALSE) {
4237                             ics_getting_history = H_REQUESTED;
4238                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4239                             SendToICS(str);
4240                           }
4241                         }
4242                         /* [HGM] copy holdings to board holdings area */
4243                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4244                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4245                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4246 #if ZIPPY
4247                         if (appData.zippyPlay && first.initDone) {
4248                             ZippyHoldings(white_holding, black_holding,
4249                                           new_piece);
4250                         }
4251 #endif /*ZIPPY*/
4252                         if (tinyLayout || smallLayout) {
4253                             char wh[16], bh[16];
4254                             PackHolding(wh, white_holding);
4255                             PackHolding(bh, black_holding);
4256                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4257                                     gameInfo.white, gameInfo.black);
4258                         } else {
4259                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4260                                     gameInfo.white, white_holding, _("vs."),
4261                                     gameInfo.black, black_holding);
4262                         }
4263                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4264                         DrawPosition(FALSE, boards[currentMove]);
4265                         DisplayTitle(str);
4266                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4267                         sscanf(parse, "game %d white [%s black [%s <- %s",
4268                                &gamenum, white_holding, black_holding,
4269                                new_piece);
4270                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4271                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4272                         /* [HGM] copy holdings to partner-board holdings area */
4273                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4274                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4275                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4276                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4277                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4278                       }
4279                     }
4280                     /* Suppress following prompt */
4281                     if (looking_at(buf, &i, "*% ")) {
4282                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4283                         savingComment = FALSE;
4284                         suppressKibitz = 0;
4285                     }
4286                     next_out = i;
4287                 }
4288                 continue;
4289             }
4290
4291             i++;                /* skip unparsed character and loop back */
4292         }
4293
4294         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4295 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4296 //          SendToPlayer(&buf[next_out], i - next_out);
4297             started != STARTED_HOLDINGS && leftover_start > next_out) {
4298             SendToPlayer(&buf[next_out], leftover_start - next_out);
4299             next_out = i;
4300         }
4301
4302         leftover_len = buf_len - leftover_start;
4303         /* if buffer ends with something we couldn't parse,
4304            reparse it after appending the next read */
4305
4306     } else if (count == 0) {
4307         RemoveInputSource(isr);
4308         DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
4309     } else {
4310         DisplayFatalError(_("Error reading from ICS"), error, 1);
4311     }
4312 }
4313
4314
4315 /* Board style 12 looks like this:
4316
4317    <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
4318
4319  * The "<12> " is stripped before it gets to this routine.  The two
4320  * trailing 0's (flip state and clock ticking) are later addition, and
4321  * some chess servers may not have them, or may have only the first.
4322  * Additional trailing fields may be added in the future.
4323  */
4324
4325 #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"
4326
4327 #define RELATION_OBSERVING_PLAYED    0
4328 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4329 #define RELATION_PLAYING_MYMOVE      1
4330 #define RELATION_PLAYING_NOTMYMOVE  -1
4331 #define RELATION_EXAMINING           2
4332 #define RELATION_ISOLATED_BOARD     -3
4333 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4334
4335 void
4336 ParseBoard12 (char *string)
4337 {
4338 #if ZIPPY
4339     int i, takeback;
4340     char *bookHit = NULL; // [HGM] book
4341 #endif
4342     GameMode newGameMode;
4343     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4344     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4345     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4346     char to_play, board_chars[200];
4347     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4348     char black[32], white[32];
4349     Board board;
4350     int prevMove = currentMove;
4351     int ticking = 2;
4352     ChessMove moveType;
4353     int fromX, fromY, toX, toY;
4354     char promoChar;
4355     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4356     Boolean weird = FALSE, reqFlag = FALSE;
4357
4358     fromX = fromY = toX = toY = -1;
4359
4360     newGame = FALSE;
4361
4362     if (appData.debugMode)
4363       fprintf(debugFP, "Parsing board: %s\n", string);
4364
4365     move_str[0] = NULLCHAR;
4366     elapsed_time[0] = NULLCHAR;
4367     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4368         int  i = 0, j;
4369         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4370             if(string[i] == ' ') { ranks++; files = 0; }
4371             else files++;
4372             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4373             i++;
4374         }
4375         for(j = 0; j <i; j++) board_chars[j] = string[j];
4376         board_chars[i] = '\0';
4377         string += i + 1;
4378     }
4379     n = sscanf(string, PATTERN, &to_play, &double_push,
4380                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4381                &gamenum, white, black, &relation, &basetime, &increment,
4382                &white_stren, &black_stren, &white_time, &black_time,
4383                &moveNum, str, elapsed_time, move_str, &ics_flip,
4384                &ticking);
4385
4386     if (n < 21) {
4387         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4388         DisplayError(str, 0);
4389         return;
4390     }
4391
4392     /* Convert the move number to internal form */
4393     moveNum = (moveNum - 1) * 2;
4394     if (to_play == 'B') moveNum++;
4395     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4396       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4397                         0, 1);
4398       return;
4399     }
4400
4401     switch (relation) {
4402       case RELATION_OBSERVING_PLAYED:
4403       case RELATION_OBSERVING_STATIC:
4404         if (gamenum == -1) {
4405             /* Old ICC buglet */
4406             relation = RELATION_OBSERVING_STATIC;
4407         }
4408         newGameMode = IcsObserving;
4409         break;
4410       case RELATION_PLAYING_MYMOVE:
4411       case RELATION_PLAYING_NOTMYMOVE:
4412         newGameMode =
4413           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4414             IcsPlayingWhite : IcsPlayingBlack;
4415         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4416         break;
4417       case RELATION_EXAMINING:
4418         newGameMode = IcsExamining;
4419         break;
4420       case RELATION_ISOLATED_BOARD:
4421       default:
4422         /* Just display this board.  If user was doing something else,
4423            we will forget about it until the next board comes. */
4424         newGameMode = IcsIdle;
4425         break;
4426       case RELATION_STARTING_POSITION:
4427         newGameMode = gameMode;
4428         break;
4429     }
4430
4431     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4432         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4433          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4434       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4435       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4436       static int lastBgGame = -1;
4437       char *toSqr;
4438       for (k = 0; k < ranks; k++) {
4439         for (j = 0; j < files; j++)
4440           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4441         if(gameInfo.holdingsWidth > 1) {
4442              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4443              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4444         }
4445       }
4446       CopyBoard(partnerBoard, board);
4447       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4448         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4449         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4450       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4451       if(toSqr = strchr(str, '-')) {
4452         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4453         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4454       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4455       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4456       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4457       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4458       if(twoBoards) {
4459           DisplayWhiteClock(white_time*fac, to_play == 'W');
4460           DisplayBlackClock(black_time*fac, to_play != 'W');
4461           activePartner = to_play;
4462           if(gamenum != lastBgGame) {
4463               char buf[MSG_SIZ];
4464               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4465               DisplayTitle(buf);
4466           }
4467           lastBgGame = gamenum;
4468           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4469                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4470       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4471                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4472       if(!twoBoards) DisplayMessage(partnerStatus, "");
4473         partnerBoardValid = TRUE;
4474       return;
4475     }
4476
4477     if(appData.dualBoard && appData.bgObserve) {
4478         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4479             SendToICS(ics_prefix), SendToICS("pobserve\n");
4480         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4481             char buf[MSG_SIZ];
4482             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4483             SendToICS(buf);
4484         }
4485     }
4486
4487     /* Modify behavior for initial board display on move listing
4488        of wild games.
4489        */
4490     switch (ics_getting_history) {
4491       case H_FALSE:
4492       case H_REQUESTED:
4493         break;
4494       case H_GOT_REQ_HEADER:
4495       case H_GOT_UNREQ_HEADER:
4496         /* This is the initial position of the current game */
4497         gamenum = ics_gamenum;
4498         moveNum = 0;            /* old ICS bug workaround */
4499         if (to_play == 'B') {
4500           startedFromSetupPosition = TRUE;
4501           blackPlaysFirst = TRUE;
4502           moveNum = 1;
4503           if (forwardMostMove == 0) forwardMostMove = 1;
4504           if (backwardMostMove == 0) backwardMostMove = 1;
4505           if (currentMove == 0) currentMove = 1;
4506         }
4507         newGameMode = gameMode;
4508         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4509         break;
4510       case H_GOT_UNWANTED_HEADER:
4511         /* This is an initial board that we don't want */
4512         return;
4513       case H_GETTING_MOVES:
4514         /* Should not happen */
4515         DisplayError(_("Error gathering move list: extra board"), 0);
4516         ics_getting_history = H_FALSE;
4517         return;
4518     }
4519
4520    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4521                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4522                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4523      /* [HGM] We seem to have switched variant unexpectedly
4524       * Try to guess new variant from board size
4525       */
4526           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4527           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4528           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4529           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4530           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4531           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4532           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4533           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4534           /* Get a move list just to see the header, which
4535              will tell us whether this is really bug or zh */
4536           if (ics_getting_history == H_FALSE) {
4537             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4538             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4539             SendToICS(str);
4540           }
4541     }
4542
4543     /* Take action if this is the first board of a new game, or of a
4544        different game than is currently being displayed.  */
4545     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4546         relation == RELATION_ISOLATED_BOARD) {
4547
4548         /* Forget the old game and get the history (if any) of the new one */
4549         if (gameMode != BeginningOfGame) {
4550           Reset(TRUE, TRUE);
4551         }
4552         newGame = TRUE;
4553         if (appData.autoRaiseBoard) BoardToTop();
4554         prevMove = -3;
4555         if (gamenum == -1) {
4556             newGameMode = IcsIdle;
4557         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4558                    appData.getMoveList && !reqFlag) {
4559             /* Need to get game history */
4560             ics_getting_history = H_REQUESTED;
4561             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4562             SendToICS(str);
4563         }
4564
4565         /* Initially flip the board to have black on the bottom if playing
4566            black or if the ICS flip flag is set, but let the user change
4567            it with the Flip View button. */
4568         flipView = appData.autoFlipView ?
4569           (newGameMode == IcsPlayingBlack) || ics_flip :
4570           appData.flipView;
4571
4572         /* Done with values from previous mode; copy in new ones */
4573         gameMode = newGameMode;
4574         ModeHighlight();
4575         ics_gamenum = gamenum;
4576         if (gamenum == gs_gamenum) {
4577             int klen = strlen(gs_kind);
4578             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4579             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4580             gameInfo.event = StrSave(str);
4581         } else {
4582             gameInfo.event = StrSave("ICS game");
4583         }
4584         gameInfo.site = StrSave(appData.icsHost);
4585         gameInfo.date = PGNDate();
4586         gameInfo.round = StrSave("-");
4587         gameInfo.white = StrSave(white);
4588         gameInfo.black = StrSave(black);
4589         timeControl = basetime * 60 * 1000;
4590         timeControl_2 = 0;
4591         timeIncrement = increment * 1000;
4592         movesPerSession = 0;
4593         gameInfo.timeControl = TimeControlTagValue();
4594         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4595   if (appData.debugMode) {
4596     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4597     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4598     setbuf(debugFP, NULL);
4599   }
4600
4601         gameInfo.outOfBook = NULL;
4602
4603         /* Do we have the ratings? */
4604         if (strcmp(player1Name, white) == 0 &&
4605             strcmp(player2Name, black) == 0) {
4606             if (appData.debugMode)
4607               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4608                       player1Rating, player2Rating);
4609             gameInfo.whiteRating = player1Rating;
4610             gameInfo.blackRating = player2Rating;
4611         } else if (strcmp(player2Name, white) == 0 &&
4612                    strcmp(player1Name, black) == 0) {
4613             if (appData.debugMode)
4614               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4615                       player2Rating, player1Rating);
4616             gameInfo.whiteRating = player2Rating;
4617             gameInfo.blackRating = player1Rating;
4618         }
4619         player1Name[0] = player2Name[0] = NULLCHAR;
4620
4621         /* Silence shouts if requested */
4622         if (appData.quietPlay &&
4623             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4624             SendToICS(ics_prefix);
4625             SendToICS("set shout 0\n");
4626         }
4627     }
4628
4629     /* Deal with midgame name changes */
4630     if (!newGame) {
4631         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4632             if (gameInfo.white) free(gameInfo.white);
4633             gameInfo.white = StrSave(white);
4634         }
4635         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4636             if (gameInfo.black) free(gameInfo.black);
4637             gameInfo.black = StrSave(black);
4638         }
4639     }
4640
4641     /* Throw away game result if anything actually changes in examine mode */
4642     if (gameMode == IcsExamining && !newGame) {
4643         gameInfo.result = GameUnfinished;
4644         if (gameInfo.resultDetails != NULL) {
4645             free(gameInfo.resultDetails);
4646             gameInfo.resultDetails = NULL;
4647         }
4648     }
4649
4650     /* In pausing && IcsExamining mode, we ignore boards coming
4651        in if they are in a different variation than we are. */
4652     if (pauseExamInvalid) return;
4653     if (pausing && gameMode == IcsExamining) {
4654         if (moveNum <= pauseExamForwardMostMove) {
4655             pauseExamInvalid = TRUE;
4656             forwardMostMove = pauseExamForwardMostMove;
4657             return;
4658         }
4659     }
4660
4661   if (appData.debugMode) {
4662     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4663   }
4664     /* Parse the board */
4665     for (k = 0; k < ranks; k++) {
4666       for (j = 0; j < files; j++)
4667         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4668       if(gameInfo.holdingsWidth > 1) {
4669            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4670            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4671       }
4672     }
4673     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4674       board[5][BOARD_RGHT+1] = WhiteAngel;
4675       board[6][BOARD_RGHT+1] = WhiteMarshall;
4676       board[1][0] = BlackMarshall;
4677       board[2][0] = BlackAngel;
4678       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4679     }
4680     CopyBoard(boards[moveNum], board);
4681     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4682     if (moveNum == 0) {
4683         startedFromSetupPosition =
4684           !CompareBoards(board, initialPosition);
4685         if(startedFromSetupPosition)
4686             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4687     }
4688
4689     /* [HGM] Set castling rights. Take the outermost Rooks,
4690        to make it also work for FRC opening positions. Note that board12
4691        is really defective for later FRC positions, as it has no way to
4692        indicate which Rook can castle if they are on the same side of King.
4693        For the initial position we grant rights to the outermost Rooks,
4694        and remember thos rights, and we then copy them on positions
4695        later in an FRC game. This means WB might not recognize castlings with
4696        Rooks that have moved back to their original position as illegal,
4697        but in ICS mode that is not its job anyway.
4698     */
4699     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4700     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4701
4702         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4703             if(board[0][i] == WhiteRook) j = i;
4704         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4705         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4706             if(board[0][i] == WhiteRook) j = i;
4707         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4708         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4709             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4710         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4711         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4712             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4713         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4714
4715         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4716         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4717         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4718             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4719         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4720             if(board[BOARD_HEIGHT-1][k] == bKing)
4721                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4722         if(gameInfo.variant == VariantTwoKings) {
4723             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4724             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4725             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4726         }
4727     } else { int r;
4728         r = boards[moveNum][CASTLING][0] = initialRights[0];
4729         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4730         r = boards[moveNum][CASTLING][1] = initialRights[1];
4731         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4732         r = boards[moveNum][CASTLING][3] = initialRights[3];
4733         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4734         r = boards[moveNum][CASTLING][4] = initialRights[4];
4735         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4736         /* wildcastle kludge: always assume King has rights */
4737         r = boards[moveNum][CASTLING][2] = initialRights[2];
4738         r = boards[moveNum][CASTLING][5] = initialRights[5];
4739     }
4740     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4741     boards[moveNum][EP_STATUS] = EP_NONE;
4742     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4743     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4744     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4745
4746
4747     if (ics_getting_history == H_GOT_REQ_HEADER ||
4748         ics_getting_history == H_GOT_UNREQ_HEADER) {
4749         /* This was an initial position from a move list, not
4750            the current position */
4751         return;
4752     }
4753
4754     /* Update currentMove and known move number limits */
4755     newMove = newGame || moveNum > forwardMostMove;
4756
4757     if (newGame) {
4758         forwardMostMove = backwardMostMove = currentMove = moveNum;
4759         if (gameMode == IcsExamining && moveNum == 0) {
4760           /* Workaround for ICS limitation: we are not told the wild
4761              type when starting to examine a game.  But if we ask for
4762              the move list, the move list header will tell us */
4763             ics_getting_history = H_REQUESTED;
4764             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4765             SendToICS(str);
4766         }
4767     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4768                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4769 #if ZIPPY
4770         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4771         /* [HGM] applied this also to an engine that is silently watching        */
4772         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4773             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4774             gameInfo.variant == currentlyInitializedVariant) {
4775           takeback = forwardMostMove - moveNum;
4776           for (i = 0; i < takeback; i++) {
4777             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4778             SendToProgram("undo\n", &first);
4779           }
4780         }
4781 #endif
4782
4783         forwardMostMove = moveNum;
4784         if (!pausing || currentMove > forwardMostMove)
4785           currentMove = forwardMostMove;
4786     } else {
4787         /* New part of history that is not contiguous with old part */
4788         if (pausing && gameMode == IcsExamining) {
4789             pauseExamInvalid = TRUE;
4790             forwardMostMove = pauseExamForwardMostMove;
4791             return;
4792         }
4793         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4794 #if ZIPPY
4795             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4796                 // [HGM] when we will receive the move list we now request, it will be
4797                 // fed to the engine from the first move on. So if the engine is not
4798                 // in the initial position now, bring it there.
4799                 InitChessProgram(&first, 0);
4800             }
4801 #endif
4802             ics_getting_history = H_REQUESTED;
4803             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4804             SendToICS(str);
4805         }
4806         forwardMostMove = backwardMostMove = currentMove = moveNum;
4807     }
4808
4809     /* Update the clocks */
4810     if (strchr(elapsed_time, '.')) {
4811       /* Time is in ms */
4812       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4813       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4814     } else {
4815       /* Time is in seconds */
4816       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4817       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4818     }
4819
4820
4821 #if ZIPPY
4822     if (appData.zippyPlay && newGame &&
4823         gameMode != IcsObserving && gameMode != IcsIdle &&
4824         gameMode != IcsExamining)
4825       ZippyFirstBoard(moveNum, basetime, increment);
4826 #endif
4827
4828     /* Put the move on the move list, first converting
4829        to canonical algebraic form. */
4830     if (moveNum > 0) {
4831   if (appData.debugMode) {
4832     int f = forwardMostMove;
4833     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4834             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4835             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4836     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4837     fprintf(debugFP, "moveNum = %d\n", moveNum);
4838     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4839     setbuf(debugFP, NULL);
4840   }
4841         if (moveNum <= backwardMostMove) {
4842             /* We don't know what the board looked like before
4843                this move.  Punt. */
4844           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4845             strcat(parseList[moveNum - 1], " ");
4846             strcat(parseList[moveNum - 1], elapsed_time);
4847             moveList[moveNum - 1][0] = NULLCHAR;
4848         } else if (strcmp(move_str, "none") == 0) {
4849             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4850             /* Again, we don't know what the board looked like;
4851                this is really the start of the game. */
4852             parseList[moveNum - 1][0] = NULLCHAR;
4853             moveList[moveNum - 1][0] = NULLCHAR;
4854             backwardMostMove = moveNum;
4855             startedFromSetupPosition = TRUE;
4856             fromX = fromY = toX = toY = -1;
4857         } else {
4858           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4859           //                 So we parse the long-algebraic move string in stead of the SAN move
4860           int valid; char buf[MSG_SIZ], *prom;
4861
4862           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4863                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4864           // str looks something like "Q/a1-a2"; kill the slash
4865           if(str[1] == '/')
4866             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4867           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4868           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4869                 strcat(buf, prom); // long move lacks promo specification!
4870           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4871                 if(appData.debugMode)
4872                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4873                 safeStrCpy(move_str, buf, MSG_SIZ);
4874           }
4875           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4876                                 &fromX, &fromY, &toX, &toY, &promoChar)
4877                || ParseOneMove(buf, moveNum - 1, &moveType,
4878                                 &fromX, &fromY, &toX, &toY, &promoChar);
4879           // end of long SAN patch
4880           if (valid) {
4881             (void) CoordsToAlgebraic(boards[moveNum - 1],
4882                                      PosFlags(moveNum - 1),
4883                                      fromY, fromX, toY, toX, promoChar,
4884                                      parseList[moveNum-1]);
4885             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4886               case MT_NONE:
4887               case MT_STALEMATE:
4888               default:
4889                 break;
4890               case MT_CHECK:
4891                 if(!IS_SHOGI(gameInfo.variant))
4892                     strcat(parseList[moveNum - 1], "+");
4893                 break;
4894               case MT_CHECKMATE:
4895               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4896                 strcat(parseList[moveNum - 1], "#");
4897                 break;
4898             }
4899             strcat(parseList[moveNum - 1], " ");
4900             strcat(parseList[moveNum - 1], elapsed_time);
4901             /* currentMoveString is set as a side-effect of ParseOneMove */
4902             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4903             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4904             strcat(moveList[moveNum - 1], "\n");
4905
4906             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4907                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4908               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4909                 ChessSquare old, new = boards[moveNum][k][j];
4910                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4911                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4912                   if(old == new) continue;
4913                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4914                   else if(new == WhiteWazir || new == BlackWazir) {
4915                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4916                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4917                       else boards[moveNum][k][j] = old; // preserve type of Gold
4918                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4919                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4920               }
4921           } else {
4922             /* Move from ICS was illegal!?  Punt. */
4923             if (appData.debugMode) {
4924               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4925               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4926             }
4927             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4928             strcat(parseList[moveNum - 1], " ");
4929             strcat(parseList[moveNum - 1], elapsed_time);
4930             moveList[moveNum - 1][0] = NULLCHAR;
4931             fromX = fromY = toX = toY = -1;
4932           }
4933         }
4934   if (appData.debugMode) {
4935     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4936     setbuf(debugFP, NULL);
4937   }
4938
4939 #if ZIPPY
4940         /* Send move to chess program (BEFORE animating it). */
4941         if (appData.zippyPlay && !newGame && newMove &&
4942            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4943
4944             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4945                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4946                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4947                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4948                             move_str);
4949                     DisplayError(str, 0);
4950                 } else {
4951                     if (first.sendTime) {
4952                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4953                     }
4954                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4955                     if (firstMove && !bookHit) {
4956                         firstMove = FALSE;
4957                         if (first.useColors) {
4958                           SendToProgram(gameMode == IcsPlayingWhite ?
4959                                         "white\ngo\n" :
4960                                         "black\ngo\n", &first);
4961                         } else {
4962                           SendToProgram("go\n", &first);
4963                         }
4964                         first.maybeThinking = TRUE;
4965                     }
4966                 }
4967             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4968               if (moveList[moveNum - 1][0] == NULLCHAR) {
4969                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4970                 DisplayError(str, 0);
4971               } else {
4972                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4973                 SendMoveToProgram(moveNum - 1, &first);
4974               }
4975             }
4976         }
4977 #endif
4978     }
4979
4980     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4981         /* If move comes from a remote source, animate it.  If it
4982            isn't remote, it will have already been animated. */
4983         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4984             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4985         }
4986         if (!pausing && appData.highlightLastMove) {
4987             SetHighlights(fromX, fromY, toX, toY);
4988         }
4989     }
4990
4991     /* Start the clocks */
4992     whiteFlag = blackFlag = FALSE;
4993     appData.clockMode = !(basetime == 0 && increment == 0);
4994     if (ticking == 0) {
4995       ics_clock_paused = TRUE;
4996       StopClocks();
4997     } else if (ticking == 1) {
4998       ics_clock_paused = FALSE;
4999     }
5000     if (gameMode == IcsIdle ||
5001         relation == RELATION_OBSERVING_STATIC ||
5002         relation == RELATION_EXAMINING ||
5003         ics_clock_paused)
5004       DisplayBothClocks();
5005     else
5006       StartClocks();
5007
5008     /* Display opponents and material strengths */
5009     if (gameInfo.variant != VariantBughouse &&
5010         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5011         if (tinyLayout || smallLayout) {
5012             if(gameInfo.variant == VariantNormal)
5013               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5014                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5015                     basetime, increment);
5016             else
5017               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5018                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5019                     basetime, increment, (int) gameInfo.variant);
5020         } else {
5021             if(gameInfo.variant == VariantNormal)
5022               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5023                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5024                     basetime, increment);
5025             else
5026               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5027                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5028                     basetime, increment, VariantName(gameInfo.variant));
5029         }
5030         DisplayTitle(str);
5031   if (appData.debugMode) {
5032     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5033   }
5034     }
5035
5036
5037     /* Display the board */
5038     if (!pausing && !appData.noGUI) {
5039
5040       if (appData.premove)
5041           if (!gotPremove ||
5042              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5043              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5044               ClearPremoveHighlights();
5045
5046       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5047         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5048       DrawPosition(j, boards[currentMove]);
5049
5050       DisplayMove(moveNum - 1);
5051       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5052             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5053               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5054         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5055       }
5056     }
5057
5058     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5059 #if ZIPPY
5060     if(bookHit) { // [HGM] book: simulate book reply
5061         static char bookMove[MSG_SIZ]; // a bit generous?
5062
5063         programStats.nodes = programStats.depth = programStats.time =
5064         programStats.score = programStats.got_only_move = 0;
5065         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5066
5067         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5068         strcat(bookMove, bookHit);
5069         HandleMachineMove(bookMove, &first);
5070     }
5071 #endif
5072 }
5073
5074 void
5075 GetMoveListEvent ()
5076 {
5077     char buf[MSG_SIZ];
5078     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5079         ics_getting_history = H_REQUESTED;
5080         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5081         SendToICS(buf);
5082     }
5083 }
5084
5085 void
5086 SendToBoth (char *msg)
5087 {   // to make it easy to keep two engines in step in dual analysis
5088     SendToProgram(msg, &first);
5089     if(second.analyzing) SendToProgram(msg, &second);
5090 }
5091
5092 void
5093 AnalysisPeriodicEvent (int force)
5094 {
5095     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5096          && !force) || !appData.periodicUpdates)
5097       return;
5098
5099     /* Send . command to Crafty to collect stats */
5100     SendToBoth(".\n");
5101
5102     /* Don't send another until we get a response (this makes
5103        us stop sending to old Crafty's which don't understand
5104        the "." command (sending illegal cmds resets node count & time,
5105        which looks bad)) */
5106     programStats.ok_to_send = 0;
5107 }
5108
5109 void
5110 ics_update_width (int new_width)
5111 {
5112         ics_printf("set width %d\n", new_width);
5113 }
5114
5115 void
5116 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5117 {
5118     char buf[MSG_SIZ];
5119
5120     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5121         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5122             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5123             SendToProgram(buf, cps);
5124             return;
5125         }
5126         // null move in variant where engine does not understand it (for analysis purposes)
5127         SendBoard(cps, moveNum + 1); // send position after move in stead.
5128         return;
5129     }
5130     if (cps->useUsermove) {
5131       SendToProgram("usermove ", cps);
5132     }
5133     if (cps->useSAN) {
5134       char *space;
5135       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5136         int len = space - parseList[moveNum];
5137         memcpy(buf, parseList[moveNum], len);
5138         buf[len++] = '\n';
5139         buf[len] = NULLCHAR;
5140       } else {
5141         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5142       }
5143       SendToProgram(buf, cps);
5144     } else {
5145       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5146         AlphaRank(moveList[moveNum], 4);
5147         SendToProgram(moveList[moveNum], cps);
5148         AlphaRank(moveList[moveNum], 4); // and back
5149       } else
5150       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5151        * the engine. It would be nice to have a better way to identify castle
5152        * moves here. */
5153       if(appData.fischerCastling && cps->useOOCastle) {
5154         int fromX = moveList[moveNum][0] - AAA;
5155         int fromY = moveList[moveNum][1] - ONE;
5156         int toX = moveList[moveNum][2] - AAA;
5157         int toY = moveList[moveNum][3] - ONE;
5158         if((boards[moveNum][fromY][fromX] == WhiteKing
5159             && boards[moveNum][toY][toX] == WhiteRook)
5160            || (boards[moveNum][fromY][fromX] == BlackKing
5161                && boards[moveNum][toY][toX] == BlackRook)) {
5162           if(toX > fromX) SendToProgram("O-O\n", cps);
5163           else SendToProgram("O-O-O\n", cps);
5164         }
5165         else SendToProgram(moveList[moveNum], cps);
5166       } else
5167       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5168         char *m = moveList[moveNum];
5169         static char c[2];
5170         *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
5171         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
5172           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5173                                                m[2], m[3] - '0',
5174                                                m[5], m[6] - '0',
5175                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5176         else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5177           *c = m[9]; if(*c == '\n') *c = NULLCHAR;
5178           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
5179                                                m[7], m[8] - '0',
5180                                                m[7], m[8] - '0',
5181                                                m[5], m[6] - '0',
5182                                                m[5], m[6] - '0',
5183                                                m[2], m[3] - '0', c);
5184         } else
5185           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5186                                                m[5], m[6] - '0',
5187                                                m[5], m[6] - '0',
5188                                                m[2], m[3] - '0', c);
5189           SendToProgram(buf, cps);
5190       } else
5191       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5192         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5193           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5194           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5195                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5196         } else
5197           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5198                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5199         SendToProgram(buf, cps);
5200       }
5201       else SendToProgram(moveList[moveNum], cps);
5202       /* End of additions by Tord */
5203     }
5204
5205     /* [HGM] setting up the opening has brought engine in force mode! */
5206     /*       Send 'go' if we are in a mode where machine should play. */
5207     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5208         (gameMode == TwoMachinesPlay   ||
5209 #if ZIPPY
5210          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5211 #endif
5212          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5213         SendToProgram("go\n", cps);
5214   if (appData.debugMode) {
5215     fprintf(debugFP, "(extra)\n");
5216   }
5217     }
5218     setboardSpoiledMachineBlack = 0;
5219 }
5220
5221 void
5222 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5223 {
5224     char user_move[MSG_SIZ];
5225     char suffix[4];
5226
5227     if(gameInfo.variant == VariantSChess && promoChar) {
5228         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5229         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5230     } else suffix[0] = NULLCHAR;
5231
5232     switch (moveType) {
5233       default:
5234         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5235                 (int)moveType, fromX, fromY, toX, toY);
5236         DisplayError(user_move + strlen("say "), 0);
5237         break;
5238       case WhiteKingSideCastle:
5239       case BlackKingSideCastle:
5240       case WhiteQueenSideCastleWild:
5241       case BlackQueenSideCastleWild:
5242       /* PUSH Fabien */
5243       case WhiteHSideCastleFR:
5244       case BlackHSideCastleFR:
5245       /* POP Fabien */
5246         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5247         break;
5248       case WhiteQueenSideCastle:
5249       case BlackQueenSideCastle:
5250       case WhiteKingSideCastleWild:
5251       case BlackKingSideCastleWild:
5252       /* PUSH Fabien */
5253       case WhiteASideCastleFR:
5254       case BlackASideCastleFR:
5255       /* POP Fabien */
5256         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5257         break;
5258       case WhiteNonPromotion:
5259       case BlackNonPromotion:
5260         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5261         break;
5262       case WhitePromotion:
5263       case BlackPromotion:
5264         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5265            gameInfo.variant == VariantMakruk)
5266           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5267                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5268                 PieceToChar(WhiteFerz));
5269         else if(gameInfo.variant == VariantGreat)
5270           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5271                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5272                 PieceToChar(WhiteMan));
5273         else
5274           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5275                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5276                 promoChar);
5277         break;
5278       case WhiteDrop:
5279       case BlackDrop:
5280       drop:
5281         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5282                  ToUpper(PieceToChar((ChessSquare) fromX)),
5283                  AAA + toX, ONE + toY);
5284         break;
5285       case IllegalMove:  /* could be a variant we don't quite understand */
5286         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5287       case NormalMove:
5288       case WhiteCapturesEnPassant:
5289       case BlackCapturesEnPassant:
5290         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5291                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5292         break;
5293     }
5294     SendToICS(user_move);
5295     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5296         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5297 }
5298
5299 void
5300 UploadGameEvent ()
5301 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5302     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5303     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5304     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5305       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5306       return;
5307     }
5308     if(gameMode != IcsExamining) { // is this ever not the case?
5309         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5310
5311         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5312           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5313         } else { // on FICS we must first go to general examine mode
5314           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5315         }
5316         if(gameInfo.variant != VariantNormal) {
5317             // try figure out wild number, as xboard names are not always valid on ICS
5318             for(i=1; i<=36; i++) {
5319               snprintf(buf, MSG_SIZ, "wild/%d", i);
5320                 if(StringToVariant(buf) == gameInfo.variant) break;
5321             }
5322             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5323             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5324             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5325         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5326         SendToICS(ics_prefix);
5327         SendToICS(buf);
5328         if(startedFromSetupPosition || backwardMostMove != 0) {
5329           fen = PositionToFEN(backwardMostMove, NULL, 1);
5330           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5331             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5332             SendToICS(buf);
5333           } else { // FICS: everything has to set by separate bsetup commands
5334             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5335             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5336             SendToICS(buf);
5337             if(!WhiteOnMove(backwardMostMove)) {
5338                 SendToICS("bsetup tomove black\n");
5339             }
5340             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5341             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5342             SendToICS(buf);
5343             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5344             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5345             SendToICS(buf);
5346             i = boards[backwardMostMove][EP_STATUS];
5347             if(i >= 0) { // set e.p.
5348               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5349                 SendToICS(buf);
5350             }
5351             bsetup++;
5352           }
5353         }
5354       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5355             SendToICS("bsetup done\n"); // switch to normal examining.
5356     }
5357     for(i = backwardMostMove; i<last; i++) {
5358         char buf[20];
5359         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5360         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5361             int len = strlen(moveList[i]);
5362             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5363             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5364         }
5365         SendToICS(buf);
5366     }
5367     SendToICS(ics_prefix);
5368     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5369 }
5370
5371 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5372 int legNr = 1;
5373
5374 void
5375 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5376 {
5377     if (rf == DROP_RANK) {
5378       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5379       sprintf(move, "%c@%c%c\n",
5380                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5381     } else {
5382         if (promoChar == 'x' || promoChar == NULLCHAR) {
5383           sprintf(move, "%c%c%c%c\n",
5384                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5385           if(killX >= 0 && killY >= 0) {
5386             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5387             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5388           }
5389         } else {
5390             sprintf(move, "%c%c%c%c%c\n",
5391                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5392           if(killX >= 0 && killY >= 0) {
5393             sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5394             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5395           }
5396         }
5397     }
5398 }
5399
5400 void
5401 ProcessICSInitScript (FILE *f)
5402 {
5403     char buf[MSG_SIZ];
5404
5405     while (fgets(buf, MSG_SIZ, f)) {
5406         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5407     }
5408
5409     fclose(f);
5410 }
5411
5412
5413 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5414 int dragging;
5415 static ClickType lastClickType;
5416
5417 int
5418 PieceInString (char *s, ChessSquare piece)
5419 {
5420   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5421   while((p = strchr(s, ID))) {
5422     if(!suffix || p[1] == suffix) return TRUE;
5423     s = p;
5424   }
5425   return FALSE;
5426 }
5427
5428 int
5429 Partner (ChessSquare *p)
5430 { // change piece into promotion partner if one shogi-promotes to the other
5431   ChessSquare partner = promoPartner[*p];
5432   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5433   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5434   *p = partner;
5435   return 1;
5436 }
5437
5438 void
5439 Sweep (int step)
5440 {
5441     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5442     static int toggleFlag;
5443     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5444     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5445     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5446     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5447     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5448     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5449     do {
5450         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5451         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5452         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5453         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5454         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5455         if(!step) step = -1;
5456     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5457             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5458             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5459             promoSweep == pawn ||
5460             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5461             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5462     if(toX >= 0) {
5463         int victim = boards[currentMove][toY][toX];
5464         boards[currentMove][toY][toX] = promoSweep;
5465         DrawPosition(FALSE, boards[currentMove]);
5466         boards[currentMove][toY][toX] = victim;
5467     } else
5468     ChangeDragPiece(promoSweep);
5469 }
5470
5471 int
5472 PromoScroll (int x, int y)
5473 {
5474   int step = 0;
5475
5476   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5477   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5478   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5479   if(!step) return FALSE;
5480   lastX = x; lastY = y;
5481   if((promoSweep < BlackPawn) == flipView) step = -step;
5482   if(step > 0) selectFlag = 1;
5483   if(!selectFlag) Sweep(step);
5484   return FALSE;
5485 }
5486
5487 void
5488 NextPiece (int step)
5489 {
5490     ChessSquare piece = boards[currentMove][toY][toX];
5491     do {
5492         pieceSweep -= step;
5493         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5494         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5495         if(!step) step = -1;
5496     } while(PieceToChar(pieceSweep) == '.');
5497     boards[currentMove][toY][toX] = pieceSweep;
5498     DrawPosition(FALSE, boards[currentMove]);
5499     boards[currentMove][toY][toX] = piece;
5500 }
5501 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5502 void
5503 AlphaRank (char *move, int n)
5504 {
5505 //    char *p = move, c; int x, y;
5506
5507     if (appData.debugMode) {
5508         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5509     }
5510
5511     if(move[1]=='*' &&
5512        move[2]>='0' && move[2]<='9' &&
5513        move[3]>='a' && move[3]<='x'    ) {
5514         move[1] = '@';
5515         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5516         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5517     } else
5518     if(move[0]>='0' && move[0]<='9' &&
5519        move[1]>='a' && move[1]<='x' &&
5520        move[2]>='0' && move[2]<='9' &&
5521        move[3]>='a' && move[3]<='x'    ) {
5522         /* input move, Shogi -> normal */
5523         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5524         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5525         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5526         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5527     } else
5528     if(move[1]=='@' &&
5529        move[3]>='0' && move[3]<='9' &&
5530        move[2]>='a' && move[2]<='x'    ) {
5531         move[1] = '*';
5532         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5533         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5534     } else
5535     if(
5536        move[0]>='a' && move[0]<='x' &&
5537        move[3]>='0' && move[3]<='9' &&
5538        move[2]>='a' && move[2]<='x'    ) {
5539          /* output move, normal -> Shogi */
5540         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5541         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5542         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5543         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5544         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5545     }
5546     if (appData.debugMode) {
5547         fprintf(debugFP, "   out = '%s'\n", move);
5548     }
5549 }
5550
5551 char yy_textstr[8000];
5552
5553 /* Parser for moves from gnuchess, ICS, or user typein box */
5554 Boolean
5555 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5556 {
5557     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5558
5559     switch (*moveType) {
5560       case WhitePromotion:
5561       case BlackPromotion:
5562       case WhiteNonPromotion:
5563       case BlackNonPromotion:
5564       case NormalMove:
5565       case FirstLeg:
5566       case WhiteCapturesEnPassant:
5567       case BlackCapturesEnPassant:
5568       case WhiteKingSideCastle:
5569       case WhiteQueenSideCastle:
5570       case BlackKingSideCastle:
5571       case BlackQueenSideCastle:
5572       case WhiteKingSideCastleWild:
5573       case WhiteQueenSideCastleWild:
5574       case BlackKingSideCastleWild:
5575       case BlackQueenSideCastleWild:
5576       /* Code added by Tord: */
5577       case WhiteHSideCastleFR:
5578       case WhiteASideCastleFR:
5579       case BlackHSideCastleFR:
5580       case BlackASideCastleFR:
5581       /* End of code added by Tord */
5582       case IllegalMove:         /* bug or odd chess variant */
5583         if(currentMoveString[1] == '@') { // illegal drop
5584           *fromX = WhiteOnMove(moveNum) ?
5585             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5586             (int) CharToPiece(ToLower(currentMoveString[0]));
5587           goto drop;
5588         }
5589         *fromX = currentMoveString[0] - AAA;
5590         *fromY = currentMoveString[1] - ONE;
5591         *toX = currentMoveString[2] - AAA;
5592         *toY = currentMoveString[3] - ONE;
5593         *promoChar = currentMoveString[4];
5594         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5595         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5596             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5597     if (appData.debugMode) {
5598         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5599     }
5600             *fromX = *fromY = *toX = *toY = 0;
5601             return FALSE;
5602         }
5603         if (appData.testLegality) {
5604           return (*moveType != IllegalMove);
5605         } else {
5606           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5607                          // [HGM] lion: if this is a double move we are less critical
5608                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5609         }
5610
5611       case WhiteDrop:
5612       case BlackDrop:
5613         *fromX = *moveType == WhiteDrop ?
5614           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5615           (int) CharToPiece(ToLower(currentMoveString[0]));
5616       drop:
5617         *fromY = DROP_RANK;
5618         *toX = currentMoveString[2] - AAA;
5619         *toY = currentMoveString[3] - ONE;
5620         *promoChar = NULLCHAR;
5621         return TRUE;
5622
5623       case AmbiguousMove:
5624       case ImpossibleMove:
5625       case EndOfFile:
5626       case ElapsedTime:
5627       case Comment:
5628       case PGNTag:
5629       case NAG:
5630       case WhiteWins:
5631       case BlackWins:
5632       case GameIsDrawn:
5633       default:
5634     if (appData.debugMode) {
5635         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5636     }
5637         /* bug? */
5638         *fromX = *fromY = *toX = *toY = 0;
5639         *promoChar = NULLCHAR;
5640         return FALSE;
5641     }
5642 }
5643
5644 Boolean pushed = FALSE;
5645 char *lastParseAttempt;
5646
5647 void
5648 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5649 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5650   int fromX, fromY, toX, toY; char promoChar;
5651   ChessMove moveType;
5652   Boolean valid;
5653   int nr = 0;
5654
5655   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5656   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5657     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5658     pushed = TRUE;
5659   }
5660   endPV = forwardMostMove;
5661   do {
5662     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5663     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5664     lastParseAttempt = pv;
5665     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5666     if(!valid && nr == 0 &&
5667        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5668         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5669         // Hande case where played move is different from leading PV move
5670         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5671         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5672         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5673         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5674           endPV += 2; // if position different, keep this
5675           moveList[endPV-1][0] = fromX + AAA;
5676           moveList[endPV-1][1] = fromY + ONE;
5677           moveList[endPV-1][2] = toX + AAA;
5678           moveList[endPV-1][3] = toY + ONE;
5679           parseList[endPV-1][0] = NULLCHAR;
5680           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5681         }
5682       }
5683     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5684     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5685     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5686     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5687         valid++; // allow comments in PV
5688         continue;
5689     }
5690     nr++;
5691     if(endPV+1 > framePtr) break; // no space, truncate
5692     if(!valid) break;
5693     endPV++;
5694     CopyBoard(boards[endPV], boards[endPV-1]);
5695     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5696     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5697     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5698     CoordsToAlgebraic(boards[endPV - 1],
5699                              PosFlags(endPV - 1),
5700                              fromY, fromX, toY, toX, promoChar,
5701                              parseList[endPV - 1]);
5702   } while(valid);
5703   if(atEnd == 2) return; // used hidden, for PV conversion
5704   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5705   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5706   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5707                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5708   DrawPosition(TRUE, boards[currentMove]);
5709 }
5710
5711 int
5712 MultiPV (ChessProgramState *cps, int kind)
5713 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5714         int i;
5715         for(i=0; i<cps->nrOptions; i++) {
5716             char *s = cps->option[i].name;
5717             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5718             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5719                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5720         }
5721         return -1;
5722 }
5723
5724 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5725 static int multi, pv_margin;
5726 static ChessProgramState *activeCps;
5727
5728 Boolean
5729 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5730 {
5731         int startPV, lineStart, origIndex = index;
5732         char *p, buf2[MSG_SIZ];
5733         ChessProgramState *cps = (pane ? &second : &first);
5734
5735         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5736         lastX = x; lastY = y;
5737         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5738         lineStart = startPV = index;
5739         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5740         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5741         index = startPV;
5742         do{ while(buf[index] && buf[index] != '\n') index++;
5743         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5744         buf[index] = 0;
5745         if(lineStart == 0 && gameMode == AnalyzeMode) {
5746             int n = 0;
5747             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5748             if(n == 0) { // click not on "fewer" or "more"
5749                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5750                     pv_margin = cps->option[multi].value;
5751                     activeCps = cps; // non-null signals margin adjustment
5752                 }
5753             } else if((multi = MultiPV(cps, 1)) >= 0) {
5754                 n += cps->option[multi].value; if(n < 1) n = 1;
5755                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5756                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5757                 cps->option[multi].value = n;
5758                 *start = *end = 0;
5759                 return FALSE;
5760             }
5761         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5762                 ExcludeClick(origIndex - lineStart);
5763                 return FALSE;
5764         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5765                 Collapse(origIndex - lineStart);
5766                 return FALSE;
5767         }
5768         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5769         *start = startPV; *end = index-1;
5770         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5771         return TRUE;
5772 }
5773
5774 char *
5775 PvToSAN (char *pv)
5776 {
5777         static char buf[10*MSG_SIZ];
5778         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5779         *buf = NULLCHAR;
5780         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5781         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5782         for(i = forwardMostMove; i<endPV; i++){
5783             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5784             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5785             k += strlen(buf+k);
5786         }
5787         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5788         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5789         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5790         endPV = savedEnd;
5791         return buf;
5792 }
5793
5794 Boolean
5795 LoadPV (int x, int y)
5796 { // called on right mouse click to load PV
5797   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5798   lastX = x; lastY = y;
5799   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5800   extendGame = FALSE;
5801   return TRUE;
5802 }
5803
5804 void
5805 UnLoadPV ()
5806 {
5807   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5808   if(activeCps) {
5809     if(pv_margin != activeCps->option[multi].value) {
5810       char buf[MSG_SIZ];
5811       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5812       SendToProgram(buf, activeCps);
5813       activeCps->option[multi].value = pv_margin;
5814     }
5815     activeCps = NULL;
5816     return;
5817   }
5818   if(endPV < 0) return;
5819   if(appData.autoCopyPV) CopyFENToClipboard();
5820   endPV = -1;
5821   if(extendGame && currentMove > forwardMostMove) {
5822         Boolean saveAnimate = appData.animate;
5823         if(pushed) {
5824             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5825                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5826             } else storedGames--; // abandon shelved tail of original game
5827         }
5828         pushed = FALSE;
5829         forwardMostMove = currentMove;
5830         currentMove = oldFMM;
5831         appData.animate = FALSE;
5832         ToNrEvent(forwardMostMove);
5833         appData.animate = saveAnimate;
5834   }
5835   currentMove = forwardMostMove;
5836   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5837   ClearPremoveHighlights();
5838   DrawPosition(TRUE, boards[currentMove]);
5839 }
5840
5841 void
5842 MovePV (int x, int y, int h)
5843 { // step through PV based on mouse coordinates (called on mouse move)
5844   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5845
5846   if(activeCps) { // adjusting engine's multi-pv margin
5847     if(x > lastX) pv_margin++; else
5848     if(x < lastX) pv_margin -= (pv_margin > 0);
5849     if(x != lastX) {
5850       char buf[MSG_SIZ];
5851       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5852       DisplayMessage(buf, "");
5853     }
5854     lastX = x;
5855     return;
5856   }
5857   // we must somehow check if right button is still down (might be released off board!)
5858   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5859   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5860   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5861   if(!step) return;
5862   lastX = x; lastY = y;
5863
5864   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5865   if(endPV < 0) return;
5866   if(y < margin) step = 1; else
5867   if(y > h - margin) step = -1;
5868   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5869   currentMove += step;
5870   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5871   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5872                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5873   DrawPosition(FALSE, boards[currentMove]);
5874 }
5875
5876
5877 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5878 // All positions will have equal probability, but the current method will not provide a unique
5879 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5880 #define DARK 1
5881 #define LITE 2
5882 #define ANY 3
5883
5884 int squaresLeft[4];
5885 int piecesLeft[(int)BlackPawn];
5886 int seed, nrOfShuffles;
5887
5888 void
5889 GetPositionNumber ()
5890 {       // sets global variable seed
5891         int i;
5892
5893         seed = appData.defaultFrcPosition;
5894         if(seed < 0) { // randomize based on time for negative FRC position numbers
5895                 for(i=0; i<50; i++) seed += random();
5896                 seed = random() ^ random() >> 8 ^ random() << 8;
5897                 if(seed<0) seed = -seed;
5898         }
5899 }
5900
5901 int
5902 put (Board board, int pieceType, int rank, int n, int shade)
5903 // put the piece on the (n-1)-th empty squares of the given shade
5904 {
5905         int i;
5906
5907         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5908                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5909                         board[rank][i] = (ChessSquare) pieceType;
5910                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5911                         squaresLeft[ANY]--;
5912                         piecesLeft[pieceType]--;
5913                         return i;
5914                 }
5915         }
5916         return -1;
5917 }
5918
5919
5920 void
5921 AddOnePiece (Board board, int pieceType, int rank, int shade)
5922 // calculate where the next piece goes, (any empty square), and put it there
5923 {
5924         int i;
5925
5926         i = seed % squaresLeft[shade];
5927         nrOfShuffles *= squaresLeft[shade];
5928         seed /= squaresLeft[shade];
5929         put(board, pieceType, rank, i, shade);
5930 }
5931
5932 void
5933 AddTwoPieces (Board board, int pieceType, int rank)
5934 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5935 {
5936         int i, n=squaresLeft[ANY], j=n-1, k;
5937
5938         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5939         i = seed % k;  // pick one
5940         nrOfShuffles *= k;
5941         seed /= k;
5942         while(i >= j) i -= j--;
5943         j = n - 1 - j; i += j;
5944         put(board, pieceType, rank, j, ANY);
5945         put(board, pieceType, rank, i, ANY);
5946 }
5947
5948 void
5949 SetUpShuffle (Board board, int number)
5950 {
5951         int i, p, first=1;
5952
5953         GetPositionNumber(); nrOfShuffles = 1;
5954
5955         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5956         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5957         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5958
5959         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5960
5961         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5962             p = (int) board[0][i];
5963             if(p < (int) BlackPawn) piecesLeft[p] ++;
5964             board[0][i] = EmptySquare;
5965         }
5966
5967         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5968             // shuffles restricted to allow normal castling put KRR first
5969             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5970                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5971             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5972                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5973             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5974                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5975             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5976                 put(board, WhiteRook, 0, 0, ANY);
5977             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5978         }
5979
5980         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5981             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5982             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5983                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5984                 while(piecesLeft[p] >= 2) {
5985                     AddOnePiece(board, p, 0, LITE);
5986                     AddOnePiece(board, p, 0, DARK);
5987                 }
5988                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5989             }
5990
5991         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5992             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5993             // but we leave King and Rooks for last, to possibly obey FRC restriction
5994             if(p == (int)WhiteRook) continue;
5995             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5996             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5997         }
5998
5999         // now everything is placed, except perhaps King (Unicorn) and Rooks
6000
6001         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
6002             // Last King gets castling rights
6003             while(piecesLeft[(int)WhiteUnicorn]) {
6004                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6005                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6006             }
6007
6008             while(piecesLeft[(int)WhiteKing]) {
6009                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6010                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6011             }
6012
6013
6014         } else {
6015             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6016             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6017         }
6018
6019         // Only Rooks can be left; simply place them all
6020         while(piecesLeft[(int)WhiteRook]) {
6021                 i = put(board, WhiteRook, 0, 0, ANY);
6022                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6023                         if(first) {
6024                                 first=0;
6025                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6026                         }
6027                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6028                 }
6029         }
6030         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6031             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6032         }
6033
6034         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6035 }
6036
6037 int
6038 ptclen (const char *s, char *escapes)
6039 {
6040     int n = 0;
6041     if(!*escapes) return strlen(s);
6042     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6043     return n;
6044 }
6045
6046 int
6047 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6048 /* [HGM] moved here from winboard.c because of its general usefulness */
6049 /*       Basically a safe strcpy that uses the last character as King */
6050 {
6051     int result = FALSE; int NrPieces;
6052     unsigned char partner[EmptySquare];
6053
6054     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6055                     && NrPieces >= 12 && !(NrPieces&1)) {
6056         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6057
6058         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6059         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6060             char *p, c=0;
6061             if(map[j] == '/') offs = WhitePBishop - i, j++;
6062             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6063             table[i+offs] = map[j++];
6064             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6065             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6066             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6067         }
6068         table[(int) WhiteKing]  = map[j++];
6069         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6070             char *p, c=0;
6071             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6072             i = WHITE_TO_BLACK ii;
6073             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6074             table[i+offs] = map[j++];
6075             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6076             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6077             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6078         }
6079         table[(int) BlackKing]  = map[j++];
6080
6081
6082         if(*escapes) { // set up promotion pairing
6083             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6084             // pieceToChar entirely filled, so we can look up specified partners
6085             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6086                 int c = table[i];
6087                 if(c == '^' || c == '-') { // has specified partner
6088                     int p;
6089                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6090                     if(c == '^') table[i] = '+';
6091                     if(p < EmptySquare) {
6092                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6093                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6094                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6095                     }
6096                 } else if(c == '*') {
6097                     table[i] = partner[i];
6098                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6099                 }
6100             }
6101         }
6102
6103         result = TRUE;
6104     }
6105
6106     return result;
6107 }
6108
6109 int
6110 SetCharTable (unsigned char *table, const char * map)
6111 {
6112     return SetCharTableEsc(table, map, "");
6113 }
6114
6115 void
6116 Prelude (Board board)
6117 {       // [HGM] superchess: random selection of exo-pieces
6118         int i, j, k; ChessSquare p;
6119         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6120
6121         GetPositionNumber(); // use FRC position number
6122
6123         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6124             SetCharTable(pieceToChar, appData.pieceToCharTable);
6125             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6126                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6127         }
6128
6129         j = seed%4;                 seed /= 4;
6130         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6131         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6132         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6133         j = seed%3 + (seed%3 >= j); seed /= 3;
6134         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6135         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6136         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6137         j = seed%3;                 seed /= 3;
6138         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6139         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6140         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6141         j = seed%2 + (seed%2 >= j); seed /= 2;
6142         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6143         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6144         board[handSize-1-k][0] = WHITE_TO_BLACK p;  board[handSize-1-k][1]++;
6145         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6146         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6147         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6148         put(board, exoPieces[0],    0, 0, ANY);
6149         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6150 }
6151
6152 void
6153 InitPosition (int redraw)
6154 {
6155     ChessSquare (* pieces)[BOARD_FILES];
6156     int i, j, pawnRow=1, pieceRows=1, overrule,
6157     oldx = gameInfo.boardWidth,
6158     oldy = gameInfo.boardHeight,
6159     oldh = gameInfo.holdingsWidth;
6160     static int oldv;
6161
6162     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6163
6164     /* [AS] Initialize pv info list [HGM] and game status */
6165     {
6166         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6167             pvInfoList[i].depth = 0;
6168             boards[i][EP_STATUS] = EP_NONE;
6169             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6170         }
6171
6172         initialRulePlies = 0; /* 50-move counter start */
6173
6174         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6175         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6176     }
6177
6178
6179     /* [HGM] logic here is completely changed. In stead of full positions */
6180     /* the initialized data only consist of the two backranks. The switch */
6181     /* selects which one we will use, which is than copied to the Board   */
6182     /* initialPosition, which for the rest is initialized by Pawns and    */
6183     /* empty squares. This initial position is then copied to boards[0],  */
6184     /* possibly after shuffling, so that it remains available.            */
6185
6186     gameInfo.holdingsWidth = 0; /* default board sizes */
6187     gameInfo.boardWidth    = 8;
6188     gameInfo.boardHeight   = 8;
6189     gameInfo.holdingsSize  = 0;
6190     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6191     for(i=0; i<BOARD_FILES-6; i++)
6192       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6193     initialPosition[EP_STATUS] = EP_NONE;
6194     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6195     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6196     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6197          SetCharTable(pieceNickName, appData.pieceNickNames);
6198     else SetCharTable(pieceNickName, "............");
6199     pieces = FIDEArray;
6200
6201     switch (gameInfo.variant) {
6202     case VariantFischeRandom:
6203       shuffleOpenings = TRUE;
6204       appData.fischerCastling = TRUE;
6205     default:
6206       break;
6207     case VariantShatranj:
6208       pieces = ShatranjArray;
6209       nrCastlingRights = 0;
6210       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6211       break;
6212     case VariantMakruk:
6213       pieces = makrukArray;
6214       nrCastlingRights = 0;
6215       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6216       break;
6217     case VariantASEAN:
6218       pieces = aseanArray;
6219       nrCastlingRights = 0;
6220       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6221       break;
6222     case VariantTwoKings:
6223       pieces = twoKingsArray;
6224       break;
6225     case VariantGrand:
6226       pieces = GrandArray;
6227       nrCastlingRights = 0;
6228       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6229       gameInfo.boardWidth = 10;
6230       gameInfo.boardHeight = 10;
6231       gameInfo.holdingsSize = 7;
6232       break;
6233     case VariantCapaRandom:
6234       shuffleOpenings = TRUE;
6235       appData.fischerCastling = TRUE;
6236     case VariantCapablanca:
6237       pieces = CapablancaArray;
6238       gameInfo.boardWidth = 10;
6239       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6240       break;
6241     case VariantGothic:
6242       pieces = GothicArray;
6243       gameInfo.boardWidth = 10;
6244       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6245       break;
6246     case VariantSChess:
6247       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6248       gameInfo.holdingsSize = 7;
6249       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6250       break;
6251     case VariantJanus:
6252       pieces = JanusArray;
6253       gameInfo.boardWidth = 10;
6254       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6255       nrCastlingRights = 6;
6256         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6257         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6258         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6259         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6260         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6261         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6262       break;
6263     case VariantFalcon:
6264       pieces = FalconArray;
6265       gameInfo.boardWidth = 10;
6266       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6267       break;
6268     case VariantXiangqi:
6269       pieces = XiangqiArray;
6270       gameInfo.boardWidth  = 9;
6271       gameInfo.boardHeight = 10;
6272       nrCastlingRights = 0;
6273       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6274       break;
6275     case VariantShogi:
6276       pieces = ShogiArray;
6277       gameInfo.boardWidth  = 9;
6278       gameInfo.boardHeight = 9;
6279       gameInfo.holdingsSize = 7;
6280       nrCastlingRights = 0;
6281       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6282       break;
6283     case VariantChu:
6284       pieces = ChuArray; pieceRows = 3;
6285       gameInfo.boardWidth  = 12;
6286       gameInfo.boardHeight = 12;
6287       nrCastlingRights = 0;
6288 //      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6289   //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6290       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"
6291                                    "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);
6292       break;
6293     case VariantCourier:
6294       pieces = CourierArray;
6295       gameInfo.boardWidth  = 12;
6296       nrCastlingRights = 0;
6297       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6298       break;
6299     case VariantKnightmate:
6300       pieces = KnightmateArray;
6301       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6302       break;
6303     case VariantSpartan:
6304       pieces = SpartanArray;
6305       SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
6306       break;
6307     case VariantLion:
6308       pieces = lionArray;
6309       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6310       break;
6311     case VariantChuChess:
6312       pieces = ChuChessArray;
6313       gameInfo.boardWidth = 10;
6314       gameInfo.boardHeight = 10;
6315       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6316       break;
6317     case VariantFairy:
6318       pieces = fairyArray;
6319       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6320       break;
6321     case VariantGreat:
6322       pieces = GreatArray;
6323       gameInfo.boardWidth = 10;
6324       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6325       gameInfo.holdingsSize = 8;
6326       break;
6327     case VariantSuper:
6328       pieces = FIDEArray;
6329       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6330       gameInfo.holdingsSize = 8;
6331       startedFromSetupPosition = TRUE;
6332       break;
6333     case VariantCrazyhouse:
6334     case VariantBughouse:
6335       pieces = FIDEArray;
6336       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6337       gameInfo.holdingsSize = 5;
6338       break;
6339     case VariantWildCastle:
6340       pieces = FIDEArray;
6341       /* !!?shuffle with kings guaranteed to be on d or e file */
6342       shuffleOpenings = 1;
6343       break;
6344     case VariantNoCastle:
6345       pieces = FIDEArray;
6346       nrCastlingRights = 0;
6347       /* !!?unconstrained back-rank shuffle */
6348       shuffleOpenings = 1;
6349       break;
6350     }
6351
6352     overrule = 0;
6353     if(appData.NrFiles >= 0) {
6354         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6355         gameInfo.boardWidth = appData.NrFiles;
6356     }
6357     if(appData.NrRanks >= 0) {
6358         gameInfo.boardHeight = appData.NrRanks;
6359     }
6360     if(appData.holdingsSize >= 0) {
6361         i = appData.holdingsSize;
6362         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6363         gameInfo.holdingsSize = i;
6364     }
6365     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6366     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6367         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6368
6369     handSize = BOARD_HEIGHT;
6370     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6371     if(pawnRow < 1) pawnRow = 1;
6372     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6373        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6374     if(gameInfo.variant == VariantChu) pawnRow = 3;
6375
6376     /* User pieceToChar list overrules defaults */
6377     if(appData.pieceToCharTable != NULL)
6378         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6379
6380     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6381
6382         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6383             s = (ChessSquare) 0; /* account holding counts in guard band */
6384         for( i=0; i<BOARD_HEIGHT; i++ )
6385             initialPosition[i][j] = s;
6386
6387         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6388         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6389         initialPosition[pawnRow][j] = WhitePawn;
6390         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6391         if(gameInfo.variant == VariantXiangqi) {
6392             if(j&1) {
6393                 initialPosition[pawnRow][j] =
6394                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6395                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6396                    initialPosition[2][j] = WhiteCannon;
6397                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6398                 }
6399             }
6400         }
6401         if(gameInfo.variant == VariantChu) {
6402              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6403                initialPosition[pawnRow+1][j] = WhiteCobra,
6404                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6405              for(i=1; i<pieceRows; i++) {
6406                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6407                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6408              }
6409         }
6410         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6411             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6412                initialPosition[0][j] = WhiteRook;
6413                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6414             }
6415         }
6416         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6417     }
6418     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6419     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6420
6421             j=BOARD_LEFT+1;
6422             initialPosition[1][j] = WhiteBishop;
6423             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6424             j=BOARD_RGHT-2;
6425             initialPosition[1][j] = WhiteRook;
6426             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6427     }
6428
6429     if( nrCastlingRights == -1) {
6430         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6431         /*       This sets default castling rights from none to normal corners   */
6432         /* Variants with other castling rights must set them themselves above    */
6433         nrCastlingRights = 6;
6434
6435         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6436         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6437         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6438         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6439         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6440         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6441      }
6442
6443      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6444      if(gameInfo.variant == VariantGreat) { // promotion commoners
6445         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6446         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6447         initialPosition[handSize-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6448         initialPosition[handSize-1-PieceToNumber(WhiteMan)][1] = 9;
6449      }
6450      if( gameInfo.variant == VariantSChess ) {
6451       initialPosition[1][0] = BlackMarshall;
6452       initialPosition[2][0] = BlackAngel;
6453       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6454       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6455       initialPosition[1][1] = initialPosition[2][1] =
6456       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6457      }
6458      initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
6459   if (appData.debugMode) {
6460     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6461   }
6462     if(shuffleOpenings) {
6463         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6464         startedFromSetupPosition = TRUE;
6465     }
6466     if(startedFromPositionFile) {
6467       /* [HGM] loadPos: use PositionFile for every new game */
6468       CopyBoard(initialPosition, filePosition);
6469       for(i=0; i<nrCastlingRights; i++)
6470           initialRights[i] = filePosition[CASTLING][i];
6471       startedFromSetupPosition = TRUE;
6472     }
6473     if(*appData.men) LoadPieceDesc(appData.men);
6474
6475     CopyBoard(boards[0], initialPosition);
6476
6477     if(oldx != gameInfo.boardWidth ||
6478        oldy != gameInfo.boardHeight ||
6479        oldv != gameInfo.variant ||
6480        oldh != gameInfo.holdingsWidth
6481                                          )
6482             InitDrawingSizes(-2 ,0);
6483
6484     oldv = gameInfo.variant;
6485     if (redraw)
6486       DrawPosition(TRUE, boards[currentMove]);
6487 }
6488
6489 void
6490 SendBoard (ChessProgramState *cps, int moveNum)
6491 {
6492     char message[MSG_SIZ];
6493
6494     if (cps->useSetboard) {
6495       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6496       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6497       SendToProgram(message, cps);
6498       free(fen);
6499
6500     } else {
6501       ChessSquare *bp;
6502       int i, j, left=0, right=BOARD_WIDTH;
6503       /* Kludge to set black to move, avoiding the troublesome and now
6504        * deprecated "black" command.
6505        */
6506       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6507         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
6508
6509       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6510
6511       SendToProgram("edit\n", cps);
6512       SendToProgram("#\n", cps);
6513       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6514         bp = &boards[moveNum][i][left];
6515         for (j = left; j < right; j++, bp++) {
6516           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6517           if ((int) *bp < (int) BlackPawn) {
6518             if(j == BOARD_RGHT+1)
6519                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6520             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6521             if(message[0] == '+' || message[0] == '~') {
6522               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6523                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6524                         AAA + j, ONE + i - '0');
6525             }
6526             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6527                 message[1] = BOARD_RGHT   - 1 - j + '1';
6528                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6529             }
6530             SendToProgram(message, cps);
6531           }
6532         }
6533       }
6534
6535       SendToProgram("c\n", cps);
6536       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6537         bp = &boards[moveNum][i][left];
6538         for (j = left; j < right; j++, bp++) {
6539           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6540           if (((int) *bp != (int) EmptySquare)
6541               && ((int) *bp >= (int) BlackPawn)) {
6542             if(j == BOARD_LEFT-2)
6543                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6544             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6545                     AAA + j, ONE + i - '0');
6546             if(message[0] == '+' || message[0] == '~') {
6547               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6548                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6549                         AAA + j, ONE + i - '0');
6550             }
6551             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6552                 message[1] = BOARD_RGHT   - 1 - j + '1';
6553                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6554             }
6555             SendToProgram(message, cps);
6556           }
6557         }
6558       }
6559
6560       SendToProgram(".\n", cps);
6561     }
6562     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6563 }
6564
6565 char exclusionHeader[MSG_SIZ];
6566 int exCnt, excludePtr;
6567 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6568 static Exclusion excluTab[200];
6569 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6570
6571 static void
6572 WriteMap (int s)
6573 {
6574     int j;
6575     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6576     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6577 }
6578
6579 static void
6580 ClearMap ()
6581 {
6582     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6583     excludePtr = 24; exCnt = 0;
6584     WriteMap(0);
6585 }
6586
6587 static void
6588 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6589 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6590     char buf[2*MOVE_LEN], *p;
6591     Exclusion *e = excluTab;
6592     int i;
6593     for(i=0; i<exCnt; i++)
6594         if(e[i].ff == fromX && e[i].fr == fromY &&
6595            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6596     if(i == exCnt) { // was not in exclude list; add it
6597         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6598         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6599             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6600             return; // abort
6601         }
6602         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6603         excludePtr++; e[i].mark = excludePtr++;
6604         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6605         exCnt++;
6606     }
6607     exclusionHeader[e[i].mark] = state;
6608 }
6609
6610 static int
6611 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6612 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6613     char buf[MSG_SIZ];
6614     int j, k;
6615     ChessMove moveType;
6616     if((signed char)promoChar == -1) { // kludge to indicate best move
6617         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6618             return 1; // if unparsable, abort
6619     }
6620     // update exclusion map (resolving toggle by consulting existing state)
6621     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6622     j = k%8; k >>= 3;
6623     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6624     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6625          excludeMap[k] |=   1<<j;
6626     else excludeMap[k] &= ~(1<<j);
6627     // update header
6628     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6629     // inform engine
6630     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6631     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6632     SendToBoth(buf);
6633     return (state == '+');
6634 }
6635
6636 static void
6637 ExcludeClick (int index)
6638 {
6639     int i, j;
6640     Exclusion *e = excluTab;
6641     if(index < 25) { // none, best or tail clicked
6642         if(index < 13) { // none: include all
6643             WriteMap(0); // clear map
6644             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6645             SendToBoth("include all\n"); // and inform engine
6646         } else if(index > 18) { // tail
6647             if(exclusionHeader[19] == '-') { // tail was excluded
6648                 SendToBoth("include all\n");
6649                 WriteMap(0); // clear map completely
6650                 // now re-exclude selected moves
6651                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6652                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6653             } else { // tail was included or in mixed state
6654                 SendToBoth("exclude all\n");
6655                 WriteMap(0xFF); // fill map completely
6656                 // now re-include selected moves
6657                 j = 0; // count them
6658                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6659                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6660                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6661             }
6662         } else { // best
6663             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6664         }
6665     } else {
6666         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6667             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6668             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6669             break;
6670         }
6671     }
6672 }
6673
6674 ChessSquare
6675 DefaultPromoChoice (int white)
6676 {
6677     ChessSquare result;
6678     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6679        gameInfo.variant == VariantMakruk)
6680         result = WhiteFerz; // no choice
6681     else if(gameInfo.variant == VariantASEAN)
6682         result = WhiteRook; // no choice
6683     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6684         result= WhiteKing; // in Suicide Q is the last thing we want
6685     else if(gameInfo.variant == VariantSpartan)
6686         result = white ? WhiteQueen : WhiteAngel;
6687     else result = WhiteQueen;
6688     if(!white) result = WHITE_TO_BLACK result;
6689     return result;
6690 }
6691
6692 static int autoQueen; // [HGM] oneclick
6693
6694 int
6695 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6696 {
6697     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6698     /* [HGM] add Shogi promotions */
6699     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6700     ChessSquare piece, partner;
6701     ChessMove moveType;
6702     Boolean premove;
6703
6704     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6705     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6706
6707     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6708       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6709         return FALSE;
6710
6711     if(legal[toY][toX] == 4) return FALSE;
6712
6713     piece = boards[currentMove][fromY][fromX];
6714     if(gameInfo.variant == VariantChu) {
6715         promotionZoneSize = BOARD_HEIGHT/3;
6716         if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
6717         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6718     } else if(gameInfo.variant == VariantShogi) {
6719         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6720         highestPromotingPiece = (int)WhiteAlfil;
6721     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6722         promotionZoneSize = 3;
6723     }
6724
6725     // Treat Lance as Pawn when it is not representing Amazon or Lance
6726     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6727         if(piece == WhiteLance) piece = WhitePawn; else
6728         if(piece == BlackLance) piece = BlackPawn;
6729     }
6730
6731     // next weed out all moves that do not touch the promotion zone at all
6732     if((int)piece >= BlackPawn) {
6733         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6734              return FALSE;
6735         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6736         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6737     } else {
6738         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6739            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6740         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6741              return FALSE;
6742     }
6743
6744     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6745
6746     // weed out mandatory Shogi promotions
6747     if(gameInfo.variant == VariantShogi) {
6748         if(piece >= BlackPawn) {
6749             if(toY == 0 && piece == BlackPawn ||
6750                toY == 0 && piece == BlackQueen ||
6751                toY <= 1 && piece == BlackKnight) {
6752                 *promoChoice = '+';
6753                 return FALSE;
6754             }
6755         } else {
6756             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6757                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6758                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6759                 *promoChoice = '+';
6760                 return FALSE;
6761             }
6762         }
6763     }
6764
6765     // weed out obviously illegal Pawn moves
6766     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6767         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6768         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6769         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6770         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6771         // note we are not allowed to test for valid (non-)capture, due to premove
6772     }
6773
6774     // we either have a choice what to promote to, or (in Shogi) whether to promote
6775     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6776        gameInfo.variant == VariantMakruk) {
6777         ChessSquare p=BlackFerz;  // no choice
6778         while(p < EmptySquare) {  //but make sure we use piece that exists
6779             *promoChoice = PieceToChar(p++);
6780             if(*promoChoice != '.') break;
6781         }
6782         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6783     }
6784     // no sense asking what we must promote to if it is going to explode...
6785     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6786         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6787         return FALSE;
6788     }
6789     // give caller the default choice even if we will not make it
6790     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6791     partner = piece; // pieces can promote if the pieceToCharTable says so
6792     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6793     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6794     if(        sweepSelect && gameInfo.variant != VariantGreat
6795                            && gameInfo.variant != VariantGrand
6796                            && gameInfo.variant != VariantSuper) return FALSE;
6797     if(autoQueen) return FALSE; // predetermined
6798
6799     // suppress promotion popup on illegal moves that are not premoves
6800     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6801               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6802     if(appData.testLegality && !premove) {
6803         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6804                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6805         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6806         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6807             return FALSE;
6808     }
6809
6810     return TRUE;
6811 }
6812
6813 int
6814 InPalace (int row, int column)
6815 {   /* [HGM] for Xiangqi */
6816     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6817          column < (BOARD_WIDTH + 4)/2 &&
6818          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6819     return FALSE;
6820 }
6821
6822 int
6823 PieceForSquare (int x, int y)
6824 {
6825   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6826      return -1;
6827   else
6828      return boards[currentMove][y][x];
6829 }
6830
6831 int
6832 OKToStartUserMove (int x, int y)
6833 {
6834     ChessSquare from_piece;
6835     int white_piece;
6836
6837     if (matchMode) return FALSE;
6838     if (gameMode == EditPosition) return TRUE;
6839
6840     if (x >= 0 && y >= 0)
6841       from_piece = boards[currentMove][y][x];
6842     else
6843       from_piece = EmptySquare;
6844
6845     if (from_piece == EmptySquare) return FALSE;
6846
6847     white_piece = (int)from_piece >= (int)WhitePawn &&
6848       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6849
6850     switch (gameMode) {
6851       case AnalyzeFile:
6852       case TwoMachinesPlay:
6853       case EndOfGame:
6854         return FALSE;
6855
6856       case IcsObserving:
6857       case IcsIdle:
6858         return FALSE;
6859
6860       case MachinePlaysWhite:
6861       case IcsPlayingBlack:
6862         if (appData.zippyPlay) return FALSE;
6863         if (white_piece) {
6864             DisplayMoveError(_("You are playing Black"));
6865             return FALSE;
6866         }
6867         break;
6868
6869       case MachinePlaysBlack:
6870       case IcsPlayingWhite:
6871         if (appData.zippyPlay) return FALSE;
6872         if (!white_piece) {
6873             DisplayMoveError(_("You are playing White"));
6874             return FALSE;
6875         }
6876         break;
6877
6878       case PlayFromGameFile:
6879             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6880       case EditGame:
6881       case AnalyzeMode:
6882         if (!white_piece && WhiteOnMove(currentMove)) {
6883             DisplayMoveError(_("It is White's turn"));
6884             return FALSE;
6885         }
6886         if (white_piece && !WhiteOnMove(currentMove)) {
6887             DisplayMoveError(_("It is Black's turn"));
6888             return FALSE;
6889         }
6890         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6891             /* Editing correspondence game history */
6892             /* Could disallow this or prompt for confirmation */
6893             cmailOldMove = -1;
6894         }
6895         break;
6896
6897       case BeginningOfGame:
6898         if (appData.icsActive) return FALSE;
6899         if (!appData.noChessProgram) {
6900             if (!white_piece) {
6901                 DisplayMoveError(_("You are playing White"));
6902                 return FALSE;
6903             }
6904         }
6905         break;
6906
6907       case Training:
6908         if (!white_piece && WhiteOnMove(currentMove)) {
6909             DisplayMoveError(_("It is White's turn"));
6910             return FALSE;
6911         }
6912         if (white_piece && !WhiteOnMove(currentMove)) {
6913             DisplayMoveError(_("It is Black's turn"));
6914             return FALSE;
6915         }
6916         break;
6917
6918       default:
6919       case IcsExamining:
6920         break;
6921     }
6922     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6923         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6924         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6925         && gameMode != AnalyzeFile && gameMode != Training) {
6926         DisplayMoveError(_("Displayed position is not current"));
6927         return FALSE;
6928     }
6929     return TRUE;
6930 }
6931
6932 Boolean
6933 OnlyMove (int *x, int *y, Boolean captures)
6934 {
6935     DisambiguateClosure cl;
6936     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6937     switch(gameMode) {
6938       case MachinePlaysBlack:
6939       case IcsPlayingWhite:
6940       case BeginningOfGame:
6941         if(!WhiteOnMove(currentMove)) return FALSE;
6942         break;
6943       case MachinePlaysWhite:
6944       case IcsPlayingBlack:
6945         if(WhiteOnMove(currentMove)) return FALSE;
6946         break;
6947       case EditGame:
6948         break;
6949       default:
6950         return FALSE;
6951     }
6952     cl.pieceIn = EmptySquare;
6953     cl.rfIn = *y;
6954     cl.ffIn = *x;
6955     cl.rtIn = -1;
6956     cl.ftIn = -1;
6957     cl.promoCharIn = NULLCHAR;
6958     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6959     if( cl.kind == NormalMove ||
6960         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6961         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6962         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6963       fromX = cl.ff;
6964       fromY = cl.rf;
6965       *x = cl.ft;
6966       *y = cl.rt;
6967       return TRUE;
6968     }
6969     if(cl.kind != ImpossibleMove) return FALSE;
6970     cl.pieceIn = EmptySquare;
6971     cl.rfIn = -1;
6972     cl.ffIn = -1;
6973     cl.rtIn = *y;
6974     cl.ftIn = *x;
6975     cl.promoCharIn = NULLCHAR;
6976     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6977     if( cl.kind == NormalMove ||
6978         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6979         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6980         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6981       fromX = cl.ff;
6982       fromY = cl.rf;
6983       *x = cl.ft;
6984       *y = cl.rt;
6985       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6986       return TRUE;
6987     }
6988     return FALSE;
6989 }
6990
6991 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6992 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6993 int lastLoadGameUseList = FALSE;
6994 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6995 ChessMove lastLoadGameStart = EndOfFile;
6996 int doubleClick;
6997 Boolean addToBookFlag;
6998 static Board rightsBoard, nullBoard;
6999
7000 void
7001 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
7002 {
7003     ChessMove moveType;
7004     ChessSquare pup;
7005     int ff=fromX, rf=fromY, ft=toX, rt=toY;
7006
7007     /* Check if the user is playing in turn.  This is complicated because we
7008        let the user "pick up" a piece before it is his turn.  So the piece he
7009        tried to pick up may have been captured by the time he puts it down!
7010        Therefore we use the color the user is supposed to be playing in this
7011        test, not the color of the piece that is currently on the starting
7012        square---except in EditGame mode, where the user is playing both
7013        sides; fortunately there the capture race can't happen.  (It can
7014        now happen in IcsExamining mode, but that's just too bad.  The user
7015        will get a somewhat confusing message in that case.)
7016        */
7017
7018     switch (gameMode) {
7019       case AnalyzeFile:
7020       case TwoMachinesPlay:
7021       case EndOfGame:
7022       case IcsObserving:
7023       case IcsIdle:
7024         /* We switched into a game mode where moves are not accepted,
7025            perhaps while the mouse button was down. */
7026         return;
7027
7028       case MachinePlaysWhite:
7029         /* User is moving for Black */
7030         if (WhiteOnMove(currentMove)) {
7031             DisplayMoveError(_("It is White's turn"));
7032             return;
7033         }
7034         break;
7035
7036       case MachinePlaysBlack:
7037         /* User is moving for White */
7038         if (!WhiteOnMove(currentMove)) {
7039             DisplayMoveError(_("It is Black's turn"));
7040             return;
7041         }
7042         break;
7043
7044       case PlayFromGameFile:
7045             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7046       case EditGame:
7047       case IcsExamining:
7048       case BeginningOfGame:
7049       case AnalyzeMode:
7050       case Training:
7051         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7052         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7053             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7054             /* User is moving for Black */
7055             if (WhiteOnMove(currentMove)) {
7056                 DisplayMoveError(_("It is White's turn"));
7057                 return;
7058             }
7059         } else {
7060             /* User is moving for White */
7061             if (!WhiteOnMove(currentMove)) {
7062                 DisplayMoveError(_("It is Black's turn"));
7063                 return;
7064             }
7065         }
7066         break;
7067
7068       case IcsPlayingBlack:
7069         /* User is moving for Black */
7070         if (WhiteOnMove(currentMove)) {
7071             if (!appData.premove) {
7072                 DisplayMoveError(_("It is White's turn"));
7073             } else if (toX >= 0 && toY >= 0) {
7074                 premoveToX = toX;
7075                 premoveToY = toY;
7076                 premoveFromX = fromX;
7077                 premoveFromY = fromY;
7078                 premovePromoChar = promoChar;
7079                 gotPremove = 1;
7080                 if (appData.debugMode)
7081                     fprintf(debugFP, "Got premove: fromX %d,"
7082                             "fromY %d, toX %d, toY %d\n",
7083                             fromX, fromY, toX, toY);
7084             }
7085             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7086             return;
7087         }
7088         break;
7089
7090       case IcsPlayingWhite:
7091         /* User is moving for White */
7092         if (!WhiteOnMove(currentMove)) {
7093             if (!appData.premove) {
7094                 DisplayMoveError(_("It is Black's turn"));
7095             } else if (toX >= 0 && toY >= 0) {
7096                 premoveToX = toX;
7097                 premoveToY = toY;
7098                 premoveFromX = fromX;
7099                 premoveFromY = fromY;
7100                 premovePromoChar = promoChar;
7101                 gotPremove = 1;
7102                 if (appData.debugMode)
7103                     fprintf(debugFP, "Got premove: fromX %d,"
7104                             "fromY %d, toX %d, toY %d\n",
7105                             fromX, fromY, toX, toY);
7106             }
7107             DrawPosition(TRUE, boards[currentMove]);
7108             return;
7109         }
7110         break;
7111
7112       default:
7113         break;
7114
7115       case EditPosition:
7116         /* EditPosition, empty square, or different color piece;
7117            click-click move is possible */
7118         if (toX == -2 || toY == -2) {
7119             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7120             DrawPosition(FALSE, boards[currentMove]);
7121             return;
7122         } else if (toX >= 0 && toY >= 0) {
7123             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7124                 ChessSquare p = boards[0][rf][ff];
7125                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7126                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
7127                 if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
7128                     int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
7129                     DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
7130                     gatingPiece = p;
7131                 }
7132             } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
7133             boards[0][toY][toX] = boards[0][fromY][fromX];
7134             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7135                 if(boards[0][fromY][0] != EmptySquare) {
7136                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7137                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7138                 }
7139             } else
7140             if(fromX == BOARD_RGHT+1) {
7141                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7142                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7143                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7144                 }
7145             } else
7146             boards[0][fromY][fromX] = gatingPiece;
7147             ClearHighlights();
7148             DrawPosition(FALSE, boards[currentMove]);
7149             return;
7150         }
7151         return;
7152     }
7153
7154     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7155     pup = boards[currentMove][toY][toX];
7156
7157     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7158     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7159          if( pup != EmptySquare ) return;
7160          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7161            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7162                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7163            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7164            if(fromX == 0) fromY = handSize-1 - fromY; // black holdings upside-down
7165            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7166            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7167          fromY = DROP_RANK;
7168     }
7169
7170     /* [HGM] always test for legality, to get promotion info */
7171     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7172                                          fromY, fromX, toY, toX, promoChar);
7173
7174     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7175
7176     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7177
7178     /* [HGM] but possibly ignore an IllegalMove result */
7179     if (appData.testLegality) {
7180         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7181             DisplayMoveError(_("Illegal move"));
7182             return;
7183         }
7184     }
7185
7186     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7187         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7188              ClearPremoveHighlights(); // was included
7189         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7190         DrawPosition(FALSE, NULL);
7191         return;
7192     }
7193
7194     if(addToBookFlag) { // adding moves to book
7195         char buf[MSG_SIZ], move[MSG_SIZ];
7196         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7197         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7198                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7199         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7200         AddBookMove(buf);
7201         addToBookFlag = FALSE;
7202         ClearHighlights();
7203         return;
7204     }
7205
7206     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7207 }
7208
7209 /* Common tail of UserMoveEvent and DropMenuEvent */
7210 int
7211 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7212 {
7213     char *bookHit = 0;
7214
7215     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7216         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7217         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7218         if(WhiteOnMove(currentMove)) {
7219             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7220         } else {
7221             if(!boards[currentMove][handSize-1-k][1]) return 0;
7222         }
7223     }
7224
7225     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7226        move type in caller when we know the move is a legal promotion */
7227     if(moveType == NormalMove && promoChar)
7228         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7229
7230     /* [HGM] <popupFix> The following if has been moved here from
7231        UserMoveEvent(). Because it seemed to belong here (why not allow
7232        piece drops in training games?), and because it can only be
7233        performed after it is known to what we promote. */
7234     if (gameMode == Training) {
7235       /* compare the move played on the board to the next move in the
7236        * game. If they match, display the move and the opponent's response.
7237        * If they don't match, display an error message.
7238        */
7239       int saveAnimate;
7240       Board testBoard;
7241       CopyBoard(testBoard, boards[currentMove]);
7242       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7243
7244       if (CompareBoards(testBoard, boards[currentMove+1])) {
7245         ForwardInner(currentMove+1);
7246
7247         /* Autoplay the opponent's response.
7248          * if appData.animate was TRUE when Training mode was entered,
7249          * the response will be animated.
7250          */
7251         saveAnimate = appData.animate;
7252         appData.animate = animateTraining;
7253         ForwardInner(currentMove+1);
7254         appData.animate = saveAnimate;
7255
7256         /* check for the end of the game */
7257         if (currentMove >= forwardMostMove) {
7258           gameMode = PlayFromGameFile;
7259           ModeHighlight();
7260           SetTrainingModeOff();
7261           DisplayInformation(_("End of game"));
7262         }
7263       } else {
7264         DisplayError(_("Incorrect move"), 0);
7265       }
7266       return 1;
7267     }
7268
7269   /* Ok, now we know that the move is good, so we can kill
7270      the previous line in Analysis Mode */
7271   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7272                                 && currentMove < forwardMostMove) {
7273     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7274     else forwardMostMove = currentMove;
7275   }
7276
7277   ClearMap();
7278
7279   /* If we need the chess program but it's dead, restart it */
7280   ResurrectChessProgram();
7281
7282   /* A user move restarts a paused game*/
7283   if (pausing)
7284     PauseEvent();
7285
7286   thinkOutput[0] = NULLCHAR;
7287
7288   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7289
7290   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7291     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7292     return 1;
7293   }
7294
7295   if (gameMode == BeginningOfGame) {
7296     if (appData.noChessProgram) {
7297       gameMode = EditGame;
7298       SetGameInfo();
7299     } else {
7300       char buf[MSG_SIZ];
7301       gameMode = MachinePlaysBlack;
7302       StartClocks();
7303       SetGameInfo();
7304       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7305       DisplayTitle(buf);
7306       if (first.sendName) {
7307         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7308         SendToProgram(buf, &first);
7309       }
7310       StartClocks();
7311     }
7312     ModeHighlight();
7313   }
7314
7315   /* Relay move to ICS or chess engine */
7316   if (appData.icsActive) {
7317     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7318         gameMode == IcsExamining) {
7319       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7320         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7321         SendToICS("draw ");
7322         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7323       }
7324       // also send plain move, in case ICS does not understand atomic claims
7325       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7326       ics_user_moved = 1;
7327     }
7328   } else {
7329     if (first.sendTime && (gameMode == BeginningOfGame ||
7330                            gameMode == MachinePlaysWhite ||
7331                            gameMode == MachinePlaysBlack)) {
7332       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7333     }
7334     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7335          // [HGM] book: if program might be playing, let it use book
7336         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7337         first.maybeThinking = TRUE;
7338     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7339         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7340         SendBoard(&first, currentMove+1);
7341         if(second.analyzing) {
7342             if(!second.useSetboard) SendToProgram("undo\n", &second);
7343             SendBoard(&second, currentMove+1);
7344         }
7345     } else {
7346         SendMoveToProgram(forwardMostMove-1, &first);
7347         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7348     }
7349     if (currentMove == cmailOldMove + 1) {
7350       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7351     }
7352   }
7353
7354   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7355
7356   switch (gameMode) {
7357   case EditGame:
7358     if(appData.testLegality)
7359     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7360     case MT_NONE:
7361     case MT_CHECK:
7362       break;
7363     case MT_CHECKMATE:
7364     case MT_STAINMATE:
7365       if (WhiteOnMove(currentMove)) {
7366         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7367       } else {
7368         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7369       }
7370       break;
7371     case MT_STALEMATE:
7372       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7373       break;
7374     }
7375     break;
7376
7377   case MachinePlaysBlack:
7378   case MachinePlaysWhite:
7379     /* disable certain menu options while machine is thinking */
7380     SetMachineThinkingEnables();
7381     break;
7382
7383   default:
7384     break;
7385   }
7386
7387   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7388   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7389
7390   if(bookHit) { // [HGM] book: simulate book reply
7391         static char bookMove[MSG_SIZ]; // a bit generous?
7392
7393         programStats.nodes = programStats.depth = programStats.time =
7394         programStats.score = programStats.got_only_move = 0;
7395         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7396
7397         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7398         strcat(bookMove, bookHit);
7399         HandleMachineMove(bookMove, &first);
7400   }
7401   return 1;
7402 }
7403
7404 void
7405 MarkByFEN(char *fen)
7406 {
7407         int r, f;
7408         if(!appData.markers || !appData.highlightDragging) return;
7409         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
7410         r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
7411         while(*fen) {
7412             int s = 0;
7413             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7414             if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
7415             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
7416             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7417             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7418             if(*fen == 'T') marker[r][f++] = 0; else
7419             if(*fen == 'Y') marker[r][f++] = 1; else
7420             if(*fen == 'G') marker[r][f++] = 3; else
7421             if(*fen == 'B') marker[r][f++] = 4; else
7422             if(*fen == 'C') marker[r][f++] = 5; else
7423             if(*fen == 'M') marker[r][f++] = 6; else
7424             if(*fen == 'W') marker[r][f++] = 7; else
7425             if(*fen == 'D') marker[r][f++] = 8; else
7426             if(*fen == 'R') marker[r][f++] = 2; else {
7427                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7428               f += s; fen -= s>0;
7429             }
7430             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7431             if(r < 0) break;
7432             fen++;
7433         }
7434         DrawPosition(TRUE, NULL);
7435 }
7436
7437 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7438
7439 void
7440 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7441 {
7442     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7443     Markers *m = (Markers *) closure;
7444     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7445                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7446         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7447                          || kind == WhiteCapturesEnPassant
7448                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7449     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7450 }
7451
7452 static int hoverSavedValid;
7453
7454 void
7455 MarkTargetSquares (int clear)
7456 {
7457   int x, y, sum=0;
7458   if(clear) { // no reason to ever suppress clearing
7459     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7460     hoverSavedValid = 0;
7461     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7462   } else {
7463     int capt = 0;
7464     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7465        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7466     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7467     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7468       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7469       if(capt)
7470       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = legal[y][x] = 0;
7471     }
7472   }
7473   DrawPosition(FALSE, NULL);
7474 }
7475
7476 int
7477 Explode (Board board, int fromX, int fromY, int toX, int toY)
7478 {
7479     if(gameInfo.variant == VariantAtomic &&
7480        (board[toY][toX] != EmptySquare ||                     // capture?
7481         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7482                          board[fromY][fromX] == BlackPawn   )
7483       )) {
7484         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7485         return TRUE;
7486     }
7487     return FALSE;
7488 }
7489
7490 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7491
7492 int
7493 CanPromote (ChessSquare piece, int y)
7494 {
7495         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7496         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7497         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7498         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7499            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7500           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7501            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7502         return (piece == BlackPawn && y <= zone ||
7503                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7504                 piece == BlackLance && y <= zone ||
7505                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7506 }
7507
7508 void
7509 HoverEvent (int xPix, int yPix, int x, int y)
7510 {
7511         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7512         int r, f;
7513         if(!first.highlight) return;
7514         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7515         if(x == oldX && y == oldY) return; // only do something if we enter new square
7516         oldFromX = fromX; oldFromY = fromY;
7517         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7518           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7519             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7520           hoverSavedValid = 1;
7521         } else if(oldX != x || oldY != y) {
7522           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7523           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7524           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7525             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7526           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7527             char buf[MSG_SIZ];
7528             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7529             SendToProgram(buf, &first);
7530           }
7531           oldX = x; oldY = y;
7532 //        SetHighlights(fromX, fromY, x, y);
7533         }
7534 }
7535
7536 void ReportClick(char *action, int x, int y)
7537 {
7538         char buf[MSG_SIZ]; // Inform engine of what user does
7539         int r, f;
7540         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7541           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7542             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7543         if(!first.highlight || gameMode == EditPosition) return;
7544         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7545         SendToProgram(buf, &first);
7546 }
7547
7548 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7549 Boolean deferChoice;
7550
7551 void
7552 LeftClick (ClickType clickType, int xPix, int yPix)
7553 {
7554     int x, y;
7555     static Boolean saveAnimate;
7556     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7557     char promoChoice = NULLCHAR;
7558     ChessSquare piece;
7559     static TimeMark lastClickTime, prevClickTime;
7560
7561     if(flashing) return;
7562
7563   if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
7564     x = EventToSquare(xPix, BOARD_WIDTH);
7565     y = EventToSquare(yPix, BOARD_HEIGHT);
7566     if (!flipView && y >= 0) {
7567         y = BOARD_HEIGHT - 1 - y;
7568     }
7569     if (flipView && x >= 0) {
7570         x = BOARD_WIDTH - 1 - x;
7571     }
7572
7573     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7574         static int dummy;
7575         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7576         right = TRUE;
7577         return;
7578     }
7579
7580     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7581
7582     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7583
7584     if (clickType == Press) ErrorPopDown();
7585     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7586
7587     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7588         defaultPromoChoice = promoSweep;
7589         promoSweep = EmptySquare;   // terminate sweep
7590         promoDefaultAltered = TRUE;
7591         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7592     }
7593
7594     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7595         if(clickType == Release) return; // ignore upclick of click-click destination
7596         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7597         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7598         if(gameInfo.holdingsWidth &&
7599                 (WhiteOnMove(currentMove)
7600                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7601                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7602             // click in right holdings, for determining promotion piece
7603             ChessSquare p = boards[currentMove][y][x];
7604             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7605             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7606             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7607                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7608                 fromX = fromY = -1;
7609                 return;
7610             }
7611         }
7612         DrawPosition(FALSE, boards[currentMove]);
7613         return;
7614     }
7615
7616     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7617     if(clickType == Press
7618             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7619               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7620               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7621         return;
7622
7623     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7624         // could be static click on premove from-square: abort premove
7625         gotPremove = 0;
7626         ClearPremoveHighlights();
7627     }
7628
7629     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7630         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7631
7632     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7633         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7634                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7635         defaultPromoChoice = DefaultPromoChoice(side);
7636     }
7637
7638     autoQueen = appData.alwaysPromoteToQueen;
7639
7640     if (fromX == -1) {
7641       int originalY = y;
7642       gatingPiece = EmptySquare;
7643       if (clickType != Press) {
7644         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7645             DragPieceEnd(xPix, yPix); dragging = 0;
7646             DrawPosition(FALSE, NULL);
7647         }
7648         return;
7649       }
7650       doubleClick = FALSE;
7651       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7652         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7653       }
7654       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7655       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7656          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7657          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7658             /* First square */
7659             if (OKToStartUserMove(fromX, fromY)) {
7660                 second = 0;
7661                 ReportClick("lift", x, y);
7662                 MarkTargetSquares(0);
7663                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7664                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7665                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7666                     promoSweep = defaultPromoChoice;
7667                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7668                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7669                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7670                 }
7671                 if (appData.highlightDragging) {
7672                     SetHighlights(fromX, fromY, -1, -1);
7673                 } else {
7674                     ClearHighlights();
7675                 }
7676             } else fromX = fromY = -1;
7677             return;
7678         }
7679     }
7680
7681     /* fromX != -1 */
7682     if (clickType == Press && gameMode != EditPosition) {
7683         ChessSquare fromP;
7684         ChessSquare toP;
7685         int frc;
7686
7687         // ignore off-board to clicks
7688         if(y < 0 || x < 0) return;
7689
7690         /* Check if clicking again on the same color piece */
7691         fromP = boards[currentMove][fromY][fromX];
7692         toP = boards[currentMove][y][x];
7693         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7694         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7695             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7696            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7697              WhitePawn <= toP && toP <= WhiteKing &&
7698              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7699              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7700             (BlackPawn <= fromP && fromP <= BlackKing &&
7701              BlackPawn <= toP && toP <= BlackKing &&
7702              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7703              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7704             /* Clicked again on same color piece -- changed his mind */
7705             second = (x == fromX && y == fromY);
7706             killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
7707             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7708                 second = FALSE; // first double-click rather than scond click
7709                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7710             }
7711             promoDefaultAltered = FALSE;
7712            if(!second) MarkTargetSquares(1);
7713            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7714             if (appData.highlightDragging) {
7715                 SetHighlights(x, y, -1, -1);
7716             } else {
7717                 ClearHighlights();
7718             }
7719             if (OKToStartUserMove(x, y)) {
7720                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7721                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7722                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7723                  gatingPiece = boards[currentMove][fromY][fromX];
7724                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7725                 fromX = x;
7726                 fromY = y; dragging = 1;
7727                 if(!second) ReportClick("lift", x, y);
7728                 MarkTargetSquares(0);
7729                 DragPieceBegin(xPix, yPix, FALSE);
7730                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7731                     promoSweep = defaultPromoChoice;
7732                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7733                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7734                 }
7735             }
7736            }
7737            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7738            second = FALSE;
7739         }
7740         // ignore clicks on holdings
7741         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7742     }
7743
7744     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7745         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7746         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7747         return;
7748     }
7749
7750     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7751         DragPieceEnd(xPix, yPix); dragging = 0;
7752         if(clearFlag) {
7753             // a deferred attempt to click-click move an empty square on top of a piece
7754             boards[currentMove][y][x] = EmptySquare;
7755             ClearHighlights();
7756             DrawPosition(FALSE, boards[currentMove]);
7757             fromX = fromY = -1; clearFlag = 0;
7758             return;
7759         }
7760         if (appData.animateDragging) {
7761             /* Undo animation damage if any */
7762             DrawPosition(FALSE, NULL);
7763         }
7764         if (second) {
7765             /* Second up/down in same square; just abort move */
7766             second = 0;
7767             fromX = fromY = -1;
7768             gatingPiece = EmptySquare;
7769             ClearHighlights();
7770             gotPremove = 0;
7771             ClearPremoveHighlights();
7772             MarkTargetSquares(-1);
7773             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7774         } else {
7775             /* First upclick in same square; start click-click mode */
7776             SetHighlights(x, y, -1, -1);
7777         }
7778         return;
7779     }
7780
7781     clearFlag = 0;
7782
7783     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7784        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7785         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7786         DisplayMessage(_("only marked squares are legal"),"");
7787         DrawPosition(TRUE, NULL);
7788         return; // ignore to-click
7789     }
7790
7791     /* we now have a different from- and (possibly off-board) to-square */
7792     /* Completed move */
7793     if(!sweepSelecting) {
7794         toX = x;
7795         toY = y;
7796     }
7797
7798     piece = boards[currentMove][fromY][fromX];
7799
7800     saveAnimate = appData.animate;
7801     if (clickType == Press) {
7802         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7803         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7804             // must be Edit Position mode with empty-square selected
7805             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7806             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7807             return;
7808         }
7809         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7810             return;
7811         }
7812         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7813             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7814         } else
7815         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7816         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7817           if(appData.sweepSelect) {
7818             promoSweep = defaultPromoChoice;
7819             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7820             selectFlag = 0; lastX = xPix; lastY = yPix;
7821             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7822             saveFlash = appData.flashCount; appData.flashCount = 0;
7823             Sweep(0); // Pawn that is going to promote: preview promotion piece
7824             sweepSelecting = 1;
7825             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7826             MarkTargetSquares(1);
7827           }
7828           return; // promo popup appears on up-click
7829         }
7830         /* Finish clickclick move */
7831         if (appData.animate || appData.highlightLastMove) {
7832             SetHighlights(fromX, fromY, toX, toY);
7833         } else {
7834             ClearHighlights();
7835         }
7836         MarkTargetSquares(1);
7837     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7838         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7839         *promoRestrict = 0; appData.flashCount = saveFlash;
7840         if (appData.animate || appData.highlightLastMove) {
7841             SetHighlights(fromX, fromY, toX, toY);
7842         } else {
7843             ClearHighlights();
7844         }
7845         MarkTargetSquares(1);
7846     } else {
7847 #if 0
7848 // [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
7849         /* Finish drag move */
7850         if (appData.highlightLastMove) {
7851             SetHighlights(fromX, fromY, toX, toY);
7852         } else {
7853             ClearHighlights();
7854         }
7855 #endif
7856         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7857           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7858         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7859         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7860           dragging *= 2;            // flag button-less dragging if we are dragging
7861           MarkTargetSquares(1);
7862           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7863           else {
7864             kill2X = killX; kill2Y = killY;
7865             killX = x; killY = y;     // remember this square as intermediate
7866             ReportClick("put", x, y); // and inform engine
7867             ReportClick("lift", x, y);
7868             MarkTargetSquares(0);
7869             return;
7870           }
7871         }
7872         DragPieceEnd(xPix, yPix); dragging = 0;
7873         /* Don't animate move and drag both */
7874         appData.animate = FALSE;
7875         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7876     }
7877
7878     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7879     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7880         ChessSquare piece = boards[currentMove][fromY][fromX];
7881         if(gameMode == EditPosition && piece != EmptySquare &&
7882            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7883             int n;
7884
7885             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7886                 n = PieceToNumber(piece - (int)BlackPawn);
7887                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7888                 boards[currentMove][handSize-1 - n][0] = piece;
7889                 boards[currentMove][handSize-1 - n][1]++;
7890             } else
7891             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7892                 n = PieceToNumber(piece);
7893                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7894                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7895                 boards[currentMove][n][BOARD_WIDTH-2]++;
7896             }
7897             boards[currentMove][fromY][fromX] = EmptySquare;
7898         }
7899         ClearHighlights();
7900         fromX = fromY = -1;
7901         MarkTargetSquares(1);
7902         DrawPosition(TRUE, boards[currentMove]);
7903         return;
7904     }
7905
7906     // off-board moves should not be highlighted
7907     if(x < 0 || y < 0) {
7908         ClearHighlights();
7909         DrawPosition(FALSE, NULL);
7910     } else ReportClick("put", x, y);
7911
7912     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7913  }
7914
7915     if(legal[toY][toX] == 2) { // highlight-induced promotion
7916         if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
7917         else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
7918     } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
7919       if(!*promoRestrict) {           // but has not done that yet
7920         deferChoice = TRUE;           // set up retry for when it does
7921         return;                       // and wait for that
7922       }
7923       promoChoice = ToLower(*promoRestrict); // force engine's choice
7924       deferChoice = FALSE;
7925     }
7926
7927     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7928         SetHighlights(fromX, fromY, toX, toY);
7929         MarkTargetSquares(1);
7930         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7931             // [HGM] super: promotion to captured piece selected from holdings
7932             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7933             promotionChoice = TRUE;
7934             // kludge follows to temporarily execute move on display, without promoting yet
7935             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7936             boards[currentMove][toY][toX] = p;
7937             DrawPosition(FALSE, boards[currentMove]);
7938             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7939             boards[currentMove][toY][toX] = q;
7940             DisplayMessage("Click in holdings to choose piece", "");
7941             return;
7942         }
7943         DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
7944         PromotionPopUp(promoChoice);
7945     } else {
7946         int oldMove = currentMove;
7947         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7948         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7949         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7950         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
7951         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7952            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7953             DrawPosition(TRUE, boards[currentMove]);
7954         else DrawPosition(FALSE, NULL);
7955         fromX = fromY = -1;
7956         flashing = 0;
7957     }
7958     appData.animate = saveAnimate;
7959     if (appData.animate || appData.animateDragging) {
7960         /* Undo animation damage if needed */
7961 //      DrawPosition(FALSE, NULL);
7962     }
7963 }
7964
7965 int
7966 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7967 {   // front-end-free part taken out of PieceMenuPopup
7968     int whichMenu; int xSqr, ySqr;
7969
7970     if(seekGraphUp) { // [HGM] seekgraph
7971         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7972         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7973         return -2;
7974     }
7975
7976     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7977          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7978         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7979         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7980         if(action == Press)   {
7981             originalFlip = flipView;
7982             flipView = !flipView; // temporarily flip board to see game from partners perspective
7983             DrawPosition(TRUE, partnerBoard);
7984             DisplayMessage(partnerStatus, "");
7985             partnerUp = TRUE;
7986         } else if(action == Release) {
7987             flipView = originalFlip;
7988             DrawPosition(TRUE, boards[currentMove]);
7989             partnerUp = FALSE;
7990         }
7991         return -2;
7992     }
7993
7994     xSqr = EventToSquare(x, BOARD_WIDTH);
7995     ySqr = EventToSquare(y, BOARD_HEIGHT);
7996     if (action == Release) {
7997         if(pieceSweep != EmptySquare) {
7998             EditPositionMenuEvent(pieceSweep, toX, toY);
7999             pieceSweep = EmptySquare;
8000         } else UnLoadPV(); // [HGM] pv
8001     }
8002     if (action != Press) return -2; // return code to be ignored
8003     switch (gameMode) {
8004       case IcsExamining:
8005         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
8006       case EditPosition:
8007         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
8008         if (xSqr < 0 || ySqr < 0) return -1;
8009         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
8010         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
8011         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
8012         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
8013         NextPiece(0);
8014         return 2; // grab
8015       case IcsObserving:
8016         if(!appData.icsEngineAnalyze) return -1;
8017       case IcsPlayingWhite:
8018       case IcsPlayingBlack:
8019         if(!appData.zippyPlay) goto noZip;
8020       case AnalyzeMode:
8021       case AnalyzeFile:
8022       case MachinePlaysWhite:
8023       case MachinePlaysBlack:
8024       case TwoMachinesPlay: // [HGM] pv: use for showing PV
8025         if (!appData.dropMenu) {
8026           LoadPV(x, y);
8027           return 2; // flag front-end to grab mouse events
8028         }
8029         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
8030            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
8031       case EditGame:
8032       noZip:
8033         if (xSqr < 0 || ySqr < 0) return -1;
8034         if (!appData.dropMenu || appData.testLegality &&
8035             gameInfo.variant != VariantBughouse &&
8036             gameInfo.variant != VariantCrazyhouse) return -1;
8037         whichMenu = 1; // drop menu
8038         break;
8039       default:
8040         return -1;
8041     }
8042
8043     if (((*fromX = xSqr) < 0) ||
8044         ((*fromY = ySqr) < 0)) {
8045         *fromX = *fromY = -1;
8046         return -1;
8047     }
8048     if (flipView)
8049       *fromX = BOARD_WIDTH - 1 - *fromX;
8050     else
8051       *fromY = BOARD_HEIGHT - 1 - *fromY;
8052
8053     return whichMenu;
8054 }
8055
8056 void
8057 Wheel (int dir, int x, int y)
8058 {
8059     if(gameMode == EditPosition) {
8060         int xSqr = EventToSquare(x, BOARD_WIDTH);
8061         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8062         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8063         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8064         do {
8065             boards[currentMove][ySqr][xSqr] += dir;
8066             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8067             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8068         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8069         DrawPosition(FALSE, boards[currentMove]);
8070     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8071 }
8072
8073 void
8074 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8075 {
8076 //    char * hint = lastHint;
8077     FrontEndProgramStats stats;
8078
8079     stats.which = cps == &first ? 0 : 1;
8080     stats.depth = cpstats->depth;
8081     stats.nodes = cpstats->nodes;
8082     stats.score = cpstats->score;
8083     stats.time = cpstats->time;
8084     stats.pv = cpstats->movelist;
8085     stats.hint = lastHint;
8086     stats.an_move_index = 0;
8087     stats.an_move_count = 0;
8088
8089     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8090         stats.hint = cpstats->move_name;
8091         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8092         stats.an_move_count = cpstats->nr_moves;
8093     }
8094
8095     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
8096
8097     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8098         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8099
8100     SetProgramStats( &stats );
8101 }
8102
8103 void
8104 ClearEngineOutputPane (int which)
8105 {
8106     static FrontEndProgramStats dummyStats;
8107     dummyStats.which = which;
8108     dummyStats.pv = "#";
8109     SetProgramStats( &dummyStats );
8110 }
8111
8112 #define MAXPLAYERS 500
8113
8114 char *
8115 TourneyStandings (int display)
8116 {
8117     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8118     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8119     char result, *p, *names[MAXPLAYERS];
8120
8121     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8122         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8123     names[0] = p = strdup(appData.participants);
8124     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8125
8126     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8127
8128     while(result = appData.results[nr]) {
8129         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8130         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8131         wScore = bScore = 0;
8132         switch(result) {
8133           case '+': wScore = 2; break;
8134           case '-': bScore = 2; break;
8135           case '=': wScore = bScore = 1; break;
8136           case ' ':
8137           case '*': return strdup("busy"); // tourney not finished
8138         }
8139         score[w] += wScore;
8140         score[b] += bScore;
8141         games[w]++;
8142         games[b]++;
8143         nr++;
8144     }
8145     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8146     for(w=0; w<nPlayers; w++) {
8147         bScore = -1;
8148         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8149         ranking[w] = b; points[w] = bScore; score[b] = -2;
8150     }
8151     p = malloc(nPlayers*34+1);
8152     for(w=0; w<nPlayers && w<display; w++)
8153         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8154     free(names[0]);
8155     return p;
8156 }
8157
8158 void
8159 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8160 {       // count all piece types
8161         int p, f, r;
8162         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8163         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8164         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8165                 p = board[r][f];
8166                 pCnt[p]++;
8167                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8168                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8169                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8170                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8171                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8172                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8173         }
8174 }
8175
8176 int
8177 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8178 {
8179         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8180         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8181
8182         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8183         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8184         if(myPawns == 2 && nMine == 3) // KPP
8185             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8186         if(myPawns == 1 && nMine == 2) // KP
8187             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8188         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8189             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8190         if(myPawns) return FALSE;
8191         if(pCnt[WhiteRook+side])
8192             return pCnt[BlackRook-side] ||
8193                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8194                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8195                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8196         if(pCnt[WhiteCannon+side]) {
8197             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8198             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8199         }
8200         if(pCnt[WhiteKnight+side])
8201             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8202         return FALSE;
8203 }
8204
8205 int
8206 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8207 {
8208         VariantClass v = gameInfo.variant;
8209
8210         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8211         if(v == VariantShatranj) return TRUE; // always winnable through baring
8212         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8213         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8214
8215         if(v == VariantXiangqi) {
8216                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8217
8218                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8219                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8220                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8221                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8222                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8223                 if(stale) // we have at least one last-rank P plus perhaps C
8224                     return majors // KPKX
8225                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8226                 else // KCA*E*
8227                     return pCnt[WhiteFerz+side] // KCAK
8228                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8229                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8230                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8231
8232         } else if(v == VariantKnightmate) {
8233                 if(nMine == 1) return FALSE;
8234                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8235         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8236                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8237
8238                 if(nMine == 1) return FALSE; // bare King
8239                 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
8240                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8241                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8242                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8243                 if(pCnt[WhiteKnight+side])
8244                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8245                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8246                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8247                 if(nBishops)
8248                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8249                 if(pCnt[WhiteAlfil+side])
8250                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8251                 if(pCnt[WhiteWazir+side])
8252                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8253         }
8254
8255         return TRUE;
8256 }
8257
8258 int
8259 CompareWithRights (Board b1, Board b2)
8260 {
8261     int rights = 0;
8262     if(!CompareBoards(b1, b2)) return FALSE;
8263     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8264     /* compare castling rights */
8265     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8266            rights++; /* King lost rights, while rook still had them */
8267     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8268         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8269            rights++; /* but at least one rook lost them */
8270     }
8271     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8272            rights++;
8273     if( b1[CASTLING][5] != NoRights ) {
8274         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8275            rights++;
8276     }
8277     return rights == 0;
8278 }
8279
8280 int
8281 Adjudicate (ChessProgramState *cps)
8282 {       // [HGM] some adjudications useful with buggy engines
8283         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8284         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8285         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8286         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8287         int k, drop, count = 0; static int bare = 1;
8288         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8289         Boolean canAdjudicate = !appData.icsActive;
8290
8291         // most tests only when we understand the game, i.e. legality-checking on
8292             if( appData.testLegality )
8293             {   /* [HGM] Some more adjudications for obstinate engines */
8294                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8295                 static int moveCount = 6;
8296                 ChessMove result;
8297                 char *reason = NULL;
8298
8299                 /* Count what is on board. */
8300                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8301
8302                 /* Some material-based adjudications that have to be made before stalemate test */
8303                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8304                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8305                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8306                      if(canAdjudicate && appData.checkMates) {
8307                          if(engineOpponent)
8308                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8309                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8310                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8311                          return 1;
8312                      }
8313                 }
8314
8315                 /* Bare King in Shatranj (loses) or Losers (wins) */
8316                 if( nrW == 1 || nrB == 1) {
8317                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8318                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8319                      if(canAdjudicate && appData.checkMates) {
8320                          if(engineOpponent)
8321                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8322                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8323                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8324                          return 1;
8325                      }
8326                   } else
8327                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8328                   {    /* bare King */
8329                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8330                         if(canAdjudicate && appData.checkMates) {
8331                             /* but only adjudicate if adjudication enabled */
8332                             if(engineOpponent)
8333                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8334                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8335                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8336                             return 1;
8337                         }
8338                   }
8339                 } else bare = 1;
8340
8341
8342             // don't wait for engine to announce game end if we can judge ourselves
8343             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8344               case MT_CHECK:
8345                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8346                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8347                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8348                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8349                             checkCnt++;
8350                         if(checkCnt >= 2) {
8351                             reason = "Xboard adjudication: 3rd check";
8352                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8353                             break;
8354                         }
8355                     }
8356                 }
8357               case MT_NONE:
8358               default:
8359                 break;
8360               case MT_STEALMATE:
8361               case MT_STALEMATE:
8362               case MT_STAINMATE:
8363                 reason = "Xboard adjudication: Stalemate";
8364                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8365                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8366                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8367                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8368                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8369                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8370                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8371                                                                         EP_CHECKMATE : EP_WINS);
8372                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8373                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8374                 }
8375                 break;
8376               case MT_CHECKMATE:
8377                 reason = "Xboard adjudication: Checkmate";
8378                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8379                 if(gameInfo.variant == VariantShogi) {
8380                     if(forwardMostMove > backwardMostMove
8381                        && moveList[forwardMostMove-1][1] == '@'
8382                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8383                         reason = "XBoard adjudication: pawn-drop mate";
8384                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8385                     }
8386                 }
8387                 break;
8388             }
8389
8390                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8391                     case EP_STALEMATE:
8392                         result = GameIsDrawn; break;
8393                     case EP_CHECKMATE:
8394                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8395                     case EP_WINS:
8396                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8397                     default:
8398                         result = EndOfFile;
8399                 }
8400                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8401                     if(engineOpponent)
8402                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8403                     GameEnds( result, reason, GE_XBOARD );
8404                     return 1;
8405                 }
8406
8407                 /* Next absolutely insufficient mating material. */
8408                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8409                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8410                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8411
8412                      /* always flag draws, for judging claims */
8413                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8414
8415                      if(canAdjudicate && appData.materialDraws) {
8416                          /* but only adjudicate them if adjudication enabled */
8417                          if(engineOpponent) {
8418                            SendToProgram("force\n", engineOpponent); // suppress reply
8419                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8420                          }
8421                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8422                          return 1;
8423                      }
8424                 }
8425
8426                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8427                 if(gameInfo.variant == VariantXiangqi ?
8428                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8429                  : nrW + nrB == 4 &&
8430                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8431                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8432                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8433                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8434                    ) ) {
8435                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8436                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8437                           if(engineOpponent) {
8438                             SendToProgram("force\n", engineOpponent); // suppress reply
8439                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8440                           }
8441                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8442                           return 1;
8443                      }
8444                 } else moveCount = 6;
8445             }
8446
8447         // Repetition draws and 50-move rule can be applied independently of legality testing
8448
8449                 /* Check for rep-draws */
8450                 count = 0;
8451                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8452                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8453                 for(k = forwardMostMove-2;
8454                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8455                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8456                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8457                     k-=2)
8458                 {   int rights=0;
8459                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8460                         /* compare castling rights */
8461                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8462                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8463                                 rights++; /* King lost rights, while rook still had them */
8464                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8465                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8466                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8467                                    rights++; /* but at least one rook lost them */
8468                         }
8469                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8470                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8471                                 rights++;
8472                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8473                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8474                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8475                                    rights++;
8476                         }
8477                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8478                             && appData.drawRepeats > 1) {
8479                              /* adjudicate after user-specified nr of repeats */
8480                              int result = GameIsDrawn;
8481                              char *details = "XBoard adjudication: repetition draw";
8482                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8483                                 // [HGM] xiangqi: check for forbidden perpetuals
8484                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8485                                 for(m=forwardMostMove; m>k; m-=2) {
8486                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8487                                         ourPerpetual = 0; // the current mover did not always check
8488                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8489                                         hisPerpetual = 0; // the opponent did not always check
8490                                 }
8491                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8492                                                                         ourPerpetual, hisPerpetual);
8493                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8494                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8495                                     details = "Xboard adjudication: perpetual checking";
8496                                 } else
8497                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8498                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8499                                 } else
8500                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8501                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8502                                         result = BlackWins;
8503                                         details = "Xboard adjudication: repetition";
8504                                     }
8505                                 } else // it must be XQ
8506                                 // Now check for perpetual chases
8507                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8508                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8509                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8510                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8511                                         static char resdet[MSG_SIZ];
8512                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8513                                         details = resdet;
8514                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8515                                     } else
8516                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8517                                         break; // Abort repetition-checking loop.
8518                                 }
8519                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8520                              }
8521                              if(engineOpponent) {
8522                                SendToProgram("force\n", engineOpponent); // suppress reply
8523                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8524                              }
8525                              GameEnds( result, details, GE_XBOARD );
8526                              return 1;
8527                         }
8528                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8529                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8530                     }
8531                 }
8532
8533                 /* Now we test for 50-move draws. Determine ply count */
8534                 count = forwardMostMove;
8535                 /* look for last irreversble move */
8536                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8537                     count--;
8538                 /* if we hit starting position, add initial plies */
8539                 if( count == backwardMostMove )
8540                     count -= initialRulePlies;
8541                 count = forwardMostMove - count;
8542                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8543                         // adjust reversible move counter for checks in Xiangqi
8544                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8545                         if(i < backwardMostMove) i = backwardMostMove;
8546                         while(i <= forwardMostMove) {
8547                                 lastCheck = inCheck; // check evasion does not count
8548                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8549                                 if(inCheck || lastCheck) count--; // check does not count
8550                                 i++;
8551                         }
8552                 }
8553                 if( count >= 100)
8554                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8555                          /* this is used to judge if draw claims are legal */
8556                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8557                          if(engineOpponent) {
8558                            SendToProgram("force\n", engineOpponent); // suppress reply
8559                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8560                          }
8561                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8562                          return 1;
8563                 }
8564
8565                 /* if draw offer is pending, treat it as a draw claim
8566                  * when draw condition present, to allow engines a way to
8567                  * claim draws before making their move to avoid a race
8568                  * condition occurring after their move
8569                  */
8570                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8571                          char *p = NULL;
8572                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8573                              p = "Draw claim: 50-move rule";
8574                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8575                              p = "Draw claim: 3-fold repetition";
8576                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8577                              p = "Draw claim: insufficient mating material";
8578                          if( p != NULL && canAdjudicate) {
8579                              if(engineOpponent) {
8580                                SendToProgram("force\n", engineOpponent); // suppress reply
8581                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8582                              }
8583                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8584                              return 1;
8585                          }
8586                 }
8587
8588                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8589                     if(engineOpponent) {
8590                       SendToProgram("force\n", engineOpponent); // suppress reply
8591                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8592                     }
8593                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8594                     return 1;
8595                 }
8596         return 0;
8597 }
8598
8599 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8600 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8601 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8602
8603 static int
8604 BitbaseProbe ()
8605 {
8606     int pieces[10], squares[10], cnt=0, r, f, res;
8607     static int loaded;
8608     static PPROBE_EGBB probeBB;
8609     if(!appData.testLegality) return 10;
8610     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8611     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8612     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8613     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8614         ChessSquare piece = boards[forwardMostMove][r][f];
8615         int black = (piece >= BlackPawn);
8616         int type = piece - black*BlackPawn;
8617         if(piece == EmptySquare) continue;
8618         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8619         if(type == WhiteKing) type = WhiteQueen + 1;
8620         type = egbbCode[type];
8621         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8622         pieces[cnt] = type + black*6;
8623         if(++cnt > 5) return 11;
8624     }
8625     pieces[cnt] = squares[cnt] = 0;
8626     // probe EGBB
8627     if(loaded == 2) return 13; // loading failed before
8628     if(loaded == 0) {
8629         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8630         HMODULE lib;
8631         PLOAD_EGBB loadBB;
8632         loaded = 2; // prepare for failure
8633         if(!path) return 13; // no egbb installed
8634         strncpy(buf, path + 8, MSG_SIZ);
8635         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8636         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8637         lib = LoadLibrary(buf);
8638         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8639         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8640         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8641         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8642         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8643         loaded = 1; // success!
8644     }
8645     res = probeBB(forwardMostMove & 1, pieces, squares);
8646     return res > 0 ? 1 : res < 0 ? -1 : 0;
8647 }
8648
8649 char *
8650 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8651 {   // [HGM] book: this routine intercepts moves to simulate book replies
8652     char *bookHit = NULL;
8653
8654     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8655         char buf[MSG_SIZ];
8656         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8657         SendToProgram(buf, cps);
8658     }
8659     //first determine if the incoming move brings opponent into his book
8660     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8661         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8662     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8663     if(bookHit != NULL && !cps->bookSuspend) {
8664         // make sure opponent is not going to reply after receiving move to book position
8665         SendToProgram("force\n", cps);
8666         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8667     }
8668     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8669     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8670     // now arrange restart after book miss
8671     if(bookHit) {
8672         // after a book hit we never send 'go', and the code after the call to this routine
8673         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8674         char buf[MSG_SIZ], *move = bookHit;
8675         if(cps->useSAN) {
8676             int fromX, fromY, toX, toY;
8677             char promoChar;
8678             ChessMove moveType;
8679             move = buf + 30;
8680             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8681                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8682                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8683                                     PosFlags(forwardMostMove),
8684                                     fromY, fromX, toY, toX, promoChar, move);
8685             } else {
8686                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8687                 bookHit = NULL;
8688             }
8689         }
8690         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8691         SendToProgram(buf, cps);
8692         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8693     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8694         SendToProgram("go\n", cps);
8695         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8696     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8697         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8698             SendToProgram("go\n", cps);
8699         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8700     }
8701     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8702 }
8703
8704 int
8705 LoadError (char *errmess, ChessProgramState *cps)
8706 {   // unloads engine and switches back to -ncp mode if it was first
8707     if(cps->initDone) return FALSE;
8708     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8709     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8710     cps->pr = NoProc;
8711     if(cps == &first) {
8712         appData.noChessProgram = TRUE;
8713         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8714         gameMode = BeginningOfGame; ModeHighlight();
8715         SetNCPMode();
8716     }
8717     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8718     DisplayMessage("", ""); // erase waiting message
8719     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8720     return TRUE;
8721 }
8722
8723 char *savedMessage;
8724 ChessProgramState *savedState;
8725 void
8726 DeferredBookMove (void)
8727 {
8728         if(savedState->lastPing != savedState->lastPong)
8729                     ScheduleDelayedEvent(DeferredBookMove, 10);
8730         else
8731         HandleMachineMove(savedMessage, savedState);
8732 }
8733
8734 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8735 static ChessProgramState *stalledEngine;
8736 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8737
8738 void
8739 HandleMachineMove (char *message, ChessProgramState *cps)
8740 {
8741     static char firstLeg[20], legs;
8742     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8743     char realname[MSG_SIZ];
8744     int fromX, fromY, toX, toY;
8745     ChessMove moveType;
8746     char promoChar, roar;
8747     char *p, *pv=buf1;
8748     int oldError;
8749     char *bookHit;
8750
8751     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8752         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8753         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8754             DisplayError(_("Invalid pairing from pairing engine"), 0);
8755             return;
8756         }
8757         pairingReceived = 1;
8758         NextMatchGame();
8759         return; // Skim the pairing messages here.
8760     }
8761
8762     oldError = cps->userError; cps->userError = 0;
8763
8764 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8765     /*
8766      * Kludge to ignore BEL characters
8767      */
8768     while (*message == '\007') message++;
8769
8770     /*
8771      * [HGM] engine debug message: ignore lines starting with '#' character
8772      */
8773     if(cps->debug && *message == '#') return;
8774
8775     /*
8776      * Look for book output
8777      */
8778     if (cps == &first && bookRequested) {
8779         if (message[0] == '\t' || message[0] == ' ') {
8780             /* Part of the book output is here; append it */
8781             strcat(bookOutput, message);
8782             strcat(bookOutput, "  \n");
8783             return;
8784         } else if (bookOutput[0] != NULLCHAR) {
8785             /* All of book output has arrived; display it */
8786             char *p = bookOutput;
8787             while (*p != NULLCHAR) {
8788                 if (*p == '\t') *p = ' ';
8789                 p++;
8790             }
8791             DisplayInformation(bookOutput);
8792             bookRequested = FALSE;
8793             /* Fall through to parse the current output */
8794         }
8795     }
8796
8797     /*
8798      * Look for machine move.
8799      */
8800     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8801         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8802     {
8803         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8804             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8805             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8806             stalledEngine = cps;
8807             if(appData.ponderNextMove) { // bring opponent out of ponder
8808                 if(gameMode == TwoMachinesPlay) {
8809                     if(cps->other->pause)
8810                         PauseEngine(cps->other);
8811                     else
8812                         SendToProgram("easy\n", cps->other);
8813                 }
8814             }
8815             StopClocks();
8816             return;
8817         }
8818
8819       if(cps->usePing) {
8820
8821         /* This method is only useful on engines that support ping */
8822         if(abortEngineThink) {
8823             if (appData.debugMode) {
8824                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8825             }
8826             SendToProgram("undo\n", cps);
8827             return;
8828         }
8829
8830         if (cps->lastPing != cps->lastPong) {
8831             /* Extra move from before last new; ignore */
8832             if (appData.debugMode) {
8833                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8834             }
8835           return;
8836         }
8837
8838       } else {
8839
8840         int machineWhite = FALSE;
8841
8842         switch (gameMode) {
8843           case BeginningOfGame:
8844             /* Extra move from before last reset; ignore */
8845             if (appData.debugMode) {
8846                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8847             }
8848             return;
8849
8850           case EndOfGame:
8851           case IcsIdle:
8852           default:
8853             /* Extra move after we tried to stop.  The mode test is
8854                not a reliable way of detecting this problem, but it's
8855                the best we can do on engines that don't support ping.
8856             */
8857             if (appData.debugMode) {
8858                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8859                         cps->which, gameMode);
8860             }
8861             SendToProgram("undo\n", cps);
8862             return;
8863
8864           case MachinePlaysWhite:
8865           case IcsPlayingWhite:
8866             machineWhite = TRUE;
8867             break;
8868
8869           case MachinePlaysBlack:
8870           case IcsPlayingBlack:
8871             machineWhite = FALSE;
8872             break;
8873
8874           case TwoMachinesPlay:
8875             machineWhite = (cps->twoMachinesColor[0] == 'w');
8876             break;
8877         }
8878         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8879             if (appData.debugMode) {
8880                 fprintf(debugFP,
8881                         "Ignoring move out of turn by %s, gameMode %d"
8882                         ", forwardMost %d\n",
8883                         cps->which, gameMode, forwardMostMove);
8884             }
8885             return;
8886         }
8887       }
8888
8889         if(cps->alphaRank) AlphaRank(machineMove, 4);
8890
8891         // [HGM] lion: (some very limited) support for Alien protocol
8892         killX = killY = kill2X = kill2Y = -1;
8893         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8894             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8895             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8896             return;
8897         }
8898         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8899             char *q = strchr(p+1, ',');            // second comma?
8900             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8901             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8902             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8903         }
8904         if(firstLeg[0]) { // there was a previous leg;
8905             // only support case where same piece makes two step
8906             char buf[20], *p = machineMove+1, *q = buf+1, f;
8907             safeStrCpy(buf, machineMove, 20);
8908             while(isdigit(*q)) q++; // find start of to-square
8909             safeStrCpy(machineMove, firstLeg, 20);
8910             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8911             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
8912             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)
8913             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8914             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8915             firstLeg[0] = NULLCHAR; legs = 0;
8916         }
8917
8918         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8919                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8920             /* Machine move could not be parsed; ignore it. */
8921           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8922                     machineMove, _(cps->which));
8923             DisplayMoveError(buf1);
8924             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8925                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8926             if (gameMode == TwoMachinesPlay) {
8927               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8928                        buf1, GE_XBOARD);
8929             }
8930             return;
8931         }
8932
8933         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8934         /* So we have to redo legality test with true e.p. status here,  */
8935         /* to make sure an illegal e.p. capture does not slip through,   */
8936         /* to cause a forfeit on a justified illegal-move complaint      */
8937         /* of the opponent.                                              */
8938         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8939            ChessMove moveType;
8940            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8941                              fromY, fromX, toY, toX, promoChar);
8942             if(moveType == IllegalMove) {
8943               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8944                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8945                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8946                            buf1, GE_XBOARD);
8947                 return;
8948            } else if(!appData.fischerCastling)
8949            /* [HGM] Kludge to handle engines that send FRC-style castling
8950               when they shouldn't (like TSCP-Gothic) */
8951            switch(moveType) {
8952              case WhiteASideCastleFR:
8953              case BlackASideCastleFR:
8954                toX+=2;
8955                currentMoveString[2]++;
8956                break;
8957              case WhiteHSideCastleFR:
8958              case BlackHSideCastleFR:
8959                toX--;
8960                currentMoveString[2]--;
8961                break;
8962              default: ; // nothing to do, but suppresses warning of pedantic compilers
8963            }
8964         }
8965         hintRequested = FALSE;
8966         lastHint[0] = NULLCHAR;
8967         bookRequested = FALSE;
8968         /* Program may be pondering now */
8969         cps->maybeThinking = TRUE;
8970         if (cps->sendTime == 2) cps->sendTime = 1;
8971         if (cps->offeredDraw) cps->offeredDraw--;
8972
8973         /* [AS] Save move info*/
8974         pvInfoList[ forwardMostMove ].score = programStats.score;
8975         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8976         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8977
8978         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8979
8980         /* Test suites abort the 'game' after one move */
8981         if(*appData.finger) {
8982            static FILE *f;
8983            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8984            if(!f) f = fopen(appData.finger, "w");
8985            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8986            else { DisplayFatalError("Bad output file", errno, 0); return; }
8987            free(fen);
8988            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8989         }
8990         if(appData.epd) {
8991            if(solvingTime >= 0) {
8992               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8993               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8994            } else {
8995               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8996               if(solvingTime == -2) second.matchWins++;
8997            }
8998            OutputKibitz(2, buf1);
8999            GameEnds(GameUnfinished, NULL, GE_XBOARD);
9000         }
9001
9002         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
9003         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
9004             int count = 0;
9005
9006             while( count < adjudicateLossPlies ) {
9007                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
9008
9009                 if( count & 1 ) {
9010                     score = -score; /* Flip score for winning side */
9011                 }
9012
9013                 if( score > appData.adjudicateLossThreshold ) {
9014                     break;
9015                 }
9016
9017                 count++;
9018             }
9019
9020             if( count >= adjudicateLossPlies ) {
9021                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9022
9023                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
9024                     "Xboard adjudication",
9025                     GE_XBOARD );
9026
9027                 return;
9028             }
9029         }
9030
9031         if(Adjudicate(cps)) {
9032             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9033             return; // [HGM] adjudicate: for all automatic game ends
9034         }
9035
9036 #if ZIPPY
9037         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9038             first.initDone) {
9039           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9040                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9041                 SendToICS("draw ");
9042                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9043           }
9044           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9045           ics_user_moved = 1;
9046           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9047                 char buf[3*MSG_SIZ];
9048
9049                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9050                         programStats.score / 100.,
9051                         programStats.depth,
9052                         programStats.time / 100.,
9053                         (unsigned int)programStats.nodes,
9054                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9055                         programStats.movelist);
9056                 SendToICS(buf);
9057           }
9058         }
9059 #endif
9060
9061         /* [AS] Clear stats for next move */
9062         ClearProgramStats();
9063         thinkOutput[0] = NULLCHAR;
9064         hiddenThinkOutputState = 0;
9065
9066         bookHit = NULL;
9067         if (gameMode == TwoMachinesPlay) {
9068             /* [HGM] relaying draw offers moved to after reception of move */
9069             /* and interpreting offer as claim if it brings draw condition */
9070             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9071                 SendToProgram("draw\n", cps->other);
9072             }
9073             if (cps->other->sendTime) {
9074                 SendTimeRemaining(cps->other,
9075                                   cps->other->twoMachinesColor[0] == 'w');
9076             }
9077             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9078             if (firstMove && !bookHit) {
9079                 firstMove = FALSE;
9080                 if (cps->other->useColors) {
9081                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9082                 }
9083                 SendToProgram("go\n", cps->other);
9084             }
9085             cps->other->maybeThinking = TRUE;
9086         }
9087
9088         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9089
9090         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9091
9092         if (!pausing && appData.ringBellAfterMoves) {
9093             if(!roar) RingBell();
9094         }
9095
9096         /*
9097          * Reenable menu items that were disabled while
9098          * machine was thinking
9099          */
9100         if (gameMode != TwoMachinesPlay)
9101             SetUserThinkingEnables();
9102
9103         // [HGM] book: after book hit opponent has received move and is now in force mode
9104         // force the book reply into it, and then fake that it outputted this move by jumping
9105         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9106         if(bookHit) {
9107                 static char bookMove[MSG_SIZ]; // a bit generous?
9108
9109                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9110                 strcat(bookMove, bookHit);
9111                 message = bookMove;
9112                 cps = cps->other;
9113                 programStats.nodes = programStats.depth = programStats.time =
9114                 programStats.score = programStats.got_only_move = 0;
9115                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9116
9117                 if(cps->lastPing != cps->lastPong) {
9118                     savedMessage = message; // args for deferred call
9119                     savedState = cps;
9120                     ScheduleDelayedEvent(DeferredBookMove, 10);
9121                     return;
9122                 }
9123                 goto FakeBookMove;
9124         }
9125
9126         return;
9127     }
9128
9129     /* Set special modes for chess engines.  Later something general
9130      *  could be added here; for now there is just one kludge feature,
9131      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9132      *  when "xboard" is given as an interactive command.
9133      */
9134     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9135         cps->useSigint = FALSE;
9136         cps->useSigterm = FALSE;
9137     }
9138     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9139       ParseFeatures(message+8, cps);
9140       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9141     }
9142
9143     if (!strncmp(message, "setup ", 6) && 
9144         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9145           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9146                                         ) { // [HGM] allow first engine to define opening position
9147       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9148       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9149       *buf = NULLCHAR;
9150       if(sscanf(message, "setup (%s", buf) == 1) {
9151         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9152         ASSIGN(appData.pieceToCharTable, buf);
9153       }
9154       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9155       if(dummy >= 3) {
9156         while(message[s] && message[s++] != ' ');
9157         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9158            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9159             if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
9160             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9161             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9162           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9163           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9164           startedFromSetupPosition = FALSE;
9165         }
9166       }
9167       if(startedFromSetupPosition) return;
9168       ParseFEN(boards[0], &dummy, message+s, FALSE);
9169       DrawPosition(TRUE, boards[0]);
9170       CopyBoard(initialPosition, boards[0]);
9171       startedFromSetupPosition = TRUE;
9172       return;
9173     }
9174     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9175       ChessSquare piece = WhitePawn;
9176       char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
9177       if(*p == '+') promoted++, ID = *++p;
9178       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9179       piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
9180       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9181       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9182       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9183       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9184       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9185       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9186                                                && gameInfo.variant != VariantGreat
9187                                                && gameInfo.variant != VariantFairy    ) return;
9188       if(piece < EmptySquare) {
9189         pieceDefs = TRUE;
9190         ASSIGN(pieceDesc[piece], buf1);
9191         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9192       }
9193       return;
9194     }
9195     if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
9196       if(deferChoice) {
9197         LeftClick(Press, 0, 0); // finish the click that was interrupted
9198       } else if(promoSweep != EmptySquare) {
9199         promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9200         if(strlen(promoRestrict) > 1) Sweep(0);
9201       }
9202       return;
9203     }
9204     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9205      * want this, I was asked to put it in, and obliged.
9206      */
9207     if (!strncmp(message, "setboard ", 9)) {
9208         Board initial_position;
9209
9210         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9211
9212         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9213             DisplayError(_("Bad FEN received from engine"), 0);
9214             return ;
9215         } else {
9216            Reset(TRUE, FALSE);
9217            CopyBoard(boards[0], initial_position);
9218            initialRulePlies = FENrulePlies;
9219            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9220            else gameMode = MachinePlaysBlack;
9221            DrawPosition(FALSE, boards[currentMove]);
9222         }
9223         return;
9224     }
9225
9226     /*
9227      * Look for communication commands
9228      */
9229     if (!strncmp(message, "telluser ", 9)) {
9230         if(message[9] == '\\' && message[10] == '\\')
9231             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9232         PlayTellSound();
9233         DisplayNote(message + 9);
9234         return;
9235     }
9236     if (!strncmp(message, "tellusererror ", 14)) {
9237         cps->userError = 1;
9238         if(message[14] == '\\' && message[15] == '\\')
9239             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9240         PlayTellSound();
9241         DisplayError(message + 14, 0);
9242         return;
9243     }
9244     if (!strncmp(message, "tellopponent ", 13)) {
9245       if (appData.icsActive) {
9246         if (loggedOn) {
9247           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9248           SendToICS(buf1);
9249         }
9250       } else {
9251         DisplayNote(message + 13);
9252       }
9253       return;
9254     }
9255     if (!strncmp(message, "tellothers ", 11)) {
9256       if (appData.icsActive) {
9257         if (loggedOn) {
9258           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9259           SendToICS(buf1);
9260         }
9261       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9262       return;
9263     }
9264     if (!strncmp(message, "tellall ", 8)) {
9265       if (appData.icsActive) {
9266         if (loggedOn) {
9267           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9268           SendToICS(buf1);
9269         }
9270       } else {
9271         DisplayNote(message + 8);
9272       }
9273       return;
9274     }
9275     if (strncmp(message, "warning", 7) == 0) {
9276         /* Undocumented feature, use tellusererror in new code */
9277         DisplayError(message, 0);
9278         return;
9279     }
9280     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9281         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9282         strcat(realname, " query");
9283         AskQuestion(realname, buf2, buf1, cps->pr);
9284         return;
9285     }
9286     /* Commands from the engine directly to ICS.  We don't allow these to be
9287      *  sent until we are logged on. Crafty kibitzes have been known to
9288      *  interfere with the login process.
9289      */
9290     if (loggedOn) {
9291         if (!strncmp(message, "tellics ", 8)) {
9292             SendToICS(message + 8);
9293             SendToICS("\n");
9294             return;
9295         }
9296         if (!strncmp(message, "tellicsnoalias ", 15)) {
9297             SendToICS(ics_prefix);
9298             SendToICS(message + 15);
9299             SendToICS("\n");
9300             return;
9301         }
9302         /* The following are for backward compatibility only */
9303         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9304             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9305             SendToICS(ics_prefix);
9306             SendToICS(message);
9307             SendToICS("\n");
9308             return;
9309         }
9310     }
9311     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9312         if(initPing == cps->lastPong) {
9313             if(gameInfo.variant == VariantUnknown) {
9314                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9315                 *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
9316                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9317             }
9318             initPing = -1;
9319         }
9320         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9321             abortEngineThink = FALSE;
9322             DisplayMessage("", "");
9323             ThawUI();
9324         }
9325         return;
9326     }
9327     if(!strncmp(message, "highlight ", 10)) {
9328         if(appData.testLegality && !*engineVariant && appData.markers) return;
9329         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9330         return;
9331     }
9332     if(!strncmp(message, "click ", 6)) {
9333         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9334         if(appData.testLegality || !appData.oneClick) return;
9335         sscanf(message+6, "%c%d%c", &f, &y, &c);
9336         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9337         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9338         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9339         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9340         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9341         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9342             LeftClick(Release, lastLeftX, lastLeftY);
9343         controlKey  = (c == ',');
9344         LeftClick(Press, x, y);
9345         LeftClick(Release, x, y);
9346         first.highlight = f;
9347         return;
9348     }
9349     /*
9350      * If the move is illegal, cancel it and redraw the board.
9351      * Also deal with other error cases.  Matching is rather loose
9352      * here to accommodate engines written before the spec.
9353      */
9354     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9355         strncmp(message, "Error", 5) == 0) {
9356         if (StrStr(message, "name") ||
9357             StrStr(message, "rating") || StrStr(message, "?") ||
9358             StrStr(message, "result") || StrStr(message, "board") ||
9359             StrStr(message, "bk") || StrStr(message, "computer") ||
9360             StrStr(message, "variant") || StrStr(message, "hint") ||
9361             StrStr(message, "random") || StrStr(message, "depth") ||
9362             StrStr(message, "accepted")) {
9363             return;
9364         }
9365         if (StrStr(message, "protover")) {
9366           /* Program is responding to input, so it's apparently done
9367              initializing, and this error message indicates it is
9368              protocol version 1.  So we don't need to wait any longer
9369              for it to initialize and send feature commands. */
9370           FeatureDone(cps, 1);
9371           cps->protocolVersion = 1;
9372           return;
9373         }
9374         cps->maybeThinking = FALSE;
9375
9376         if (StrStr(message, "draw")) {
9377             /* Program doesn't have "draw" command */
9378             cps->sendDrawOffers = 0;
9379             return;
9380         }
9381         if (cps->sendTime != 1 &&
9382             (StrStr(message, "time") || StrStr(message, "otim"))) {
9383           /* Program apparently doesn't have "time" or "otim" command */
9384           cps->sendTime = 0;
9385           return;
9386         }
9387         if (StrStr(message, "analyze")) {
9388             cps->analysisSupport = FALSE;
9389             cps->analyzing = FALSE;
9390 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9391             EditGameEvent(); // [HGM] try to preserve loaded game
9392             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9393             DisplayError(buf2, 0);
9394             return;
9395         }
9396         if (StrStr(message, "(no matching move)st")) {
9397           /* Special kludge for GNU Chess 4 only */
9398           cps->stKludge = TRUE;
9399           SendTimeControl(cps, movesPerSession, timeControl,
9400                           timeIncrement, appData.searchDepth,
9401                           searchTime);
9402           return;
9403         }
9404         if (StrStr(message, "(no matching move)sd")) {
9405           /* Special kludge for GNU Chess 4 only */
9406           cps->sdKludge = TRUE;
9407           SendTimeControl(cps, movesPerSession, timeControl,
9408                           timeIncrement, appData.searchDepth,
9409                           searchTime);
9410           return;
9411         }
9412         if (!StrStr(message, "llegal")) {
9413             return;
9414         }
9415         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9416             gameMode == IcsIdle) return;
9417         if (forwardMostMove <= backwardMostMove) return;
9418         if (pausing) PauseEvent();
9419       if(appData.forceIllegal) {
9420             // [HGM] illegal: machine refused move; force position after move into it
9421           SendToProgram("force\n", cps);
9422           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9423                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9424                 // when black is to move, while there might be nothing on a2 or black
9425                 // might already have the move. So send the board as if white has the move.
9426                 // But first we must change the stm of the engine, as it refused the last move
9427                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9428                 if(WhiteOnMove(forwardMostMove)) {
9429                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9430                     SendBoard(cps, forwardMostMove); // kludgeless board
9431                 } else {
9432                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9433                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9434                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9435                 }
9436           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9437             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9438                  gameMode == TwoMachinesPlay)
9439               SendToProgram("go\n", cps);
9440             return;
9441       } else
9442         if (gameMode == PlayFromGameFile) {
9443             /* Stop reading this game file */
9444             gameMode = EditGame;
9445             ModeHighlight();
9446         }
9447         /* [HGM] illegal-move claim should forfeit game when Xboard */
9448         /* only passes fully legal moves                            */
9449         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9450             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9451                                 "False illegal-move claim", GE_XBOARD );
9452             return; // do not take back move we tested as valid
9453         }
9454         currentMove = forwardMostMove-1;
9455         DisplayMove(currentMove-1); /* before DisplayMoveError */
9456         SwitchClocks(forwardMostMove-1); // [HGM] race
9457         DisplayBothClocks();
9458         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9459                 parseList[currentMove], _(cps->which));
9460         DisplayMoveError(buf1);
9461         DrawPosition(FALSE, boards[currentMove]);
9462
9463         SetUserThinkingEnables();
9464         return;
9465     }
9466     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9467         /* Program has a broken "time" command that
9468            outputs a string not ending in newline.
9469            Don't use it. */
9470         cps->sendTime = 0;
9471     }
9472     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9473         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9474             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9475     }
9476
9477     /*
9478      * If chess program startup fails, exit with an error message.
9479      * Attempts to recover here are futile. [HGM] Well, we try anyway
9480      */
9481     if ((StrStr(message, "unknown host") != NULL)
9482         || (StrStr(message, "No remote directory") != NULL)
9483         || (StrStr(message, "not found") != NULL)
9484         || (StrStr(message, "No such file") != NULL)
9485         || (StrStr(message, "can't alloc") != NULL)
9486         || (StrStr(message, "Permission denied") != NULL)) {
9487
9488         cps->maybeThinking = FALSE;
9489         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9490                 _(cps->which), cps->program, cps->host, message);
9491         RemoveInputSource(cps->isr);
9492         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9493             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9494             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9495         }
9496         return;
9497     }
9498
9499     /*
9500      * Look for hint output
9501      */
9502     if (sscanf(message, "Hint: %s", buf1) == 1) {
9503         if (cps == &first && hintRequested) {
9504             hintRequested = FALSE;
9505             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9506                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9507                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9508                                     PosFlags(forwardMostMove),
9509                                     fromY, fromX, toY, toX, promoChar, buf1);
9510                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9511                 DisplayInformation(buf2);
9512             } else {
9513                 /* Hint move could not be parsed!? */
9514               snprintf(buf2, sizeof(buf2),
9515                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9516                         buf1, _(cps->which));
9517                 DisplayError(buf2, 0);
9518             }
9519         } else {
9520           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9521         }
9522         return;
9523     }
9524
9525     /*
9526      * Ignore other messages if game is not in progress
9527      */
9528     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9529         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9530
9531     /*
9532      * look for win, lose, draw, or draw offer
9533      */
9534     if (strncmp(message, "1-0", 3) == 0) {
9535         char *p, *q, *r = "";
9536         p = strchr(message, '{');
9537         if (p) {
9538             q = strchr(p, '}');
9539             if (q) {
9540                 *q = NULLCHAR;
9541                 r = p + 1;
9542             }
9543         }
9544         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9545         return;
9546     } else if (strncmp(message, "0-1", 3) == 0) {
9547         char *p, *q, *r = "";
9548         p = strchr(message, '{');
9549         if (p) {
9550             q = strchr(p, '}');
9551             if (q) {
9552                 *q = NULLCHAR;
9553                 r = p + 1;
9554             }
9555         }
9556         /* Kludge for Arasan 4.1 bug */
9557         if (strcmp(r, "Black resigns") == 0) {
9558             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9559             return;
9560         }
9561         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9562         return;
9563     } else if (strncmp(message, "1/2", 3) == 0) {
9564         char *p, *q, *r = "";
9565         p = strchr(message, '{');
9566         if (p) {
9567             q = strchr(p, '}');
9568             if (q) {
9569                 *q = NULLCHAR;
9570                 r = p + 1;
9571             }
9572         }
9573
9574         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9575         return;
9576
9577     } else if (strncmp(message, "White resign", 12) == 0) {
9578         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9579         return;
9580     } else if (strncmp(message, "Black resign", 12) == 0) {
9581         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9582         return;
9583     } else if (strncmp(message, "White matches", 13) == 0 ||
9584                strncmp(message, "Black matches", 13) == 0   ) {
9585         /* [HGM] ignore GNUShogi noises */
9586         return;
9587     } else if (strncmp(message, "White", 5) == 0 &&
9588                message[5] != '(' &&
9589                StrStr(message, "Black") == NULL) {
9590         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9591         return;
9592     } else if (strncmp(message, "Black", 5) == 0 &&
9593                message[5] != '(') {
9594         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9595         return;
9596     } else if (strcmp(message, "resign") == 0 ||
9597                strcmp(message, "computer resigns") == 0) {
9598         switch (gameMode) {
9599           case MachinePlaysBlack:
9600           case IcsPlayingBlack:
9601             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9602             break;
9603           case MachinePlaysWhite:
9604           case IcsPlayingWhite:
9605             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9606             break;
9607           case TwoMachinesPlay:
9608             if (cps->twoMachinesColor[0] == 'w')
9609               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9610             else
9611               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9612             break;
9613           default:
9614             /* can't happen */
9615             break;
9616         }
9617         return;
9618     } else if (strncmp(message, "opponent mates", 14) == 0) {
9619         switch (gameMode) {
9620           case MachinePlaysBlack:
9621           case IcsPlayingBlack:
9622             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9623             break;
9624           case MachinePlaysWhite:
9625           case IcsPlayingWhite:
9626             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9627             break;
9628           case TwoMachinesPlay:
9629             if (cps->twoMachinesColor[0] == 'w')
9630               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9631             else
9632               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9633             break;
9634           default:
9635             /* can't happen */
9636             break;
9637         }
9638         return;
9639     } else if (strncmp(message, "computer mates", 14) == 0) {
9640         switch (gameMode) {
9641           case MachinePlaysBlack:
9642           case IcsPlayingBlack:
9643             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9644             break;
9645           case MachinePlaysWhite:
9646           case IcsPlayingWhite:
9647             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9648             break;
9649           case TwoMachinesPlay:
9650             if (cps->twoMachinesColor[0] == 'w')
9651               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9652             else
9653               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9654             break;
9655           default:
9656             /* can't happen */
9657             break;
9658         }
9659         return;
9660     } else if (strncmp(message, "checkmate", 9) == 0) {
9661         if (WhiteOnMove(forwardMostMove)) {
9662             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9663         } else {
9664             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9665         }
9666         return;
9667     } else if (strstr(message, "Draw") != NULL ||
9668                strstr(message, "game is a draw") != NULL) {
9669         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9670         return;
9671     } else if (strstr(message, "offer") != NULL &&
9672                strstr(message, "draw") != NULL) {
9673 #if ZIPPY
9674         if (appData.zippyPlay && first.initDone) {
9675             /* Relay offer to ICS */
9676             SendToICS(ics_prefix);
9677             SendToICS("draw\n");
9678         }
9679 #endif
9680         cps->offeredDraw = 2; /* valid until this engine moves twice */
9681         if (gameMode == TwoMachinesPlay) {
9682             if (cps->other->offeredDraw) {
9683                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9684             /* [HGM] in two-machine mode we delay relaying draw offer      */
9685             /* until after we also have move, to see if it is really claim */
9686             }
9687         } else if (gameMode == MachinePlaysWhite ||
9688                    gameMode == MachinePlaysBlack) {
9689           if (userOfferedDraw) {
9690             DisplayInformation(_("Machine accepts your draw offer"));
9691             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9692           } else {
9693             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9694           }
9695         }
9696     }
9697
9698
9699     /*
9700      * Look for thinking output
9701      */
9702     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9703           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9704                                 ) {
9705         int plylev, mvleft, mvtot, curscore, time;
9706         char mvname[MOVE_LEN];
9707         u64 nodes; // [DM]
9708         char plyext;
9709         int ignore = FALSE;
9710         int prefixHint = FALSE;
9711         mvname[0] = NULLCHAR;
9712
9713         switch (gameMode) {
9714           case MachinePlaysBlack:
9715           case IcsPlayingBlack:
9716             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9717             break;
9718           case MachinePlaysWhite:
9719           case IcsPlayingWhite:
9720             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9721             break;
9722           case AnalyzeMode:
9723           case AnalyzeFile:
9724             break;
9725           case IcsObserving: /* [DM] icsEngineAnalyze */
9726             if (!appData.icsEngineAnalyze) ignore = TRUE;
9727             break;
9728           case TwoMachinesPlay:
9729             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9730                 ignore = TRUE;
9731             }
9732             break;
9733           default:
9734             ignore = TRUE;
9735             break;
9736         }
9737
9738         if (!ignore) {
9739             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9740             int solved = 0;
9741             buf1[0] = NULLCHAR;
9742             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9743                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9744                 char score_buf[MSG_SIZ];
9745
9746                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9747                     nodes += u64Const(0x100000000);
9748
9749                 if (plyext != ' ' && plyext != '\t') {
9750                     time *= 100;
9751                 }
9752
9753                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9754                 if( cps->scoreIsAbsolute &&
9755                     ( gameMode == MachinePlaysBlack ||
9756                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9757                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9758                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9759                      !WhiteOnMove(currentMove)
9760                     ) )
9761                 {
9762                     curscore = -curscore;
9763                 }
9764
9765                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9766
9767                 if(*bestMove) { // rememer time best EPD move was first found
9768                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9769                     ChessMove mt; char *p = bestMove;
9770                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9771                     solved = 0;
9772                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9773                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9774                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9775                             solved = 1;
9776                             break;
9777                         }
9778                         while(*p && *p != ' ') p++;
9779                         while(*p == ' ') p++;
9780                     }
9781                     if(!solved) solvingTime = -1;
9782                 }
9783                 if(*avoidMove && !solved) {
9784                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9785                     ChessMove mt; char *p = avoidMove, solved = 1;
9786                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9787                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9788                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9789                             solved = 0; solvingTime = -2;
9790                             break;
9791                         }
9792                         while(*p && *p != ' ') p++;
9793                         while(*p == ' ') p++;
9794                     }
9795                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9796                 }
9797
9798                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9799                         char buf[MSG_SIZ];
9800                         FILE *f;
9801                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9802                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9803                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9804                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9805                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9806                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9807                                 fclose(f);
9808                         }
9809                         else
9810                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9811                           DisplayError(_("failed writing PV"), 0);
9812                 }
9813
9814                 tempStats.depth = plylev;
9815                 tempStats.nodes = nodes;
9816                 tempStats.time = time;
9817                 tempStats.score = curscore;
9818                 tempStats.got_only_move = 0;
9819
9820                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9821                         int ticklen;
9822
9823                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9824                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9825                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9826                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9827                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9828                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9829                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9830                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9831                 }
9832
9833                 /* Buffer overflow protection */
9834                 if (pv[0] != NULLCHAR) {
9835                     if (strlen(pv) >= sizeof(tempStats.movelist)
9836                         && appData.debugMode) {
9837                         fprintf(debugFP,
9838                                 "PV is too long; using the first %u bytes.\n",
9839                                 (unsigned) sizeof(tempStats.movelist) - 1);
9840                     }
9841
9842                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9843                 } else {
9844                     sprintf(tempStats.movelist, " no PV\n");
9845                 }
9846
9847                 if (tempStats.seen_stat) {
9848                     tempStats.ok_to_send = 1;
9849                 }
9850
9851                 if (strchr(tempStats.movelist, '(') != NULL) {
9852                     tempStats.line_is_book = 1;
9853                     tempStats.nr_moves = 0;
9854                     tempStats.moves_left = 0;
9855                 } else {
9856                     tempStats.line_is_book = 0;
9857                 }
9858
9859                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9860                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9861
9862                 SendProgramStatsToFrontend( cps, &tempStats );
9863
9864                 /*
9865                     [AS] Protect the thinkOutput buffer from overflow... this
9866                     is only useful if buf1 hasn't overflowed first!
9867                 */
9868                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9869                 if(curscore >= MATE_SCORE) 
9870                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9871                 else if(curscore <= -MATE_SCORE) 
9872                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9873                 else
9874                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9875                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9876                          plylev,
9877                          (gameMode == TwoMachinesPlay ?
9878                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9879                          score_buf,
9880                          prefixHint ? lastHint : "",
9881                          prefixHint ? " " : "" );
9882
9883                 if( buf1[0] != NULLCHAR ) {
9884                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9885
9886                     if( strlen(pv) > max_len ) {
9887                         if( appData.debugMode) {
9888                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9889                         }
9890                         pv[max_len+1] = '\0';
9891                     }
9892
9893                     strcat( thinkOutput, pv);
9894                 }
9895
9896                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9897                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9898                     DisplayMove(currentMove - 1);
9899                 }
9900                 return;
9901
9902             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9903                 /* crafty (9.25+) says "(only move) <move>"
9904                  * if there is only 1 legal move
9905                  */
9906                 sscanf(p, "(only move) %s", buf1);
9907                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9908                 sprintf(programStats.movelist, "%s (only move)", buf1);
9909                 programStats.depth = 1;
9910                 programStats.nr_moves = 1;
9911                 programStats.moves_left = 1;
9912                 programStats.nodes = 1;
9913                 programStats.time = 1;
9914                 programStats.got_only_move = 1;
9915
9916                 /* Not really, but we also use this member to
9917                    mean "line isn't going to change" (Crafty
9918                    isn't searching, so stats won't change) */
9919                 programStats.line_is_book = 1;
9920
9921                 SendProgramStatsToFrontend( cps, &programStats );
9922
9923                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9924                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9925                     DisplayMove(currentMove - 1);
9926                 }
9927                 return;
9928             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9929                               &time, &nodes, &plylev, &mvleft,
9930                               &mvtot, mvname) >= 5) {
9931                 /* The stat01: line is from Crafty (9.29+) in response
9932                    to the "." command */
9933                 programStats.seen_stat = 1;
9934                 cps->maybeThinking = TRUE;
9935
9936                 if (programStats.got_only_move || !appData.periodicUpdates)
9937                   return;
9938
9939                 programStats.depth = plylev;
9940                 programStats.time = time;
9941                 programStats.nodes = nodes;
9942                 programStats.moves_left = mvleft;
9943                 programStats.nr_moves = mvtot;
9944                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9945                 programStats.ok_to_send = 1;
9946                 programStats.movelist[0] = '\0';
9947
9948                 SendProgramStatsToFrontend( cps, &programStats );
9949
9950                 return;
9951
9952             } else if (strncmp(message,"++",2) == 0) {
9953                 /* Crafty 9.29+ outputs this */
9954                 programStats.got_fail = 2;
9955                 return;
9956
9957             } else if (strncmp(message,"--",2) == 0) {
9958                 /* Crafty 9.29+ outputs this */
9959                 programStats.got_fail = 1;
9960                 return;
9961
9962             } else if (thinkOutput[0] != NULLCHAR &&
9963                        strncmp(message, "    ", 4) == 0) {
9964                 unsigned message_len;
9965
9966                 p = message;
9967                 while (*p && *p == ' ') p++;
9968
9969                 message_len = strlen( p );
9970
9971                 /* [AS] Avoid buffer overflow */
9972                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9973                     strcat(thinkOutput, " ");
9974                     strcat(thinkOutput, p);
9975                 }
9976
9977                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9978                     strcat(programStats.movelist, " ");
9979                     strcat(programStats.movelist, p);
9980                 }
9981
9982                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9983                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9984                     DisplayMove(currentMove - 1);
9985                 }
9986                 return;
9987             }
9988         }
9989         else {
9990             buf1[0] = NULLCHAR;
9991
9992             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9993                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9994             {
9995                 ChessProgramStats cpstats;
9996
9997                 if (plyext != ' ' && plyext != '\t') {
9998                     time *= 100;
9999                 }
10000
10001                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
10002                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
10003                     curscore = -curscore;
10004                 }
10005
10006                 cpstats.depth = plylev;
10007                 cpstats.nodes = nodes;
10008                 cpstats.time = time;
10009                 cpstats.score = curscore;
10010                 cpstats.got_only_move = 0;
10011                 cpstats.movelist[0] = '\0';
10012
10013                 if (buf1[0] != NULLCHAR) {
10014                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
10015                 }
10016
10017                 cpstats.ok_to_send = 0;
10018                 cpstats.line_is_book = 0;
10019                 cpstats.nr_moves = 0;
10020                 cpstats.moves_left = 0;
10021
10022                 SendProgramStatsToFrontend( cps, &cpstats );
10023             }
10024         }
10025     }
10026 }
10027
10028
10029 /* Parse a game score from the character string "game", and
10030    record it as the history of the current game.  The game
10031    score is NOT assumed to start from the standard position.
10032    The display is not updated in any way.
10033    */
10034 void
10035 ParseGameHistory (char *game)
10036 {
10037     ChessMove moveType;
10038     int fromX, fromY, toX, toY, boardIndex, mask;
10039     char promoChar;
10040     char *p, *q;
10041     char buf[MSG_SIZ];
10042
10043     if (appData.debugMode)
10044       fprintf(debugFP, "Parsing game history: %s\n", game);
10045
10046     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10047     gameInfo.site = StrSave(appData.icsHost);
10048     gameInfo.date = PGNDate();
10049     gameInfo.round = StrSave("-");
10050
10051     /* Parse out names of players */
10052     while (*game == ' ') game++;
10053     p = buf;
10054     while (*game != ' ') *p++ = *game++;
10055     *p = NULLCHAR;
10056     gameInfo.white = StrSave(buf);
10057     while (*game == ' ') game++;
10058     p = buf;
10059     while (*game != ' ' && *game != '\n') *p++ = *game++;
10060     *p = NULLCHAR;
10061     gameInfo.black = StrSave(buf);
10062
10063     /* Parse moves */
10064     boardIndex = blackPlaysFirst ? 1 : 0;
10065     yynewstr(game);
10066     for (;;) {
10067         yyboardindex = boardIndex;
10068         moveType = (ChessMove) Myylex();
10069         switch (moveType) {
10070           case IllegalMove:             /* maybe suicide chess, etc. */
10071   if (appData.debugMode) {
10072     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10073     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10074     setbuf(debugFP, NULL);
10075   }
10076           case WhitePromotion:
10077           case BlackPromotion:
10078           case WhiteNonPromotion:
10079           case BlackNonPromotion:
10080           case NormalMove:
10081           case FirstLeg:
10082           case WhiteCapturesEnPassant:
10083           case BlackCapturesEnPassant:
10084           case WhiteKingSideCastle:
10085           case WhiteQueenSideCastle:
10086           case BlackKingSideCastle:
10087           case BlackQueenSideCastle:
10088           case WhiteKingSideCastleWild:
10089           case WhiteQueenSideCastleWild:
10090           case BlackKingSideCastleWild:
10091           case BlackQueenSideCastleWild:
10092           /* PUSH Fabien */
10093           case WhiteHSideCastleFR:
10094           case WhiteASideCastleFR:
10095           case BlackHSideCastleFR:
10096           case BlackASideCastleFR:
10097           /* POP Fabien */
10098             fromX = currentMoveString[0] - AAA;
10099             fromY = currentMoveString[1] - ONE;
10100             toX = currentMoveString[2] - AAA;
10101             toY = currentMoveString[3] - ONE;
10102             promoChar = currentMoveString[4];
10103             break;
10104           case WhiteDrop:
10105           case BlackDrop:
10106             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10107             fromX = moveType == WhiteDrop ?
10108               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10109             (int) CharToPiece(ToLower(currentMoveString[0]));
10110             fromY = DROP_RANK;
10111             toX = currentMoveString[2] - AAA;
10112             toY = currentMoveString[3] - ONE;
10113             promoChar = NULLCHAR;
10114             break;
10115           case AmbiguousMove:
10116             /* bug? */
10117             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10118   if (appData.debugMode) {
10119     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10120     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10121     setbuf(debugFP, NULL);
10122   }
10123             DisplayError(buf, 0);
10124             return;
10125           case ImpossibleMove:
10126             /* bug? */
10127             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10128   if (appData.debugMode) {
10129     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10130     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10131     setbuf(debugFP, NULL);
10132   }
10133             DisplayError(buf, 0);
10134             return;
10135           case EndOfFile:
10136             if (boardIndex < backwardMostMove) {
10137                 /* Oops, gap.  How did that happen? */
10138                 DisplayError(_("Gap in move list"), 0);
10139                 return;
10140             }
10141             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10142             if (boardIndex > forwardMostMove) {
10143                 forwardMostMove = boardIndex;
10144             }
10145             return;
10146           case ElapsedTime:
10147             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10148                 strcat(parseList[boardIndex-1], " ");
10149                 strcat(parseList[boardIndex-1], yy_text);
10150             }
10151             continue;
10152           case Comment:
10153           case PGNTag:
10154           case NAG:
10155           default:
10156             /* ignore */
10157             continue;
10158           case WhiteWins:
10159           case BlackWins:
10160           case GameIsDrawn:
10161           case GameUnfinished:
10162             if (gameMode == IcsExamining) {
10163                 if (boardIndex < backwardMostMove) {
10164                     /* Oops, gap.  How did that happen? */
10165                     return;
10166                 }
10167                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10168                 return;
10169             }
10170             gameInfo.result = moveType;
10171             p = strchr(yy_text, '{');
10172             if (p == NULL) p = strchr(yy_text, '(');
10173             if (p == NULL) {
10174                 p = yy_text;
10175                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10176             } else {
10177                 q = strchr(p, *p == '{' ? '}' : ')');
10178                 if (q != NULL) *q = NULLCHAR;
10179                 p++;
10180             }
10181             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10182             gameInfo.resultDetails = StrSave(p);
10183             continue;
10184         }
10185         if (boardIndex >= forwardMostMove &&
10186             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10187             backwardMostMove = blackPlaysFirst ? 1 : 0;
10188             return;
10189         }
10190         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10191                                  fromY, fromX, toY, toX, promoChar,
10192                                  parseList[boardIndex]);
10193         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10194         /* currentMoveString is set as a side-effect of yylex */
10195         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10196         strcat(moveList[boardIndex], "\n");
10197         boardIndex++;
10198         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10199         mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
10200         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10201           case MT_NONE:
10202           case MT_STALEMATE:
10203           default:
10204             break;
10205           case MT_CHECK:
10206             if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
10207             if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
10208                 if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
10209                 break;
10210             }
10211           case MT_CHECKMATE:
10212           case MT_STAINMATE:
10213             strcat(parseList[boardIndex - 1], "#");
10214             break;
10215         }
10216     }
10217 }
10218
10219
10220 /* Apply a move to the given board  */
10221 void
10222 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10223 {
10224   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, berolina = 0;
10225   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10226
10227     /* [HGM] compute & store e.p. status and castling rights for new position */
10228     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10229
10230       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10231       oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_TO] & 255,lastRank = board[LAST_TO] >> 8;
10232       board[EP_STATUS] = EP_NONE;
10233       board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
10234
10235   if (fromY == DROP_RANK) {
10236         /* must be first */
10237         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10238             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10239             return;
10240         }
10241         piece = board[toY][toX] = (ChessSquare) fromX;
10242   } else {
10243 //      ChessSquare victim;
10244       int i;
10245
10246       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10247 //           victim = board[killY][killX],
10248            killed = board[killY][killX],
10249            board[killY][killX] = EmptySquare,
10250            board[EP_STATUS] = EP_CAPTURE;
10251            if( kill2X >= 0 && kill2Y >= 0)
10252              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10253       }
10254
10255       if( board[toY][toX] != EmptySquare ) {
10256            board[EP_STATUS] = EP_CAPTURE;
10257            if( (fromX != toX || fromY != toY) && // not igui!
10258                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10259                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10260                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10261            }
10262       }
10263
10264       pawn = board[fromY][fromX];
10265       if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
10266         if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
10267             captured = board[lastRank][lastFile]; // remove victim
10268             board[lastRank][lastFile] = EmptySquare;
10269             pawn = EmptySquare; // kludge to suppress old e.p. code
10270         }
10271       }
10272       if( pawn == WhiteLance || pawn == BlackLance ) {
10273            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10274                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10275                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10276            }
10277       }
10278       if( pawn == WhitePawn ) {
10279            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10280                board[EP_STATUS] = EP_PAWN_MOVE;
10281            if( toY-fromY>=2) {
10282                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10283                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10284                         gameInfo.variant != VariantBerolina || toX < fromX)
10285                       board[EP_STATUS] = toX | berolina;
10286                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10287                         gameInfo.variant != VariantBerolina || toX > fromX)
10288                       board[EP_STATUS] = toX;
10289                board[LAST_TO] = toX + 256*toY;
10290            }
10291       } else
10292       if( pawn == BlackPawn ) {
10293            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10294                board[EP_STATUS] = EP_PAWN_MOVE;
10295            if( toY-fromY<= -2) {
10296                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10297                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10298                         gameInfo.variant != VariantBerolina || toX < fromX)
10299                       board[EP_STATUS] = toX | berolina;
10300                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10301                         gameInfo.variant != VariantBerolina || toX > fromX)
10302                       board[EP_STATUS] = toX;
10303                board[LAST_TO] = toX + 256*toY;
10304            }
10305        }
10306
10307        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10308        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10309        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10310        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10311
10312        for(i=0; i<nrCastlingRights; i++) {
10313            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10314               board[CASTLING][i] == toX   && castlingRank[i] == toY
10315              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10316        }
10317
10318        if(gameInfo.variant == VariantSChess) { // update virginity
10319            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10320            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10321            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10322            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10323        }
10324
10325      if (fromX == toX && fromY == toY && killX < 0) return;
10326
10327      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10328      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10329      if(gameInfo.variant == VariantKnightmate)
10330          king += (int) WhiteUnicorn - (int) WhiteKing;
10331
10332     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10333        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10334         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10335         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10336         board[EP_STATUS] = EP_NONE; // capture was fake!
10337     } else
10338     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10339         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10340         board[toY][toX] = piece;
10341         board[EP_STATUS] = EP_NONE; // capture was fake!
10342     } else
10343     /* Code added by Tord: */
10344     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10345     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10346         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10347       board[EP_STATUS] = EP_NONE; // capture was fake!
10348       board[fromY][fromX] = EmptySquare;
10349       board[toY][toX] = EmptySquare;
10350       if((toX > fromX) != (piece == WhiteRook)) {
10351         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10352       } else {
10353         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10354       }
10355     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10356                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10357       board[EP_STATUS] = EP_NONE;
10358       board[fromY][fromX] = EmptySquare;
10359       board[toY][toX] = EmptySquare;
10360       if((toX > fromX) != (piece == BlackRook)) {
10361         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10362       } else {
10363         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10364       }
10365     /* End of code added by Tord */
10366
10367     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10368         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10369         board[toY][toX] = piece;
10370     } else if (board[fromY][fromX] == king
10371         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10372         && toY == fromY && toX > fromX+1) {
10373         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
10374                                                                                              ; // castle with nearest piece
10375         board[fromY][toX-1] = board[fromY][rookX];
10376         board[fromY][rookX] = EmptySquare;
10377         board[fromY][fromX] = EmptySquare;
10378         board[toY][toX] = king;
10379     } else if (board[fromY][fromX] == king
10380         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10381                && toY == fromY && toX < fromX-1) {
10382         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10383                                                                                   ; // castle with nearest piece
10384         board[fromY][toX+1] = board[fromY][rookX];
10385         board[fromY][rookX] = EmptySquare;
10386         board[fromY][fromX] = EmptySquare;
10387         board[toY][toX] = king;
10388     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10389                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10390                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10391                ) {
10392         /* white pawn promotion */
10393         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10394         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10395             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10396         board[fromY][fromX] = EmptySquare;
10397     } else if ((fromY >= BOARD_HEIGHT>>1)
10398                && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10399                && (toX != fromX)
10400                && gameInfo.variant != VariantXiangqi
10401                && gameInfo.variant != VariantBerolina
10402                && (pawn == WhitePawn)
10403                && (board[toY][toX] == EmptySquare)) {
10404         if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
10405         board[fromY][fromX] = EmptySquare;
10406         board[toY][toX] = piece;
10407         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10408     } else if ((fromY == BOARD_HEIGHT-4)
10409                && (toX == fromX)
10410                && gameInfo.variant == VariantBerolina
10411                && (board[fromY][fromX] == WhitePawn)
10412                && (board[toY][toX] == EmptySquare)) {
10413         board[fromY][fromX] = EmptySquare;
10414         board[toY][toX] = WhitePawn;
10415         if(oldEP & EP_BEROLIN_A) {
10416                 captured = board[fromY][fromX-1];
10417                 board[fromY][fromX-1] = EmptySquare;
10418         }else{  captured = board[fromY][fromX+1];
10419                 board[fromY][fromX+1] = EmptySquare;
10420         }
10421     } else if (board[fromY][fromX] == king
10422         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10423                && toY == fromY && toX > fromX+1) {
10424         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
10425                                                                                              ;
10426         board[fromY][toX-1] = board[fromY][rookX];
10427         board[fromY][rookX] = EmptySquare;
10428         board[fromY][fromX] = EmptySquare;
10429         board[toY][toX] = king;
10430     } else if (board[fromY][fromX] == king
10431         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10432                && toY == fromY && toX < fromX-1) {
10433         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
10434                                                                                 ;
10435         board[fromY][toX+1] = board[fromY][rookX];
10436         board[fromY][rookX] = EmptySquare;
10437         board[fromY][fromX] = EmptySquare;
10438         board[toY][toX] = king;
10439     } else if (fromY == 7 && fromX == 3
10440                && board[fromY][fromX] == BlackKing
10441                && toY == 7 && toX == 5) {
10442         board[fromY][fromX] = EmptySquare;
10443         board[toY][toX] = BlackKing;
10444         board[fromY][7] = EmptySquare;
10445         board[toY][4] = BlackRook;
10446     } else if (fromY == 7 && fromX == 3
10447                && board[fromY][fromX] == BlackKing
10448                && toY == 7 && toX == 1) {
10449         board[fromY][fromX] = EmptySquare;
10450         board[toY][toX] = BlackKing;
10451         board[fromY][0] = EmptySquare;
10452         board[toY][2] = BlackRook;
10453     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10454                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10455                && toY < promoRank && promoChar
10456                ) {
10457         /* black pawn promotion */
10458         board[toY][toX] = CharToPiece(ToLower(promoChar));
10459         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10460             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10461         board[fromY][fromX] = EmptySquare;
10462     } else if ((fromY < BOARD_HEIGHT>>1)
10463                && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10464                && (toX != fromX)
10465                && gameInfo.variant != VariantXiangqi
10466                && gameInfo.variant != VariantBerolina
10467                && (pawn == BlackPawn)
10468                && (board[toY][toX] == EmptySquare)) {
10469         if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
10470         board[fromY][fromX] = EmptySquare;
10471         board[toY][toX] = piece;
10472         captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
10473     } else if ((fromY == 3)
10474                && (toX == fromX)
10475                && gameInfo.variant == VariantBerolina
10476                && (board[fromY][fromX] == BlackPawn)
10477                && (board[toY][toX] == EmptySquare)) {
10478         board[fromY][fromX] = EmptySquare;
10479         board[toY][toX] = BlackPawn;
10480         if(oldEP & EP_BEROLIN_A) {
10481                 captured = board[fromY][fromX-1];
10482                 board[fromY][fromX-1] = EmptySquare;
10483         }else{  captured = board[fromY][fromX+1];
10484                 board[fromY][fromX+1] = EmptySquare;
10485         }
10486     } else {
10487         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10488         board[fromY][fromX] = EmptySquare;
10489         board[toY][toX] = piece;
10490     }
10491   }
10492
10493     if (gameInfo.holdingsWidth != 0) {
10494
10495       /* !!A lot more code needs to be written to support holdings  */
10496       /* [HGM] OK, so I have written it. Holdings are stored in the */
10497       /* penultimate board files, so they are automaticlly stored   */
10498       /* in the game history.                                       */
10499       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10500                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10501         /* Delete from holdings, by decreasing count */
10502         /* and erasing image if necessary            */
10503         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10504         if(p < (int) BlackPawn) { /* white drop */
10505              p -= (int)WhitePawn;
10506                  p = PieceToNumber((ChessSquare)p);
10507              if(p >= gameInfo.holdingsSize) p = 0;
10508              if(--board[p][BOARD_WIDTH-2] <= 0)
10509                   board[p][BOARD_WIDTH-1] = EmptySquare;
10510              if((int)board[p][BOARD_WIDTH-2] < 0)
10511                         board[p][BOARD_WIDTH-2] = 0;
10512         } else {                  /* black drop */
10513              p -= (int)BlackPawn;
10514                  p = PieceToNumber((ChessSquare)p);
10515              if(p >= gameInfo.holdingsSize) p = 0;
10516              if(--board[handSize-1-p][1] <= 0)
10517                   board[handSize-1-p][0] = EmptySquare;
10518              if((int)board[handSize-1-p][1] < 0)
10519                         board[handSize-1-p][1] = 0;
10520         }
10521       }
10522       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10523           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10524         /* [HGM] holdings: Add to holdings, if holdings exist */
10525         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10526                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10527                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10528         }
10529         p = (int) captured;
10530         if (p >= (int) BlackPawn) {
10531           p -= (int)BlackPawn;
10532           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10533                   /* Restore shogi-promoted piece to its original  first */
10534                   captured = (ChessSquare) (DEMOTED(captured));
10535                   p = DEMOTED(p);
10536           }
10537           p = PieceToNumber((ChessSquare)p);
10538           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10539           board[p][BOARD_WIDTH-2]++;
10540           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10541         } else {
10542           p -= (int)WhitePawn;
10543           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10544                   captured = (ChessSquare) (DEMOTED(captured));
10545                   p = DEMOTED(p);
10546           }
10547           p = PieceToNumber((ChessSquare)p);
10548           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10549           board[handSize-1-p][1]++;
10550           board[handSize-1-p][0] = WHITE_TO_BLACK captured;
10551         }
10552       }
10553     } else if (gameInfo.variant == VariantAtomic) {
10554       if (captured != EmptySquare) {
10555         int y, x;
10556         for (y = toY-1; y <= toY+1; y++) {
10557           for (x = toX-1; x <= toX+1; x++) {
10558             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10559                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10560               board[y][x] = EmptySquare;
10561             }
10562           }
10563         }
10564         board[toY][toX] = EmptySquare;
10565       }
10566     }
10567
10568     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10569         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10570     } else
10571     if(promoChar == '+') {
10572         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10573         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10574         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10575           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10576     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10577         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10578         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10579            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10580         board[toY][toX] = newPiece;
10581     }
10582     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10583                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10584         // [HGM] superchess: take promotion piece out of holdings
10585         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10586         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10587             if(!--board[k][BOARD_WIDTH-2])
10588                 board[k][BOARD_WIDTH-1] = EmptySquare;
10589         } else {
10590             if(!--board[handSize-1-k][1])
10591                 board[handSize-1-k][0] = EmptySquare;
10592         }
10593     }
10594 }
10595
10596 /* Updates forwardMostMove */
10597 void
10598 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10599 {
10600     int x = toX, y = toY, mask;
10601     char *s = parseList[forwardMostMove];
10602     ChessSquare p = boards[forwardMostMove][toY][toX];
10603 //    forwardMostMove++; // [HGM] bare: moved downstream
10604
10605     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10606     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10607     (void) CoordsToAlgebraic(boards[forwardMostMove],
10608                              PosFlags(forwardMostMove),
10609                              fromY, fromX, y, x, (killX < 0)*promoChar,
10610                              s);
10611     if(kill2X >= 0 && kill2Y >= 0)
10612         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10613     if(killX >= 0 && killY >= 0)
10614         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10615                                            toX + AAA, toY + ONE - '0', promoChar);
10616
10617     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10618         int timeLeft; static int lastLoadFlag=0; int king, piece;
10619         piece = boards[forwardMostMove][fromY][fromX];
10620         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10621         if(gameInfo.variant == VariantKnightmate)
10622             king += (int) WhiteUnicorn - (int) WhiteKing;
10623         if(forwardMostMove == 0) {
10624             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10625                 fprintf(serverMoves, "%s;", UserName());
10626             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10627                 fprintf(serverMoves, "%s;", second.tidy);
10628             fprintf(serverMoves, "%s;", first.tidy);
10629             if(gameMode == MachinePlaysWhite)
10630                 fprintf(serverMoves, "%s;", UserName());
10631             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10632                 fprintf(serverMoves, "%s;", second.tidy);
10633         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10634         lastLoadFlag = loadFlag;
10635         // print base move
10636         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10637         // print castling suffix
10638         if( toY == fromY && piece == king ) {
10639             if(toX-fromX > 1)
10640                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10641             if(fromX-toX >1)
10642                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10643         }
10644         // e.p. suffix
10645         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10646              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10647              boards[forwardMostMove][toY][toX] == EmptySquare
10648              && fromX != toX && fromY != toY)
10649                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10650         // promotion suffix
10651         if(promoChar != NULLCHAR) {
10652             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10653                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10654                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10655             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10656         }
10657         if(!loadFlag) {
10658                 char buf[MOVE_LEN*2], *p; int len;
10659             fprintf(serverMoves, "/%d/%d",
10660                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10661             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10662             else                      timeLeft = blackTimeRemaining/1000;
10663             fprintf(serverMoves, "/%d", timeLeft);
10664                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10665                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10666                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10667                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10668             fprintf(serverMoves, "/%s", buf);
10669         }
10670         fflush(serverMoves);
10671     }
10672
10673     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10674         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10675       return;
10676     }
10677     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10678     if (commentList[forwardMostMove+1] != NULL) {
10679         free(commentList[forwardMostMove+1]);
10680         commentList[forwardMostMove+1] = NULL;
10681     }
10682     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10683     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10684     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10685     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10686     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10687     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10688     adjustedClock = FALSE;
10689     gameInfo.result = GameUnfinished;
10690     if (gameInfo.resultDetails != NULL) {
10691         free(gameInfo.resultDetails);
10692         gameInfo.resultDetails = NULL;
10693     }
10694     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10695                               moveList[forwardMostMove - 1]);
10696     mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
10697     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10698       case MT_NONE:
10699       case MT_STALEMATE:
10700       default:
10701         break;
10702       case MT_CHECK:
10703         if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
10704         if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
10705             if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
10706             break;
10707         }
10708       case MT_CHECKMATE:
10709       case MT_STAINMATE:
10710         strcat(parseList[forwardMostMove - 1], "#");
10711         break;
10712     }
10713 }
10714
10715 /* Updates currentMove if not pausing */
10716 void
10717 ShowMove (int fromX, int fromY, int toX, int toY)
10718 {
10719     int instant = (gameMode == PlayFromGameFile) ?
10720         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10721     if(appData.noGUI) return;
10722     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10723         if (!instant) {
10724             if (forwardMostMove == currentMove + 1) {
10725                 AnimateMove(boards[forwardMostMove - 1],
10726                             fromX, fromY, toX, toY);
10727             }
10728         }
10729         currentMove = forwardMostMove;
10730     }
10731
10732     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10733
10734     if (instant) return;
10735
10736     DisplayMove(currentMove - 1);
10737     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10738             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10739                 SetHighlights(fromX, fromY, toX, toY);
10740             }
10741     }
10742     DrawPosition(FALSE, boards[currentMove]);
10743     DisplayBothClocks();
10744     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10745 }
10746
10747 void
10748 SendEgtPath (ChessProgramState *cps)
10749 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10750         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10751
10752         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10753
10754         while(*p) {
10755             char c, *q = name+1, *r, *s;
10756
10757             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10758             while(*p && *p != ',') *q++ = *p++;
10759             *q++ = ':'; *q = 0;
10760             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10761                 strcmp(name, ",nalimov:") == 0 ) {
10762                 // take nalimov path from the menu-changeable option first, if it is defined
10763               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10764                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10765             } else
10766             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10767                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10768                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10769                 s = r = StrStr(s, ":") + 1; // beginning of path info
10770                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10771                 c = *r; *r = 0;             // temporarily null-terminate path info
10772                     *--q = 0;               // strip of trailig ':' from name
10773                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10774                 *r = c;
10775                 SendToProgram(buf,cps);     // send egtbpath command for this format
10776             }
10777             if(*p == ',') p++; // read away comma to position for next format name
10778         }
10779 }
10780
10781 static int
10782 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10783 {
10784       int width = 8, height = 8, holdings = 0;             // most common sizes
10785       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10786       // correct the deviations default for each variant
10787       if( v == VariantXiangqi ) width = 9,  height = 10;
10788       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10789       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10790       if( v == VariantCapablanca || v == VariantCapaRandom ||
10791           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10792                                 width = 10;
10793       if( v == VariantCourier ) width = 12;
10794       if( v == VariantSuper )                            holdings = 8;
10795       if( v == VariantGreat )   width = 10,              holdings = 8;
10796       if( v == VariantSChess )                           holdings = 7;
10797       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10798       if( v == VariantChuChess) width = 10, height = 10;
10799       if( v == VariantChu )     width = 12, height = 12;
10800       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10801              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10802              holdingsSize >= 0 && holdingsSize != holdings;
10803 }
10804
10805 char variantError[MSG_SIZ];
10806
10807 char *
10808 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10809 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10810       char *p, *variant = VariantName(v);
10811       static char b[MSG_SIZ];
10812       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10813            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10814                                                holdingsSize, variant); // cook up sized variant name
10815            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10816            if(StrStr(list, b) == NULL) {
10817                // specific sized variant not known, check if general sizing allowed
10818                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10819                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10820                             boardWidth, boardHeight, holdingsSize, engine);
10821                    return NULL;
10822                }
10823                /* [HGM] here we really should compare with the maximum supported board size */
10824            }
10825       } else snprintf(b, MSG_SIZ,"%s", variant);
10826       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10827       p = StrStr(list, b);
10828       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10829       if(p == NULL) {
10830           // occurs not at all in list, or only as sub-string
10831           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10832           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10833               int l = strlen(variantError);
10834               char *q;
10835               while(p != list && p[-1] != ',') p--;
10836               q = strchr(p, ',');
10837               if(q) *q = NULLCHAR;
10838               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10839               if(q) *q= ',';
10840           }
10841           return NULL;
10842       }
10843       return b;
10844 }
10845
10846 void
10847 InitChessProgram (ChessProgramState *cps, int setup)
10848 /* setup needed to setup FRC opening position */
10849 {
10850     char buf[MSG_SIZ], *b;
10851     if (appData.noChessProgram) return;
10852     hintRequested = FALSE;
10853     bookRequested = FALSE;
10854
10855     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10856     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10857     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10858     if(cps->memSize) { /* [HGM] memory */
10859       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10860         SendToProgram(buf, cps);
10861     }
10862     SendEgtPath(cps); /* [HGM] EGT */
10863     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10864       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10865         SendToProgram(buf, cps);
10866     }
10867
10868     setboardSpoiledMachineBlack = FALSE;
10869     SendToProgram(cps->initString, cps);
10870     if (gameInfo.variant != VariantNormal &&
10871         gameInfo.variant != VariantLoadable
10872         /* [HGM] also send variant if board size non-standard */
10873         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10874
10875       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10876                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10877
10878       if (b == NULL) {
10879         VariantClass v;
10880         char c, *q = cps->variants, *p = strchr(q, ',');
10881         if(p) *p = NULLCHAR;
10882         v = StringToVariant(q);
10883         DisplayError(variantError, 0);
10884         if(v != VariantUnknown && cps == &first) {
10885             int w, h, s;
10886             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10887                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10888             ASSIGN(appData.variant, q);
10889             Reset(TRUE, FALSE);
10890         }
10891         if(p) *p = ',';
10892         return;
10893       }
10894
10895       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10896       SendToProgram(buf, cps);
10897     }
10898     currentlyInitializedVariant = gameInfo.variant;
10899
10900     /* [HGM] send opening position in FRC to first engine */
10901     if(setup) {
10902           SendToProgram("force\n", cps);
10903           SendBoard(cps, 0);
10904           /* engine is now in force mode! Set flag to wake it up after first move. */
10905           setboardSpoiledMachineBlack = 1;
10906     }
10907
10908     if (cps->sendICS) {
10909       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10910       SendToProgram(buf, cps);
10911     }
10912     cps->maybeThinking = FALSE;
10913     cps->offeredDraw = 0;
10914     if (!appData.icsActive) {
10915         SendTimeControl(cps, movesPerSession, timeControl,
10916                         timeIncrement, appData.searchDepth,
10917                         searchTime);
10918     }
10919     if (appData.showThinking
10920         // [HGM] thinking: four options require thinking output to be sent
10921         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10922                                 ) {
10923         SendToProgram("post\n", cps);
10924     }
10925     SendToProgram("hard\n", cps);
10926     if (!appData.ponderNextMove) {
10927         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10928            it without being sure what state we are in first.  "hard"
10929            is not a toggle, so that one is OK.
10930          */
10931         SendToProgram("easy\n", cps);
10932     }
10933     if (cps->usePing) {
10934       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10935       SendToProgram(buf, cps);
10936     }
10937     cps->initDone = TRUE;
10938     ClearEngineOutputPane(cps == &second);
10939 }
10940
10941
10942 char *
10943 ResendOptions (ChessProgramState *cps, int toEngine)
10944 { // send the stored value of the options
10945   int i;
10946   static char buf2[MSG_SIZ*10];
10947   char buf[MSG_SIZ], *p = buf2;
10948   Option *opt = cps->option;
10949   *p = NULLCHAR;
10950   for(i=0; i<cps->nrOptions; i++, opt++) {
10951       *buf = NULLCHAR;
10952       switch(opt->type) {
10953         case Spin:
10954         case Slider:
10955         case CheckBox:
10956             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10957             snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
10958           break;
10959         case ComboBox:
10960             if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
10961             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
10962           break;
10963         default:
10964             if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
10965             snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
10966           break;
10967         case Button:
10968         case SaveButton:
10969           continue;
10970       }
10971       if(*buf) {
10972         if(toEngine) {
10973           snprintf(buf2, MSG_SIZ, "option %s\n", buf);
10974           SendToProgram(buf2, cps);
10975         } else {
10976           if(p != buf2) *p++ = ',';
10977           strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
10978           while(*p) p++;
10979         }
10980       }
10981   }
10982   return buf2;
10983 }
10984
10985 void
10986 StartChessProgram (ChessProgramState *cps)
10987 {
10988     char buf[MSG_SIZ];
10989     int err;
10990
10991     if (appData.noChessProgram) return;
10992     cps->initDone = FALSE;
10993
10994     if (strcmp(cps->host, "localhost") == 0) {
10995         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10996     } else if (*appData.remoteShell == NULLCHAR) {
10997         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10998     } else {
10999         if (*appData.remoteUser == NULLCHAR) {
11000           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
11001                     cps->program);
11002         } else {
11003           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
11004                     cps->host, appData.remoteUser, cps->program);
11005         }
11006         err = StartChildProcess(buf, "", &cps->pr);
11007     }
11008
11009     if (err != 0) {
11010       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
11011         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
11012         if(cps != &first) return;
11013         appData.noChessProgram = TRUE;
11014         ThawUI();
11015         SetNCPMode();
11016 //      DisplayFatalError(buf, err, 1);
11017 //      cps->pr = NoProc;
11018 //      cps->isr = NULL;
11019         return;
11020     }
11021
11022     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
11023     if (cps->protocolVersion > 1) {
11024       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
11025       if(!cps->reload) { // do not clear options when reloading because of -xreuse
11026         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
11027         cps->comboCnt = 0;  //                and values of combo boxes
11028       }
11029       SendToProgram(buf, cps);
11030       if(cps->reload) ResendOptions(cps, TRUE);
11031     } else {
11032       SendToProgram("xboard\n", cps);
11033     }
11034 }
11035
11036 void
11037 TwoMachinesEventIfReady P((void))
11038 {
11039   static int curMess = 0;
11040   if (first.lastPing != first.lastPong) {
11041     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
11042     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11043     return;
11044   }
11045   if (second.lastPing != second.lastPong) {
11046     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
11047     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
11048     return;
11049   }
11050   DisplayMessage("", ""); curMess = 0;
11051   TwoMachinesEvent();
11052 }
11053
11054 char *
11055 MakeName (char *template)
11056 {
11057     time_t clock;
11058     struct tm *tm;
11059     static char buf[MSG_SIZ];
11060     char *p = buf;
11061     int i;
11062
11063     clock = time((time_t *)NULL);
11064     tm = localtime(&clock);
11065
11066     while(*p++ = *template++) if(p[-1] == '%') {
11067         switch(*template++) {
11068           case 0:   *p = 0; return buf;
11069           case 'Y': i = tm->tm_year+1900; break;
11070           case 'y': i = tm->tm_year-100; break;
11071           case 'M': i = tm->tm_mon+1; break;
11072           case 'd': i = tm->tm_mday; break;
11073           case 'h': i = tm->tm_hour; break;
11074           case 'm': i = tm->tm_min; break;
11075           case 's': i = tm->tm_sec; break;
11076           default:  i = 0;
11077         }
11078         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11079     }
11080     return buf;
11081 }
11082
11083 int
11084 CountPlayers (char *p)
11085 {
11086     int n = 0;
11087     while(p = strchr(p, '\n')) p++, n++; // count participants
11088     return n;
11089 }
11090
11091 FILE *
11092 WriteTourneyFile (char *results, FILE *f)
11093 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11094     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11095     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11096         // create a file with tournament description
11097         fprintf(f, "-participants {%s}\n", appData.participants);
11098         fprintf(f, "-seedBase %d\n", appData.seedBase);
11099         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11100         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11101         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11102         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11103         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11104         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11105         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11106         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11107         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11108         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11109         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11110         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11111         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11112         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11113         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11114         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11115         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11116         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11117         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11118         fprintf(f, "-smpCores %d\n", appData.smpCores);
11119         if(searchTime > 0)
11120                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11121         else {
11122                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11123                 fprintf(f, "-tc %s\n", appData.timeControl);
11124                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11125         }
11126         fprintf(f, "-results \"%s\"\n", results);
11127     }
11128     return f;
11129 }
11130
11131 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11132
11133 void
11134 Substitute (char *participants, int expunge)
11135 {
11136     int i, changed, changes=0, nPlayers=0;
11137     char *p, *q, *r, buf[MSG_SIZ];
11138     if(participants == NULL) return;
11139     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11140     r = p = participants; q = appData.participants;
11141     while(*p && *p == *q) {
11142         if(*p == '\n') r = p+1, nPlayers++;
11143         p++; q++;
11144     }
11145     if(*p) { // difference
11146         while(*p && *p++ != '\n')
11147                                  ;
11148         while(*q && *q++ != '\n')
11149                                  ;
11150       changed = nPlayers;
11151         changes = 1 + (strcmp(p, q) != 0);
11152     }
11153     if(changes == 1) { // a single engine mnemonic was changed
11154         q = r; while(*q) nPlayers += (*q++ == '\n');
11155         p = buf; while(*r && (*p = *r++) != '\n') p++;
11156         *p = NULLCHAR;
11157         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11158         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11159         if(mnemonic[i]) { // The substitute is valid
11160             FILE *f;
11161             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11162                 flock(fileno(f), LOCK_EX);
11163                 ParseArgsFromFile(f);
11164                 fseek(f, 0, SEEK_SET);
11165                 FREE(appData.participants); appData.participants = participants;
11166                 if(expunge) { // erase results of replaced engine
11167                     int len = strlen(appData.results), w, b, dummy;
11168                     for(i=0; i<len; i++) {
11169                         Pairing(i, nPlayers, &w, &b, &dummy);
11170                         if((w == changed || b == changed) && appData.results[i] == '*') {
11171                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11172                             fclose(f);
11173                             return;
11174                         }
11175                     }
11176                     for(i=0; i<len; i++) {
11177                         Pairing(i, nPlayers, &w, &b, &dummy);
11178                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11179                     }
11180                 }
11181                 WriteTourneyFile(appData.results, f);
11182                 fclose(f); // release lock
11183                 return;
11184             }
11185         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11186     }
11187     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11188     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11189     free(participants);
11190     return;
11191 }
11192
11193 int
11194 CheckPlayers (char *participants)
11195 {
11196         int i;
11197         char buf[MSG_SIZ], *p;
11198         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11199         while(p = strchr(participants, '\n')) {
11200             *p = NULLCHAR;
11201             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11202             if(!mnemonic[i]) {
11203                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11204                 *p = '\n';
11205                 DisplayError(buf, 0);
11206                 return 1;
11207             }
11208             *p = '\n';
11209             participants = p + 1;
11210         }
11211         return 0;
11212 }
11213
11214 int
11215 CreateTourney (char *name)
11216 {
11217         FILE *f;
11218         if(matchMode && strcmp(name, appData.tourneyFile)) {
11219              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11220         }
11221         if(name[0] == NULLCHAR) {
11222             if(appData.participants[0])
11223                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11224             return 0;
11225         }
11226         f = fopen(name, "r");
11227         if(f) { // file exists
11228             ASSIGN(appData.tourneyFile, name);
11229             ParseArgsFromFile(f); // parse it
11230         } else {
11231             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11232             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11233                 DisplayError(_("Not enough participants"), 0);
11234                 return 0;
11235             }
11236             if(CheckPlayers(appData.participants)) return 0;
11237             ASSIGN(appData.tourneyFile, name);
11238             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11239             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11240         }
11241         fclose(f);
11242         appData.noChessProgram = FALSE;
11243         appData.clockMode = TRUE;
11244         SetGNUMode();
11245         return 1;
11246 }
11247
11248 int
11249 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11250 {
11251     char buf[2*MSG_SIZ], *p, *q;
11252     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11253     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11254     skip = !all && group[0]; // if group requested, we start in skip mode
11255     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11256         p = names; q = buf; header = 0;
11257         while(*p && *p != '\n') *q++ = *p++;
11258         *q = 0;
11259         if(*p == '\n') p++;
11260         if(buf[0] == '#') {
11261             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11262             depth++; // we must be entering a new group
11263             if(all) continue; // suppress printing group headers when complete list requested
11264             header = 1;
11265             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11266         }
11267         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11268         if(engineList[i]) free(engineList[i]);
11269         engineList[i] = strdup(buf);
11270         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11271         if(engineMnemonic[i]) free(engineMnemonic[i]);
11272         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11273             strcat(buf, " (");
11274             sscanf(q + 8, "%s", buf + strlen(buf));
11275             strcat(buf, ")");
11276         }
11277         engineMnemonic[i] = strdup(buf);
11278         i++;
11279     }
11280     engineList[i] = engineMnemonic[i] = NULL;
11281     return i;
11282 }
11283
11284 void
11285 SaveEngineSettings (int n)
11286 {
11287     int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
11288     if(!currentEngine[n] || !currentEngine[n][0]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
11289     p = strstr(firstChessProgramNames, currentEngine[n]);
11290     if(!p) { DisplayMessage("saving failed: engine not found in list", ""); return; } // sanity check; engine could be deleted from list after loading
11291     optionSettings = ResendOptions(n ? &second : &first, FALSE);
11292     len = strlen(currentEngine[n]);
11293     q = p + len; *p = 0; // cut list into head and tail piece
11294     s = strstr(currentEngine[n], "firstOptions");
11295     if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
11296         char *r = s + 14;
11297         while(*r && *r != s[13]) r++;
11298         s[14] = 0; // cut currentEngine into head and tail part, removing old settings
11299         snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
11300     } else if(*optionSettings) {
11301         snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
11302     }
11303     ASSIGN(currentEngine[n], buf); // updated engine line
11304     len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
11305     s = malloc(len);
11306     snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
11307     FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
11308 }
11309
11310 // following implemented as macro to avoid type limitations
11311 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11312
11313 void
11314 SwapEngines (int n)
11315 {   // swap settings for first engine and other engine (so far only some selected options)
11316     int h;
11317     char *p;
11318     if(n == 0) return;
11319     SWAP(directory, p)
11320     SWAP(chessProgram, p)
11321     SWAP(isUCI, h)
11322     SWAP(hasOwnBookUCI, h)
11323     SWAP(protocolVersion, h)
11324     SWAP(reuse, h)
11325     SWAP(scoreIsAbsolute, h)
11326     SWAP(timeOdds, h)
11327     SWAP(logo, p)
11328     SWAP(pgnName, p)
11329     SWAP(pvSAN, h)
11330     SWAP(engOptions, p)
11331     SWAP(engInitString, p)
11332     SWAP(computerString, p)
11333     SWAP(features, p)
11334     SWAP(fenOverride, p)
11335     SWAP(NPS, h)
11336     SWAP(accumulateTC, h)
11337     SWAP(drawDepth, h)
11338     SWAP(host, p)
11339     SWAP(pseudo, h)
11340 }
11341
11342 int
11343 GetEngineLine (char *s, int n)
11344 {
11345     int i;
11346     char buf[MSG_SIZ];
11347     extern char *icsNames;
11348     if(!s || !*s) return 0;
11349     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11350     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11351     if(!mnemonic[i]) return 0;
11352     if(n == 11) return 1; // just testing if there was a match
11353     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11354     if(n == 1) SwapEngines(n);
11355     ParseArgsFromString(buf);
11356     if(n == 1) SwapEngines(n);
11357     if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
11358     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11359         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11360         ParseArgsFromString(buf);
11361     }
11362     return 1;
11363 }
11364
11365 int
11366 SetPlayer (int player, char *p)
11367 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11368     int i;
11369     char buf[MSG_SIZ], *engineName;
11370     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11371     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11372     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11373     if(mnemonic[i]) {
11374         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11375         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11376         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11377         ParseArgsFromString(buf);
11378     } else { // no engine with this nickname is installed!
11379         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11380         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11381         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11382         ModeHighlight();
11383         DisplayError(buf, 0);
11384         return 0;
11385     }
11386     free(engineName);
11387     return i;
11388 }
11389
11390 char *recentEngines;
11391
11392 void
11393 RecentEngineEvent (int nr)
11394 {
11395     int n;
11396 //    SwapEngines(1); // bump first to second
11397 //    ReplaceEngine(&second, 1); // and load it there
11398     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11399     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11400     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11401         ReplaceEngine(&first, 0);
11402         FloatToFront(&appData.recentEngineList, command[n]);
11403     }
11404 }
11405
11406 int
11407 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11408 {   // determine players from game number
11409     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11410
11411     if(appData.tourneyType == 0) {
11412         roundsPerCycle = (nPlayers - 1) | 1;
11413         pairingsPerRound = nPlayers / 2;
11414     } else if(appData.tourneyType > 0) {
11415         roundsPerCycle = nPlayers - appData.tourneyType;
11416         pairingsPerRound = appData.tourneyType;
11417     }
11418     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11419     gamesPerCycle = gamesPerRound * roundsPerCycle;
11420     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11421     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11422     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11423     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11424     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11425     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11426
11427     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11428     if(appData.roundSync) *syncInterval = gamesPerRound;
11429
11430     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11431
11432     if(appData.tourneyType == 0) {
11433         if(curPairing == (nPlayers-1)/2 ) {
11434             *whitePlayer = curRound;
11435             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11436         } else {
11437             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11438             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11439             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11440             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11441         }
11442     } else if(appData.tourneyType > 1) {
11443         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11444         *whitePlayer = curRound + appData.tourneyType;
11445     } else if(appData.tourneyType > 0) {
11446         *whitePlayer = curPairing;
11447         *blackPlayer = curRound + appData.tourneyType;
11448     }
11449
11450     // take care of white/black alternation per round.
11451     // For cycles and games this is already taken care of by default, derived from matchGame!
11452     return curRound & 1;
11453 }
11454
11455 int
11456 NextTourneyGame (int nr, int *swapColors)
11457 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11458     char *p, *q;
11459     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11460     FILE *tf;
11461     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11462     tf = fopen(appData.tourneyFile, "r");
11463     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11464     ParseArgsFromFile(tf); fclose(tf);
11465     InitTimeControls(); // TC might be altered from tourney file
11466
11467     nPlayers = CountPlayers(appData.participants); // count participants
11468     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11469     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11470
11471     if(syncInterval) {
11472         p = q = appData.results;
11473         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11474         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11475             DisplayMessage(_("Waiting for other game(s)"),"");
11476             waitingForGame = TRUE;
11477             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11478             return 0;
11479         }
11480         waitingForGame = FALSE;
11481     }
11482
11483     if(appData.tourneyType < 0) {
11484         if(nr>=0 && !pairingReceived) {
11485             char buf[1<<16];
11486             if(pairing.pr == NoProc) {
11487                 if(!appData.pairingEngine[0]) {
11488                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11489                     return 0;
11490                 }
11491                 StartChessProgram(&pairing); // starts the pairing engine
11492             }
11493             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11494             SendToProgram(buf, &pairing);
11495             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11496             SendToProgram(buf, &pairing);
11497             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11498         }
11499         pairingReceived = 0;                              // ... so we continue here
11500         *swapColors = 0;
11501         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11502         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11503         matchGame = 1; roundNr = nr / syncInterval + 1;
11504     }
11505
11506     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11507
11508     // redefine engines, engine dir, etc.
11509     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11510     if(first.pr == NoProc) {
11511       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11512       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11513     }
11514     if(second.pr == NoProc) {
11515       SwapEngines(1);
11516       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11517       SwapEngines(1);         // and make that valid for second engine by swapping
11518       InitEngine(&second, 1);
11519     }
11520     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11521     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11522     return OK;
11523 }
11524
11525 void
11526 NextMatchGame ()
11527 {   // performs game initialization that does not invoke engines, and then tries to start the game
11528     int res, firstWhite, swapColors = 0;
11529     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11530     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
11531         char buf[MSG_SIZ];
11532         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11533         if(strcmp(buf, currentDebugFile)) { // name has changed
11534             FILE *f = fopen(buf, "w");
11535             if(f) { // if opening the new file failed, just keep using the old one
11536                 ASSIGN(currentDebugFile, buf);
11537                 fclose(debugFP);
11538                 debugFP = f;
11539             }
11540             if(appData.serverFileName) {
11541                 if(serverFP) fclose(serverFP);
11542                 serverFP = fopen(appData.serverFileName, "w");
11543                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11544                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11545             }
11546         }
11547     }
11548     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11549     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11550     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11551     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11552     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11553     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11554     Reset(FALSE, first.pr != NoProc);
11555     res = LoadGameOrPosition(matchGame); // setup game
11556     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11557     if(!res) return; // abort when bad game/pos file
11558     if(appData.epd) {// in EPD mode we make sure first engine is to move
11559         firstWhite = !(forwardMostMove & 1);
11560         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11561         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11562     }
11563     TwoMachinesEvent();
11564 }
11565
11566 void
11567 UserAdjudicationEvent (int result)
11568 {
11569     ChessMove gameResult = GameIsDrawn;
11570
11571     if( result > 0 ) {
11572         gameResult = WhiteWins;
11573     }
11574     else if( result < 0 ) {
11575         gameResult = BlackWins;
11576     }
11577
11578     if( gameMode == TwoMachinesPlay ) {
11579         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11580     }
11581 }
11582
11583
11584 // [HGM] save: calculate checksum of game to make games easily identifiable
11585 int
11586 StringCheckSum (char *s)
11587 {
11588         int i = 0;
11589         if(s==NULL) return 0;
11590         while(*s) i = i*259 + *s++;
11591         return i;
11592 }
11593
11594 int
11595 GameCheckSum ()
11596 {
11597         int i, sum=0;
11598         for(i=backwardMostMove; i<forwardMostMove; i++) {
11599                 sum += pvInfoList[i].depth;
11600                 sum += StringCheckSum(parseList[i]);
11601                 sum += StringCheckSum(commentList[i]);
11602                 sum *= 261;
11603         }
11604         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11605         return sum + StringCheckSum(commentList[i]);
11606 } // end of save patch
11607
11608 void
11609 GameEnds (ChessMove result, char *resultDetails, int whosays)
11610 {
11611     GameMode nextGameMode;
11612     int isIcsGame;
11613     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11614
11615     if(endingGame) return; /* [HGM] crash: forbid recursion */
11616     endingGame = 1;
11617     if(twoBoards) { // [HGM] dual: switch back to one board
11618         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11619         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11620     }
11621     if (appData.debugMode) {
11622       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11623               result, resultDetails ? resultDetails : "(null)", whosays);
11624     }
11625
11626     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11627
11628     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11629
11630     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11631         /* If we are playing on ICS, the server decides when the
11632            game is over, but the engine can offer to draw, claim
11633            a draw, or resign.
11634          */
11635 #if ZIPPY
11636         if (appData.zippyPlay && first.initDone) {
11637             if (result == GameIsDrawn) {
11638                 /* In case draw still needs to be claimed */
11639                 SendToICS(ics_prefix);
11640                 SendToICS("draw\n");
11641             } else if (StrCaseStr(resultDetails, "resign")) {
11642                 SendToICS(ics_prefix);
11643                 SendToICS("resign\n");
11644             }
11645         }
11646 #endif
11647         endingGame = 0; /* [HGM] crash */
11648         return;
11649     }
11650
11651     /* If we're loading the game from a file, stop */
11652     if (whosays == GE_FILE) {
11653       (void) StopLoadGameTimer();
11654       gameFileFP = NULL;
11655     }
11656
11657     /* Cancel draw offers */
11658     first.offeredDraw = second.offeredDraw = 0;
11659
11660     /* If this is an ICS game, only ICS can really say it's done;
11661        if not, anyone can. */
11662     isIcsGame = (gameMode == IcsPlayingWhite ||
11663                  gameMode == IcsPlayingBlack ||
11664                  gameMode == IcsObserving    ||
11665                  gameMode == IcsExamining);
11666
11667     if (!isIcsGame || whosays == GE_ICS) {
11668         /* OK -- not an ICS game, or ICS said it was done */
11669         StopClocks();
11670         if (!isIcsGame && !appData.noChessProgram)
11671           SetUserThinkingEnables();
11672
11673         /* [HGM] if a machine claims the game end we verify this claim */
11674         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11675             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11676                 char claimer;
11677                 ChessMove trueResult = (ChessMove) -1;
11678
11679                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11680                                             first.twoMachinesColor[0] :
11681                                             second.twoMachinesColor[0] ;
11682
11683                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11684                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11685                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11686                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11687                 } else
11688                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11689                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11690                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11691                 } else
11692                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11693                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11694                 }
11695
11696                 // now verify win claims, but not in drop games, as we don't understand those yet
11697                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11698                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11699                     (result == WhiteWins && claimer == 'w' ||
11700                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11701                       if (appData.debugMode) {
11702                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11703                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11704                       }
11705                       if(result != trueResult) {
11706                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11707                               result = claimer == 'w' ? BlackWins : WhiteWins;
11708                               resultDetails = buf;
11709                       }
11710                 } else
11711                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11712                     && (forwardMostMove <= backwardMostMove ||
11713                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11714                         (claimer=='b')==(forwardMostMove&1))
11715                                                                                   ) {
11716                       /* [HGM] verify: draws that were not flagged are false claims */
11717                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11718                       result = claimer == 'w' ? BlackWins : WhiteWins;
11719                       resultDetails = buf;
11720                 }
11721                 /* (Claiming a loss is accepted no questions asked!) */
11722             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11723                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11724                 result = GameUnfinished;
11725                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11726             }
11727             /* [HGM] bare: don't allow bare King to win */
11728             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11729                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11730                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11731                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11732                && result != GameIsDrawn)
11733             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11734                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11735                         int p = (int)boards[forwardMostMove][i][j] - color;
11736                         if(p >= 0 && p <= (int)WhiteKing) k++;
11737                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11738                 }
11739                 if (appData.debugMode) {
11740                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11741                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11742                 }
11743                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11744                         result = GameIsDrawn;
11745                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11746                         resultDetails = buf;
11747                 }
11748             }
11749         }
11750
11751
11752         if(serverMoves != NULL && !loadFlag) { char c = '=';
11753             if(result==WhiteWins) c = '+';
11754             if(result==BlackWins) c = '-';
11755             if(resultDetails != NULL)
11756                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11757         }
11758         if (resultDetails != NULL) {
11759             gameInfo.result = result;
11760             gameInfo.resultDetails = StrSave(resultDetails);
11761
11762             /* display last move only if game was not loaded from file */
11763             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11764                 DisplayMove(currentMove - 1);
11765
11766             if (forwardMostMove != 0) {
11767                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11768                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11769                                                                 ) {
11770                     if (*appData.saveGameFile != NULLCHAR) {
11771                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11772                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11773                         else
11774                         SaveGameToFile(appData.saveGameFile, TRUE);
11775                     } else if (appData.autoSaveGames) {
11776                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11777                     }
11778                     if (*appData.savePositionFile != NULLCHAR) {
11779                         SavePositionToFile(appData.savePositionFile);
11780                     }
11781                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11782                 }
11783             }
11784
11785             /* Tell program how game ended in case it is learning */
11786             /* [HGM] Moved this to after saving the PGN, just in case */
11787             /* engine died and we got here through time loss. In that */
11788             /* case we will get a fatal error writing the pipe, which */
11789             /* would otherwise lose us the PGN.                       */
11790             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11791             /* output during GameEnds should never be fatal anymore   */
11792             if (gameMode == MachinePlaysWhite ||
11793                 gameMode == MachinePlaysBlack ||
11794                 gameMode == TwoMachinesPlay ||
11795                 gameMode == IcsPlayingWhite ||
11796                 gameMode == IcsPlayingBlack ||
11797                 gameMode == BeginningOfGame) {
11798                 char buf[MSG_SIZ];
11799                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11800                         resultDetails);
11801                 if (first.pr != NoProc) {
11802                     SendToProgram(buf, &first);
11803                 }
11804                 if (second.pr != NoProc &&
11805                     gameMode == TwoMachinesPlay) {
11806                     SendToProgram(buf, &second);
11807                 }
11808             }
11809         }
11810
11811         if (appData.icsActive) {
11812             if (appData.quietPlay &&
11813                 (gameMode == IcsPlayingWhite ||
11814                  gameMode == IcsPlayingBlack)) {
11815                 SendToICS(ics_prefix);
11816                 SendToICS("set shout 1\n");
11817             }
11818             nextGameMode = IcsIdle;
11819             ics_user_moved = FALSE;
11820             /* clean up premove.  It's ugly when the game has ended and the
11821              * premove highlights are still on the board.
11822              */
11823             if (gotPremove) {
11824               gotPremove = FALSE;
11825               ClearPremoveHighlights();
11826               DrawPosition(FALSE, boards[currentMove]);
11827             }
11828             if (whosays == GE_ICS) {
11829                 switch (result) {
11830                 case WhiteWins:
11831                     if (gameMode == IcsPlayingWhite)
11832                         PlayIcsWinSound();
11833                     else if(gameMode == IcsPlayingBlack)
11834                         PlayIcsLossSound();
11835                     break;
11836                 case BlackWins:
11837                     if (gameMode == IcsPlayingBlack)
11838                         PlayIcsWinSound();
11839                     else if(gameMode == IcsPlayingWhite)
11840                         PlayIcsLossSound();
11841                     break;
11842                 case GameIsDrawn:
11843                     PlayIcsDrawSound();
11844                     break;
11845                 default:
11846                     PlayIcsUnfinishedSound();
11847                 }
11848             }
11849             if(appData.quitNext) { ExitEvent(0); return; }
11850         } else if (gameMode == EditGame ||
11851                    gameMode == PlayFromGameFile ||
11852                    gameMode == AnalyzeMode ||
11853                    gameMode == AnalyzeFile) {
11854             nextGameMode = gameMode;
11855         } else {
11856             nextGameMode = EndOfGame;
11857         }
11858         pausing = FALSE;
11859         ModeHighlight();
11860     } else {
11861         nextGameMode = gameMode;
11862     }
11863
11864     if (appData.noChessProgram) {
11865         gameMode = nextGameMode;
11866         ModeHighlight();
11867         endingGame = 0; /* [HGM] crash */
11868         return;
11869     }
11870
11871     if (first.reuse) {
11872         /* Put first chess program into idle state */
11873         if (first.pr != NoProc &&
11874             (gameMode == MachinePlaysWhite ||
11875              gameMode == MachinePlaysBlack ||
11876              gameMode == TwoMachinesPlay ||
11877              gameMode == IcsPlayingWhite ||
11878              gameMode == IcsPlayingBlack ||
11879              gameMode == BeginningOfGame)) {
11880             SendToProgram("force\n", &first);
11881             if (first.usePing) {
11882               char buf[MSG_SIZ];
11883               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11884               SendToProgram(buf, &first);
11885             }
11886         }
11887     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11888         /* Kill off first chess program */
11889         if (first.isr != NULL)
11890           RemoveInputSource(first.isr);
11891         first.isr = NULL;
11892
11893         if (first.pr != NoProc) {
11894             ExitAnalyzeMode();
11895             DoSleep( appData.delayBeforeQuit );
11896             SendToProgram("quit\n", &first);
11897             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11898             first.reload = TRUE;
11899         }
11900         first.pr = NoProc;
11901     }
11902     if (second.reuse) {
11903         /* Put second chess program into idle state */
11904         if (second.pr != NoProc &&
11905             gameMode == TwoMachinesPlay) {
11906             SendToProgram("force\n", &second);
11907             if (second.usePing) {
11908               char buf[MSG_SIZ];
11909               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11910               SendToProgram(buf, &second);
11911             }
11912         }
11913     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11914         /* Kill off second chess program */
11915         if (second.isr != NULL)
11916           RemoveInputSource(second.isr);
11917         second.isr = NULL;
11918
11919         if (second.pr != NoProc) {
11920             DoSleep( appData.delayBeforeQuit );
11921             SendToProgram("quit\n", &second);
11922             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11923             second.reload = TRUE;
11924         }
11925         second.pr = NoProc;
11926     }
11927
11928     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11929         char resChar = '=';
11930         switch (result) {
11931         case WhiteWins:
11932           resChar = '+';
11933           if (first.twoMachinesColor[0] == 'w') {
11934             first.matchWins++;
11935           } else {
11936             second.matchWins++;
11937           }
11938           break;
11939         case BlackWins:
11940           resChar = '-';
11941           if (first.twoMachinesColor[0] == 'b') {
11942             first.matchWins++;
11943           } else {
11944             second.matchWins++;
11945           }
11946           break;
11947         case GameUnfinished:
11948           resChar = ' ';
11949         default:
11950           break;
11951         }
11952
11953         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11954         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11955             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11956             ReserveGame(nextGame, resChar); // sets nextGame
11957             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11958             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11959         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11960
11961         if (nextGame <= appData.matchGames && !abortMatch) {
11962             gameMode = nextGameMode;
11963             matchGame = nextGame; // this will be overruled in tourney mode!
11964             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11965             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11966             endingGame = 0; /* [HGM] crash */
11967             return;
11968         } else {
11969             gameMode = nextGameMode;
11970             if(appData.epd) {
11971                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11972                 OutputKibitz(2, buf);
11973                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11974                 OutputKibitz(2, buf);
11975                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11976                 if(second.matchWins) OutputKibitz(2, buf);
11977                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11978                 OutputKibitz(2, buf);
11979             }
11980             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11981                      first.tidy, second.tidy,
11982                      first.matchWins, second.matchWins,
11983                      appData.matchGames - (first.matchWins + second.matchWins));
11984             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11985             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11986             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11987             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11988                 first.twoMachinesColor = "black\n";
11989                 second.twoMachinesColor = "white\n";
11990             } else {
11991                 first.twoMachinesColor = "white\n";
11992                 second.twoMachinesColor = "black\n";
11993             }
11994         }
11995     }
11996     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11997         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11998       ExitAnalyzeMode();
11999     gameMode = nextGameMode;
12000     ModeHighlight();
12001     endingGame = 0;  /* [HGM] crash */
12002     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
12003         if(matchMode == TRUE) { // match through command line: exit with or without popup
12004             if(ranking) {
12005                 ToNrEvent(forwardMostMove);
12006                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
12007                 else ExitEvent(0);
12008             } else DisplayFatalError(buf, 0, 0);
12009         } else { // match through menu; just stop, with or without popup
12010             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
12011             ModeHighlight();
12012             if(ranking){
12013                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
12014             } else DisplayNote(buf);
12015       }
12016       if(ranking) free(ranking);
12017     }
12018 }
12019
12020 /* Assumes program was just initialized (initString sent).
12021    Leaves program in force mode. */
12022 void
12023 FeedMovesToProgram (ChessProgramState *cps, int upto)
12024 {
12025     int i;
12026
12027     if (appData.debugMode)
12028       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
12029               startedFromSetupPosition ? "position and " : "",
12030               backwardMostMove, upto, cps->which);
12031     if(currentlyInitializedVariant != gameInfo.variant) {
12032       char buf[MSG_SIZ];
12033         // [HGM] variantswitch: make engine aware of new variant
12034         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
12035                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
12036                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
12037         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
12038         SendToProgram(buf, cps);
12039         currentlyInitializedVariant = gameInfo.variant;
12040     }
12041     SendToProgram("force\n", cps);
12042     if (startedFromSetupPosition) {
12043         SendBoard(cps, backwardMostMove);
12044     if (appData.debugMode) {
12045         fprintf(debugFP, "feedMoves\n");
12046     }
12047     }
12048     for (i = backwardMostMove; i < upto; i++) {
12049         SendMoveToProgram(i, cps);
12050     }
12051 }
12052
12053
12054 int
12055 ResurrectChessProgram ()
12056 {
12057      /* The chess program may have exited.
12058         If so, restart it and feed it all the moves made so far. */
12059     static int doInit = 0;
12060
12061     if (appData.noChessProgram) return 1;
12062
12063     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
12064         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
12065         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
12066         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
12067     } else {
12068         if (first.pr != NoProc) return 1;
12069         StartChessProgram(&first);
12070     }
12071     InitChessProgram(&first, FALSE);
12072     FeedMovesToProgram(&first, currentMove);
12073
12074     if (!first.sendTime) {
12075         /* can't tell gnuchess what its clock should read,
12076            so we bow to its notion. */
12077         ResetClocks();
12078         timeRemaining[0][currentMove] = whiteTimeRemaining;
12079         timeRemaining[1][currentMove] = blackTimeRemaining;
12080     }
12081
12082     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
12083                 appData.icsEngineAnalyze) && first.analysisSupport) {
12084       SendToProgram("analyze\n", &first);
12085       first.analyzing = TRUE;
12086     }
12087     return 1;
12088 }
12089
12090 /*
12091  * Button procedures
12092  */
12093 void
12094 Reset (int redraw, int init)
12095 {
12096     int i;
12097
12098     if (appData.debugMode) {
12099         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
12100                 redraw, init, gameMode);
12101     }
12102     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12103     deadRanks = 0; // assume entire board is used
12104     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12105     CleanupTail(); // [HGM] vari: delete any stored variations
12106     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12107     pausing = pauseExamInvalid = FALSE;
12108     startedFromSetupPosition = blackPlaysFirst = FALSE;
12109     firstMove = TRUE;
12110     whiteFlag = blackFlag = FALSE;
12111     userOfferedDraw = FALSE;
12112     hintRequested = bookRequested = FALSE;
12113     first.maybeThinking = FALSE;
12114     second.maybeThinking = FALSE;
12115     first.bookSuspend = FALSE; // [HGM] book
12116     second.bookSuspend = FALSE;
12117     thinkOutput[0] = NULLCHAR;
12118     lastHint[0] = NULLCHAR;
12119     ClearGameInfo(&gameInfo);
12120     gameInfo.variant = StringToVariant(appData.variant);
12121     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12122         gameInfo.variant = VariantUnknown;
12123         strncpy(engineVariant, appData.variant, MSG_SIZ);
12124     }
12125     ics_user_moved = ics_clock_paused = FALSE;
12126     ics_getting_history = H_FALSE;
12127     ics_gamenum = -1;
12128     white_holding[0] = black_holding[0] = NULLCHAR;
12129     ClearProgramStats();
12130     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12131
12132     ResetFrontEnd();
12133     ClearHighlights();
12134     flipView = appData.flipView;
12135     ClearPremoveHighlights();
12136     gotPremove = FALSE;
12137     alarmSounded = FALSE;
12138     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12139
12140     GameEnds(EndOfFile, NULL, GE_PLAYER);
12141     if(appData.serverMovesName != NULL) {
12142         /* [HGM] prepare to make moves file for broadcasting */
12143         clock_t t = clock();
12144         if(serverMoves != NULL) fclose(serverMoves);
12145         serverMoves = fopen(appData.serverMovesName, "r");
12146         if(serverMoves != NULL) {
12147             fclose(serverMoves);
12148             /* delay 15 sec before overwriting, so all clients can see end */
12149             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12150         }
12151         serverMoves = fopen(appData.serverMovesName, "w");
12152     }
12153
12154     ExitAnalyzeMode();
12155     gameMode = BeginningOfGame;
12156     ModeHighlight();
12157     if(appData.icsActive) gameInfo.variant = VariantNormal;
12158     currentMove = forwardMostMove = backwardMostMove = 0;
12159     MarkTargetSquares(1);
12160     InitPosition(redraw);
12161     for (i = 0; i < MAX_MOVES; i++) {
12162         if (commentList[i] != NULL) {
12163             free(commentList[i]);
12164             commentList[i] = NULL;
12165         }
12166     }
12167     ResetClocks();
12168     timeRemaining[0][0] = whiteTimeRemaining;
12169     timeRemaining[1][0] = blackTimeRemaining;
12170
12171     if (first.pr == NoProc) {
12172         StartChessProgram(&first);
12173     }
12174     if (init) {
12175             InitChessProgram(&first, startedFromSetupPosition);
12176     }
12177     DisplayTitle("");
12178     DisplayMessage("", "");
12179     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12180     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12181     ClearMap();        // [HGM] exclude: invalidate map
12182 }
12183
12184 void
12185 AutoPlayGameLoop ()
12186 {
12187     for (;;) {
12188         if (!AutoPlayOneMove())
12189           return;
12190         if (matchMode || appData.timeDelay == 0)
12191           continue;
12192         if (appData.timeDelay < 0)
12193           return;
12194         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12195         break;
12196     }
12197 }
12198
12199 void
12200 AnalyzeNextGame()
12201 {
12202     ReloadGame(1); // next game
12203 }
12204
12205 int
12206 AutoPlayOneMove ()
12207 {
12208     int fromX, fromY, toX, toY;
12209
12210     if (appData.debugMode) {
12211       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12212     }
12213
12214     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12215       return FALSE;
12216
12217     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12218       pvInfoList[currentMove].depth = programStats.depth;
12219       pvInfoList[currentMove].score = programStats.score;
12220       pvInfoList[currentMove].time  = 0;
12221       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12222       else { // append analysis of final position as comment
12223         char buf[MSG_SIZ];
12224         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12225         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12226       }
12227       programStats.depth = 0;
12228     }
12229
12230     if (currentMove >= forwardMostMove) {
12231       if(gameMode == AnalyzeFile) {
12232           if(appData.loadGameIndex == -1) {
12233             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12234           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12235           } else {
12236           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12237         }
12238       }
12239 //      gameMode = EndOfGame;
12240 //      ModeHighlight();
12241
12242       /* [AS] Clear current move marker at the end of a game */
12243       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12244
12245       return FALSE;
12246     }
12247
12248     toX = moveList[currentMove][2] - AAA;
12249     toY = moveList[currentMove][3] - ONE;
12250
12251     if (moveList[currentMove][1] == '@') {
12252         if (appData.highlightLastMove) {
12253             SetHighlights(-1, -1, toX, toY);
12254         }
12255     } else {
12256         fromX = moveList[currentMove][0] - AAA;
12257         fromY = moveList[currentMove][1] - ONE;
12258
12259         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12260
12261         if(moveList[currentMove][4] == ';') { // multi-leg
12262             killX = moveList[currentMove][5] - AAA;
12263             killY = moveList[currentMove][6] - ONE;
12264         }
12265         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12266         killX = killY = -1;
12267
12268         if (appData.highlightLastMove) {
12269             SetHighlights(fromX, fromY, toX, toY);
12270         }
12271     }
12272     DisplayMove(currentMove);
12273     SendMoveToProgram(currentMove++, &first);
12274     DisplayBothClocks();
12275     DrawPosition(FALSE, boards[currentMove]);
12276     // [HGM] PV info: always display, routine tests if empty
12277     DisplayComment(currentMove - 1, commentList[currentMove]);
12278     return TRUE;
12279 }
12280
12281
12282 int
12283 LoadGameOneMove (ChessMove readAhead)
12284 {
12285     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12286     char promoChar = NULLCHAR;
12287     ChessMove moveType;
12288     char move[MSG_SIZ];
12289     char *p, *q;
12290
12291     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12292         gameMode != AnalyzeMode && gameMode != Training) {
12293         gameFileFP = NULL;
12294         return FALSE;
12295     }
12296
12297     yyboardindex = forwardMostMove;
12298     if (readAhead != EndOfFile) {
12299       moveType = readAhead;
12300     } else {
12301       if (gameFileFP == NULL)
12302           return FALSE;
12303       moveType = (ChessMove) Myylex();
12304     }
12305
12306     done = FALSE;
12307     switch (moveType) {
12308       case Comment:
12309         if (appData.debugMode)
12310           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12311         p = yy_text;
12312
12313         /* append the comment but don't display it */
12314         AppendComment(currentMove, p, FALSE);
12315         return TRUE;
12316
12317       case WhiteCapturesEnPassant:
12318       case BlackCapturesEnPassant:
12319       case WhitePromotion:
12320       case BlackPromotion:
12321       case WhiteNonPromotion:
12322       case BlackNonPromotion:
12323       case NormalMove:
12324       case FirstLeg:
12325       case WhiteKingSideCastle:
12326       case WhiteQueenSideCastle:
12327       case BlackKingSideCastle:
12328       case BlackQueenSideCastle:
12329       case WhiteKingSideCastleWild:
12330       case WhiteQueenSideCastleWild:
12331       case BlackKingSideCastleWild:
12332       case BlackQueenSideCastleWild:
12333       /* PUSH Fabien */
12334       case WhiteHSideCastleFR:
12335       case WhiteASideCastleFR:
12336       case BlackHSideCastleFR:
12337       case BlackASideCastleFR:
12338       /* POP Fabien */
12339         if (appData.debugMode)
12340           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12341         fromX = currentMoveString[0] - AAA;
12342         fromY = currentMoveString[1] - ONE;
12343         toX = currentMoveString[2] - AAA;
12344         toY = currentMoveString[3] - ONE;
12345         promoChar = currentMoveString[4];
12346         if(promoChar == ';') promoChar = currentMoveString[7];
12347         break;
12348
12349       case WhiteDrop:
12350       case BlackDrop:
12351         if (appData.debugMode)
12352           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12353         fromX = moveType == WhiteDrop ?
12354           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12355         (int) CharToPiece(ToLower(currentMoveString[0]));
12356         fromY = DROP_RANK;
12357         toX = currentMoveString[2] - AAA;
12358         toY = currentMoveString[3] - ONE;
12359         break;
12360
12361       case WhiteWins:
12362       case BlackWins:
12363       case GameIsDrawn:
12364       case GameUnfinished:
12365         if (appData.debugMode)
12366           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12367         p = strchr(yy_text, '{');
12368         if (p == NULL) p = strchr(yy_text, '(');
12369         if (p == NULL) {
12370             p = yy_text;
12371             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12372         } else {
12373             q = strchr(p, *p == '{' ? '}' : ')');
12374             if (q != NULL) *q = NULLCHAR;
12375             p++;
12376         }
12377         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12378         GameEnds(moveType, p, GE_FILE);
12379         done = TRUE;
12380         if (cmailMsgLoaded) {
12381             ClearHighlights();
12382             flipView = WhiteOnMove(currentMove);
12383             if (moveType == GameUnfinished) flipView = !flipView;
12384             if (appData.debugMode)
12385               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12386         }
12387         break;
12388
12389       case EndOfFile:
12390         if (appData.debugMode)
12391           fprintf(debugFP, "Parser hit end of file\n");
12392         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12393           case MT_NONE:
12394           case MT_CHECK:
12395             break;
12396           case MT_CHECKMATE:
12397           case MT_STAINMATE:
12398             if (WhiteOnMove(currentMove)) {
12399                 GameEnds(BlackWins, "Black mates", GE_FILE);
12400             } else {
12401                 GameEnds(WhiteWins, "White mates", GE_FILE);
12402             }
12403             break;
12404           case MT_STALEMATE:
12405             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12406             break;
12407         }
12408         done = TRUE;
12409         break;
12410
12411       case MoveNumberOne:
12412         if (lastLoadGameStart == GNUChessGame) {
12413             /* GNUChessGames have numbers, but they aren't move numbers */
12414             if (appData.debugMode)
12415               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12416                       yy_text, (int) moveType);
12417             return LoadGameOneMove(EndOfFile); /* tail recursion */
12418         }
12419         /* else fall thru */
12420
12421       case XBoardGame:
12422       case GNUChessGame:
12423       case PGNTag:
12424         /* Reached start of next game in file */
12425         if (appData.debugMode)
12426           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12427         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12428           case MT_NONE:
12429           case MT_CHECK:
12430             break;
12431           case MT_CHECKMATE:
12432           case MT_STAINMATE:
12433             if (WhiteOnMove(currentMove)) {
12434                 GameEnds(BlackWins, "Black mates", GE_FILE);
12435             } else {
12436                 GameEnds(WhiteWins, "White mates", GE_FILE);
12437             }
12438             break;
12439           case MT_STALEMATE:
12440             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12441             break;
12442         }
12443         done = TRUE;
12444         break;
12445
12446       case PositionDiagram:     /* should not happen; ignore */
12447       case ElapsedTime:         /* ignore */
12448       case NAG:                 /* ignore */
12449         if (appData.debugMode)
12450           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12451                   yy_text, (int) moveType);
12452         return LoadGameOneMove(EndOfFile); /* tail recursion */
12453
12454       case IllegalMove:
12455         if (appData.testLegality) {
12456             if (appData.debugMode)
12457               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12458             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12459                     (forwardMostMove / 2) + 1,
12460                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12461             DisplayError(move, 0);
12462             done = TRUE;
12463         } else {
12464             if (appData.debugMode)
12465               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12466                       yy_text, currentMoveString);
12467             if(currentMoveString[1] == '@') {
12468                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12469                 fromY = DROP_RANK;
12470             } else {
12471                 fromX = currentMoveString[0] - AAA;
12472                 fromY = currentMoveString[1] - ONE;
12473             }
12474             toX = currentMoveString[2] - AAA;
12475             toY = currentMoveString[3] - ONE;
12476             promoChar = currentMoveString[4];
12477         }
12478         break;
12479
12480       case AmbiguousMove:
12481         if (appData.debugMode)
12482           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12483         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12484                 (forwardMostMove / 2) + 1,
12485                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12486         DisplayError(move, 0);
12487         done = TRUE;
12488         break;
12489
12490       default:
12491       case ImpossibleMove:
12492         if (appData.debugMode)
12493           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12494         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12495                 (forwardMostMove / 2) + 1,
12496                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12497         DisplayError(move, 0);
12498         done = TRUE;
12499         break;
12500     }
12501
12502     if (done) {
12503         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12504             DrawPosition(FALSE, boards[currentMove]);
12505             DisplayBothClocks();
12506             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12507               DisplayComment(currentMove - 1, commentList[currentMove]);
12508         }
12509         (void) StopLoadGameTimer();
12510         gameFileFP = NULL;
12511         cmailOldMove = forwardMostMove;
12512         return FALSE;
12513     } else {
12514         /* currentMoveString is set as a side-effect of yylex */
12515
12516         thinkOutput[0] = NULLCHAR;
12517         MakeMove(fromX, fromY, toX, toY, promoChar);
12518         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12519         currentMove = forwardMostMove;
12520         return TRUE;
12521     }
12522 }
12523
12524 /* Load the nth game from the given file */
12525 int
12526 LoadGameFromFile (char *filename, int n, char *title, int useList)
12527 {
12528     FILE *f;
12529     char buf[MSG_SIZ];
12530
12531     if (strcmp(filename, "-") == 0) {
12532         f = stdin;
12533         title = "stdin";
12534     } else {
12535         f = fopen(filename, "rb");
12536         if (f == NULL) {
12537           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12538             DisplayError(buf, errno);
12539             return FALSE;
12540         }
12541     }
12542     if (fseek(f, 0, 0) == -1) {
12543         /* f is not seekable; probably a pipe */
12544         useList = FALSE;
12545     }
12546     if (useList && n == 0) {
12547         int error = GameListBuild(f);
12548         if (error) {
12549             DisplayError(_("Cannot build game list"), error);
12550         } else if (!ListEmpty(&gameList) &&
12551                    ((ListGame *) gameList.tailPred)->number > 1) {
12552             GameListPopUp(f, title);
12553             return TRUE;
12554         }
12555         GameListDestroy();
12556         n = 1;
12557     }
12558     if (n == 0) n = 1;
12559     return LoadGame(f, n, title, FALSE);
12560 }
12561
12562
12563 void
12564 MakeRegisteredMove ()
12565 {
12566     int fromX, fromY, toX, toY;
12567     char promoChar;
12568     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12569         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12570           case CMAIL_MOVE:
12571           case CMAIL_DRAW:
12572             if (appData.debugMode)
12573               fprintf(debugFP, "Restoring %s for game %d\n",
12574                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12575
12576             thinkOutput[0] = NULLCHAR;
12577             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12578             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12579             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12580             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12581             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12582             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12583             MakeMove(fromX, fromY, toX, toY, promoChar);
12584             ShowMove(fromX, fromY, toX, toY);
12585
12586             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12587               case MT_NONE:
12588               case MT_CHECK:
12589                 break;
12590
12591               case MT_CHECKMATE:
12592               case MT_STAINMATE:
12593                 if (WhiteOnMove(currentMove)) {
12594                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12595                 } else {
12596                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12597                 }
12598                 break;
12599
12600               case MT_STALEMATE:
12601                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12602                 break;
12603             }
12604
12605             break;
12606
12607           case CMAIL_RESIGN:
12608             if (WhiteOnMove(currentMove)) {
12609                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12610             } else {
12611                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12612             }
12613             break;
12614
12615           case CMAIL_ACCEPT:
12616             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12617             break;
12618
12619           default:
12620             break;
12621         }
12622     }
12623
12624     return;
12625 }
12626
12627 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12628 int
12629 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12630 {
12631     int retVal;
12632
12633     if (gameNumber > nCmailGames) {
12634         DisplayError(_("No more games in this message"), 0);
12635         return FALSE;
12636     }
12637     if (f == lastLoadGameFP) {
12638         int offset = gameNumber - lastLoadGameNumber;
12639         if (offset == 0) {
12640             cmailMsg[0] = NULLCHAR;
12641             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12642                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12643                 nCmailMovesRegistered--;
12644             }
12645             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12646             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12647                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12648             }
12649         } else {
12650             if (! RegisterMove()) return FALSE;
12651         }
12652     }
12653
12654     retVal = LoadGame(f, gameNumber, title, useList);
12655
12656     /* Make move registered during previous look at this game, if any */
12657     MakeRegisteredMove();
12658
12659     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12660         commentList[currentMove]
12661           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12662         DisplayComment(currentMove - 1, commentList[currentMove]);
12663     }
12664
12665     return retVal;
12666 }
12667
12668 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12669 int
12670 ReloadGame (int offset)
12671 {
12672     int gameNumber = lastLoadGameNumber + offset;
12673     if (lastLoadGameFP == NULL) {
12674         DisplayError(_("No game has been loaded yet"), 0);
12675         return FALSE;
12676     }
12677     if (gameNumber <= 0) {
12678         DisplayError(_("Can't back up any further"), 0);
12679         return FALSE;
12680     }
12681     if (cmailMsgLoaded) {
12682         return CmailLoadGame(lastLoadGameFP, gameNumber,
12683                              lastLoadGameTitle, lastLoadGameUseList);
12684     } else {
12685         return LoadGame(lastLoadGameFP, gameNumber,
12686                         lastLoadGameTitle, lastLoadGameUseList);
12687     }
12688 }
12689
12690 int keys[EmptySquare+1];
12691
12692 int
12693 PositionMatches (Board b1, Board b2)
12694 {
12695     int r, f, sum=0;
12696     switch(appData.searchMode) {
12697         case 1: return CompareWithRights(b1, b2);
12698         case 2:
12699             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12700                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12701             }
12702             return TRUE;
12703         case 3:
12704             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12705               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12706                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12707             }
12708             return sum==0;
12709         case 4:
12710             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12711                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12712             }
12713             return sum==0;
12714     }
12715     return TRUE;
12716 }
12717
12718 #define Q_PROMO  4
12719 #define Q_EP     3
12720 #define Q_BCASTL 2
12721 #define Q_WCASTL 1
12722
12723 int pieceList[256], quickBoard[256];
12724 ChessSquare pieceType[256] = { EmptySquare };
12725 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12726 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12727 int soughtTotal, turn;
12728 Boolean epOK, flipSearch;
12729
12730 typedef struct {
12731     unsigned char piece, to;
12732 } Move;
12733
12734 #define DSIZE (250000)
12735
12736 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12737 Move *moveDatabase = initialSpace;
12738 unsigned int movePtr, dataSize = DSIZE;
12739
12740 int
12741 MakePieceList (Board board, int *counts)
12742 {
12743     int r, f, n=Q_PROMO, total=0;
12744     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12745     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12746         int sq = f + (r<<4);
12747         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12748             quickBoard[sq] = ++n;
12749             pieceList[n] = sq;
12750             pieceType[n] = board[r][f];
12751             counts[board[r][f]]++;
12752             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12753             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12754             total++;
12755         }
12756     }
12757     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12758     return total;
12759 }
12760
12761 void
12762 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12763 {
12764     int sq = fromX + (fromY<<4);
12765     int piece = quickBoard[sq], rook;
12766     quickBoard[sq] = 0;
12767     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12768     if(piece == pieceList[1] && fromY == toY) {
12769       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12770         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12771         moveDatabase[movePtr++].piece = Q_WCASTL;
12772         quickBoard[sq] = piece;
12773         piece = quickBoard[from]; quickBoard[from] = 0;
12774         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12775       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12776         quickBoard[sq] = 0; // remove Rook
12777         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12778         moveDatabase[movePtr++].piece = Q_WCASTL;
12779         quickBoard[sq] = pieceList[1]; // put King
12780         piece = rook;
12781         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12782       }
12783     } else
12784     if(piece == pieceList[2] && fromY == toY) {
12785       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12786         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12787         moveDatabase[movePtr++].piece = Q_BCASTL;
12788         quickBoard[sq] = piece;
12789         piece = quickBoard[from]; quickBoard[from] = 0;
12790         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12791       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12792         quickBoard[sq] = 0; // remove Rook
12793         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12794         moveDatabase[movePtr++].piece = Q_BCASTL;
12795         quickBoard[sq] = pieceList[2]; // put King
12796         piece = rook;
12797         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12798       }
12799     } else
12800     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12801         quickBoard[(fromY<<4)+toX] = 0;
12802         moveDatabase[movePtr].piece = Q_EP;
12803         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12804         moveDatabase[movePtr].to = sq;
12805     } else
12806     if(promoPiece != pieceType[piece]) {
12807         moveDatabase[movePtr++].piece = Q_PROMO;
12808         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12809     }
12810     moveDatabase[movePtr].piece = piece;
12811     quickBoard[sq] = piece;
12812     movePtr++;
12813 }
12814
12815 int
12816 PackGame (Board board)
12817 {
12818     Move *newSpace = NULL;
12819     moveDatabase[movePtr].piece = 0; // terminate previous game
12820     if(movePtr > dataSize) {
12821         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12822         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12823         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12824         if(newSpace) {
12825             int i;
12826             Move *p = moveDatabase, *q = newSpace;
12827             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12828             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12829             moveDatabase = newSpace;
12830         } else { // calloc failed, we must be out of memory. Too bad...
12831             dataSize = 0; // prevent calloc events for all subsequent games
12832             return 0;     // and signal this one isn't cached
12833         }
12834     }
12835     movePtr++;
12836     MakePieceList(board, counts);
12837     return movePtr;
12838 }
12839
12840 int
12841 QuickCompare (Board board, int *minCounts, int *maxCounts)
12842 {   // compare according to search mode
12843     int r, f;
12844     switch(appData.searchMode)
12845     {
12846       case 1: // exact position match
12847         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12848         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12849             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12850         }
12851         break;
12852       case 2: // can have extra material on empty squares
12853         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12854             if(board[r][f] == EmptySquare) continue;
12855             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12856         }
12857         break;
12858       case 3: // material with exact Pawn structure
12859         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12860             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12861             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12862         } // fall through to material comparison
12863       case 4: // exact material
12864         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12865         break;
12866       case 6: // material range with given imbalance
12867         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12868         // fall through to range comparison
12869       case 5: // material range
12870         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12871     }
12872     return TRUE;
12873 }
12874
12875 int
12876 QuickScan (Board board, Move *move)
12877 {   // reconstruct game,and compare all positions in it
12878     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12879     do {
12880         int piece = move->piece;
12881         int to = move->to, from = pieceList[piece];
12882         if(found < 0) { // if already found just scan to game end for final piece count
12883           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12884            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12885            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12886                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12887             ) {
12888             static int lastCounts[EmptySquare+1];
12889             int i;
12890             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12891             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12892           } else stretch = 0;
12893           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12894           if(found >= 0 && !appData.minPieces) return found;
12895         }
12896         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12897           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12898           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12899             piece = (++move)->piece;
12900             from = pieceList[piece];
12901             counts[pieceType[piece]]--;
12902             pieceType[piece] = (ChessSquare) move->to;
12903             counts[move->to]++;
12904           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12905             counts[pieceType[quickBoard[to]]]--;
12906             quickBoard[to] = 0; total--;
12907             move++;
12908             continue;
12909           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12910             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12911             from  = pieceList[piece]; // so this must be King
12912             quickBoard[from] = 0;
12913             pieceList[piece] = to;
12914             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12915             quickBoard[from] = 0; // rook
12916             quickBoard[to] = piece;
12917             to = move->to; piece = move->piece;
12918             goto aftercastle;
12919           }
12920         }
12921         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12922         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12923         quickBoard[from] = 0;
12924       aftercastle:
12925         quickBoard[to] = piece;
12926         pieceList[piece] = to;
12927         cnt++; turn ^= 3;
12928         move++;
12929     } while(1);
12930 }
12931
12932 void
12933 InitSearch ()
12934 {
12935     int r, f;
12936     flipSearch = FALSE;
12937     CopyBoard(soughtBoard, boards[currentMove]);
12938     soughtTotal = MakePieceList(soughtBoard, maxSought);
12939     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12940     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12941     CopyBoard(reverseBoard, boards[currentMove]);
12942     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12943         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12944         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12945         reverseBoard[r][f] = piece;
12946     }
12947     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12948     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12949     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12950                  || (boards[currentMove][CASTLING][2] == NoRights ||
12951                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12952                  && (boards[currentMove][CASTLING][5] == NoRights ||
12953                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12954       ) {
12955         flipSearch = TRUE;
12956         CopyBoard(flipBoard, soughtBoard);
12957         CopyBoard(rotateBoard, reverseBoard);
12958         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12959             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12960             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12961         }
12962     }
12963     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12964     if(appData.searchMode >= 5) {
12965         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12966         MakePieceList(soughtBoard, minSought);
12967         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12968     }
12969     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12970         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12971 }
12972
12973 GameInfo dummyInfo;
12974 static int creatingBook;
12975
12976 int
12977 GameContainsPosition (FILE *f, ListGame *lg)
12978 {
12979     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12980     int fromX, fromY, toX, toY;
12981     char promoChar;
12982     static int initDone=FALSE;
12983
12984     // weed out games based on numerical tag comparison
12985     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12986     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12987     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12988     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12989     if(!initDone) {
12990         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12991         initDone = TRUE;
12992     }
12993     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12994     else CopyBoard(boards[scratch], initialPosition); // default start position
12995     if(lg->moves) {
12996         turn = btm + 1;
12997         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12998         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12999     }
13000     if(btm) plyNr++;
13001     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13002     fseek(f, lg->offset, 0);
13003     yynewfile(f);
13004     while(1) {
13005         yyboardindex = scratch;
13006         quickFlag = plyNr+1;
13007         next = Myylex();
13008         quickFlag = 0;
13009         switch(next) {
13010             case PGNTag:
13011                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
13012             default:
13013                 continue;
13014
13015             case XBoardGame:
13016             case GNUChessGame:
13017                 if(plyNr) return -1; // after we have seen moves, this is for new game
13018               continue;
13019
13020             case AmbiguousMove: // we cannot reconstruct the game beyond these two
13021             case ImpossibleMove:
13022             case WhiteWins: // game ends here with these four
13023             case BlackWins:
13024             case GameIsDrawn:
13025             case GameUnfinished:
13026                 return -1;
13027
13028             case IllegalMove:
13029                 if(appData.testLegality) return -1;
13030             case WhiteCapturesEnPassant:
13031             case BlackCapturesEnPassant:
13032             case WhitePromotion:
13033             case BlackPromotion:
13034             case WhiteNonPromotion:
13035             case BlackNonPromotion:
13036             case NormalMove:
13037             case FirstLeg:
13038             case WhiteKingSideCastle:
13039             case WhiteQueenSideCastle:
13040             case BlackKingSideCastle:
13041             case BlackQueenSideCastle:
13042             case WhiteKingSideCastleWild:
13043             case WhiteQueenSideCastleWild:
13044             case BlackKingSideCastleWild:
13045             case BlackQueenSideCastleWild:
13046             case WhiteHSideCastleFR:
13047             case WhiteASideCastleFR:
13048             case BlackHSideCastleFR:
13049             case BlackASideCastleFR:
13050                 fromX = currentMoveString[0] - AAA;
13051                 fromY = currentMoveString[1] - ONE;
13052                 toX = currentMoveString[2] - AAA;
13053                 toY = currentMoveString[3] - ONE;
13054                 promoChar = currentMoveString[4];
13055                 break;
13056             case WhiteDrop:
13057             case BlackDrop:
13058                 fromX = next == WhiteDrop ?
13059                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
13060                   (int) CharToPiece(ToLower(currentMoveString[0]));
13061                 fromY = DROP_RANK;
13062                 toX = currentMoveString[2] - AAA;
13063                 toY = currentMoveString[3] - ONE;
13064                 promoChar = 0;
13065                 break;
13066         }
13067         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
13068         plyNr++;
13069         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
13070         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
13071         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
13072         if(appData.findMirror) {
13073             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
13074             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
13075         }
13076     }
13077 }
13078
13079 /* Load the nth game from open file f */
13080 int
13081 LoadGame (FILE *f, int gameNumber, char *title, int useList)
13082 {
13083     ChessMove cm;
13084     char buf[MSG_SIZ];
13085     int gn = gameNumber;
13086     ListGame *lg = NULL;
13087     int numPGNTags = 0, i;
13088     int err, pos = -1;
13089     GameMode oldGameMode;
13090     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
13091     char oldName[MSG_SIZ];
13092
13093     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
13094
13095     if (appData.debugMode)
13096         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
13097
13098     if (gameMode == Training )
13099         SetTrainingModeOff();
13100
13101     oldGameMode = gameMode;
13102     if (gameMode != BeginningOfGame) {
13103       Reset(FALSE, TRUE);
13104     }
13105     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13106
13107     gameFileFP = f;
13108     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13109         fclose(lastLoadGameFP);
13110     }
13111
13112     if (useList) {
13113         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13114
13115         if (lg) {
13116             fseek(f, lg->offset, 0);
13117             GameListHighlight(gameNumber);
13118             pos = lg->position;
13119             gn = 1;
13120         }
13121         else {
13122             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13123               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13124             else
13125             DisplayError(_("Game number out of range"), 0);
13126             return FALSE;
13127         }
13128     } else {
13129         GameListDestroy();
13130         if (fseek(f, 0, 0) == -1) {
13131             if (f == lastLoadGameFP ?
13132                 gameNumber == lastLoadGameNumber + 1 :
13133                 gameNumber == 1) {
13134                 gn = 1;
13135             } else {
13136                 DisplayError(_("Can't seek on game file"), 0);
13137                 return FALSE;
13138             }
13139         }
13140     }
13141     lastLoadGameFP = f;
13142     lastLoadGameNumber = gameNumber;
13143     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13144     lastLoadGameUseList = useList;
13145
13146     yynewfile(f);
13147
13148     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13149       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13150                 lg->gameInfo.black);
13151             DisplayTitle(buf);
13152     } else if (*title != NULLCHAR) {
13153         if (gameNumber > 1) {
13154           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13155             DisplayTitle(buf);
13156         } else {
13157             DisplayTitle(title);
13158         }
13159     }
13160
13161     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13162         gameMode = PlayFromGameFile;
13163         ModeHighlight();
13164     }
13165
13166     currentMove = forwardMostMove = backwardMostMove = 0;
13167     CopyBoard(boards[0], initialPosition);
13168     StopClocks();
13169
13170     /*
13171      * Skip the first gn-1 games in the file.
13172      * Also skip over anything that precedes an identifiable
13173      * start of game marker, to avoid being confused by
13174      * garbage at the start of the file.  Currently
13175      * recognized start of game markers are the move number "1",
13176      * the pattern "gnuchess .* game", the pattern
13177      * "^[#;%] [^ ]* game file", and a PGN tag block.
13178      * A game that starts with one of the latter two patterns
13179      * will also have a move number 1, possibly
13180      * following a position diagram.
13181      * 5-4-02: Let's try being more lenient and allowing a game to
13182      * start with an unnumbered move.  Does that break anything?
13183      */
13184     cm = lastLoadGameStart = EndOfFile;
13185     while (gn > 0) {
13186         yyboardindex = forwardMostMove;
13187         cm = (ChessMove) Myylex();
13188         switch (cm) {
13189           case EndOfFile:
13190             if (cmailMsgLoaded) {
13191                 nCmailGames = CMAIL_MAX_GAMES - gn;
13192             } else {
13193                 Reset(TRUE, TRUE);
13194                 DisplayError(_("Game not found in file"), 0);
13195             }
13196             return FALSE;
13197
13198           case GNUChessGame:
13199           case XBoardGame:
13200             gn--;
13201             lastLoadGameStart = cm;
13202             break;
13203
13204           case MoveNumberOne:
13205             switch (lastLoadGameStart) {
13206               case GNUChessGame:
13207               case XBoardGame:
13208               case PGNTag:
13209                 break;
13210               case MoveNumberOne:
13211               case EndOfFile:
13212                 gn--;           /* count this game */
13213                 lastLoadGameStart = cm;
13214                 break;
13215               default:
13216                 /* impossible */
13217                 break;
13218             }
13219             break;
13220
13221           case PGNTag:
13222             switch (lastLoadGameStart) {
13223               case GNUChessGame:
13224               case PGNTag:
13225               case MoveNumberOne:
13226               case EndOfFile:
13227                 gn--;           /* count this game */
13228                 lastLoadGameStart = cm;
13229                 break;
13230               case XBoardGame:
13231                 lastLoadGameStart = cm; /* game counted already */
13232                 break;
13233               default:
13234                 /* impossible */
13235                 break;
13236             }
13237             if (gn > 0) {
13238                 do {
13239                     yyboardindex = forwardMostMove;
13240                     cm = (ChessMove) Myylex();
13241                 } while (cm == PGNTag || cm == Comment);
13242             }
13243             break;
13244
13245           case WhiteWins:
13246           case BlackWins:
13247           case GameIsDrawn:
13248             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13249                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13250                     != CMAIL_OLD_RESULT) {
13251                     nCmailResults ++ ;
13252                     cmailResult[  CMAIL_MAX_GAMES
13253                                 - gn - 1] = CMAIL_OLD_RESULT;
13254                 }
13255             }
13256             break;
13257
13258           case NormalMove:
13259           case FirstLeg:
13260             /* Only a NormalMove can be at the start of a game
13261              * without a position diagram. */
13262             if (lastLoadGameStart == EndOfFile ) {
13263               gn--;
13264               lastLoadGameStart = MoveNumberOne;
13265             }
13266             break;
13267
13268           default:
13269             break;
13270         }
13271     }
13272
13273     if (appData.debugMode)
13274       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13275
13276     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13277
13278     if (cm == XBoardGame) {
13279         /* Skip any header junk before position diagram and/or move 1 */
13280         for (;;) {
13281             yyboardindex = forwardMostMove;
13282             cm = (ChessMove) Myylex();
13283
13284             if (cm == EndOfFile ||
13285                 cm == GNUChessGame || cm == XBoardGame) {
13286                 /* Empty game; pretend end-of-file and handle later */
13287                 cm = EndOfFile;
13288                 break;
13289             }
13290
13291             if (cm == MoveNumberOne || cm == PositionDiagram ||
13292                 cm == PGNTag || cm == Comment)
13293               break;
13294         }
13295     } else if (cm == GNUChessGame) {
13296         if (gameInfo.event != NULL) {
13297             free(gameInfo.event);
13298         }
13299         gameInfo.event = StrSave(yy_text);
13300     }
13301
13302     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13303     while (cm == PGNTag) {
13304         if (appData.debugMode)
13305           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13306         err = ParsePGNTag(yy_text, &gameInfo);
13307         if (!err) numPGNTags++;
13308
13309         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13310         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13311             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13312             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13313             InitPosition(TRUE);
13314             oldVariant = gameInfo.variant;
13315             if (appData.debugMode)
13316               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13317         }
13318
13319
13320         if (gameInfo.fen != NULL) {
13321           Board initial_position;
13322           startedFromSetupPosition = TRUE;
13323           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13324             Reset(TRUE, TRUE);
13325             DisplayError(_("Bad FEN position in file"), 0);
13326             return FALSE;
13327           }
13328           CopyBoard(boards[0], initial_position);
13329           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13330             CopyBoard(initialPosition, initial_position);
13331           if (blackPlaysFirst) {
13332             currentMove = forwardMostMove = backwardMostMove = 1;
13333             CopyBoard(boards[1], initial_position);
13334             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13335             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13336             timeRemaining[0][1] = whiteTimeRemaining;
13337             timeRemaining[1][1] = blackTimeRemaining;
13338             if (commentList[0] != NULL) {
13339               commentList[1] = commentList[0];
13340               commentList[0] = NULL;
13341             }
13342           } else {
13343             currentMove = forwardMostMove = backwardMostMove = 0;
13344           }
13345           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13346           {   int i;
13347               initialRulePlies = FENrulePlies;
13348               for( i=0; i< nrCastlingRights; i++ )
13349                   initialRights[i] = initial_position[CASTLING][i];
13350           }
13351           yyboardindex = forwardMostMove;
13352           free(gameInfo.fen);
13353           gameInfo.fen = NULL;
13354         }
13355
13356         yyboardindex = forwardMostMove;
13357         cm = (ChessMove) Myylex();
13358
13359         /* Handle comments interspersed among the tags */
13360         while (cm == Comment) {
13361             char *p;
13362             if (appData.debugMode)
13363               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13364             p = yy_text;
13365             AppendComment(currentMove, p, FALSE);
13366             yyboardindex = forwardMostMove;
13367             cm = (ChessMove) Myylex();
13368         }
13369     }
13370
13371     /* don't rely on existence of Event tag since if game was
13372      * pasted from clipboard the Event tag may not exist
13373      */
13374     if (numPGNTags > 0){
13375         char *tags;
13376         if (gameInfo.variant == VariantNormal) {
13377           VariantClass v = StringToVariant(gameInfo.event);
13378           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13379           if(v < VariantShogi) gameInfo.variant = v;
13380         }
13381         if (!matchMode) {
13382           if( appData.autoDisplayTags ) {
13383             tags = PGNTags(&gameInfo);
13384             TagsPopUp(tags, CmailMsg());
13385             free(tags);
13386           }
13387         }
13388     } else {
13389         /* Make something up, but don't display it now */
13390         SetGameInfo();
13391         TagsPopDown();
13392     }
13393
13394     if (cm == PositionDiagram) {
13395         int i, j;
13396         char *p;
13397         Board initial_position;
13398
13399         if (appData.debugMode)
13400           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13401
13402         if (!startedFromSetupPosition) {
13403             p = yy_text;
13404             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13405               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13406                 switch (*p) {
13407                   case '{':
13408                   case '[':
13409                   case '-':
13410                   case ' ':
13411                   case '\t':
13412                   case '\n':
13413                   case '\r':
13414                     break;
13415                   default:
13416                     initial_position[i][j++] = CharToPiece(*p);
13417                     break;
13418                 }
13419             while (*p == ' ' || *p == '\t' ||
13420                    *p == '\n' || *p == '\r') p++;
13421
13422             if (strncmp(p, "black", strlen("black"))==0)
13423               blackPlaysFirst = TRUE;
13424             else
13425               blackPlaysFirst = FALSE;
13426             startedFromSetupPosition = TRUE;
13427
13428             CopyBoard(boards[0], initial_position);
13429             if (blackPlaysFirst) {
13430                 currentMove = forwardMostMove = backwardMostMove = 1;
13431                 CopyBoard(boards[1], initial_position);
13432                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13433                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13434                 timeRemaining[0][1] = whiteTimeRemaining;
13435                 timeRemaining[1][1] = blackTimeRemaining;
13436                 if (commentList[0] != NULL) {
13437                     commentList[1] = commentList[0];
13438                     commentList[0] = NULL;
13439                 }
13440             } else {
13441                 currentMove = forwardMostMove = backwardMostMove = 0;
13442             }
13443         }
13444         yyboardindex = forwardMostMove;
13445         cm = (ChessMove) Myylex();
13446     }
13447
13448   if(!creatingBook) {
13449     if (first.pr == NoProc) {
13450         StartChessProgram(&first);
13451     }
13452     InitChessProgram(&first, FALSE);
13453     if(gameInfo.variant == VariantUnknown && *oldName) {
13454         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13455         gameInfo.variant = v;
13456     }
13457     SendToProgram("force\n", &first);
13458     if (startedFromSetupPosition) {
13459         SendBoard(&first, forwardMostMove);
13460     if (appData.debugMode) {
13461         fprintf(debugFP, "Load Game\n");
13462     }
13463         DisplayBothClocks();
13464     }
13465   }
13466
13467     /* [HGM] server: flag to write setup moves in broadcast file as one */
13468     loadFlag = appData.suppressLoadMoves;
13469
13470     while (cm == Comment) {
13471         char *p;
13472         if (appData.debugMode)
13473           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13474         p = yy_text;
13475         AppendComment(currentMove, p, FALSE);
13476         yyboardindex = forwardMostMove;
13477         cm = (ChessMove) Myylex();
13478     }
13479
13480     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13481         cm == WhiteWins || cm == BlackWins ||
13482         cm == GameIsDrawn || cm == GameUnfinished) {
13483         DisplayMessage("", _("No moves in game"));
13484         if (cmailMsgLoaded) {
13485             if (appData.debugMode)
13486               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13487             ClearHighlights();
13488             flipView = FALSE;
13489         }
13490         DrawPosition(FALSE, boards[currentMove]);
13491         DisplayBothClocks();
13492         gameMode = EditGame;
13493         ModeHighlight();
13494         gameFileFP = NULL;
13495         cmailOldMove = 0;
13496         return TRUE;
13497     }
13498
13499     // [HGM] PV info: routine tests if comment empty
13500     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13501         DisplayComment(currentMove - 1, commentList[currentMove]);
13502     }
13503     if (!matchMode && appData.timeDelay != 0)
13504       DrawPosition(FALSE, boards[currentMove]);
13505
13506     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13507       programStats.ok_to_send = 1;
13508     }
13509
13510     /* if the first token after the PGN tags is a move
13511      * and not move number 1, retrieve it from the parser
13512      */
13513     if (cm != MoveNumberOne)
13514         LoadGameOneMove(cm);
13515
13516     /* load the remaining moves from the file */
13517     while (LoadGameOneMove(EndOfFile)) {
13518       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13519       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13520     }
13521
13522     /* rewind to the start of the game */
13523     currentMove = backwardMostMove;
13524
13525     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13526
13527     if (oldGameMode == AnalyzeFile) {
13528       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13529       AnalyzeFileEvent();
13530     } else
13531     if (oldGameMode == AnalyzeMode) {
13532       AnalyzeFileEvent();
13533     }
13534
13535     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13536         long int w, b; // [HGM] adjourn: restore saved clock times
13537         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13538         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13539             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13540             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13541         }
13542     }
13543
13544     if(creatingBook) return TRUE;
13545     if (!matchMode && pos > 0) {
13546         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13547     } else
13548     if (matchMode || appData.timeDelay == 0) {
13549       ToEndEvent();
13550     } else if (appData.timeDelay > 0) {
13551       AutoPlayGameLoop();
13552     }
13553
13554     if (appData.debugMode)
13555         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13556
13557     loadFlag = 0; /* [HGM] true game starts */
13558     return TRUE;
13559 }
13560
13561 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13562 int
13563 ReloadPosition (int offset)
13564 {
13565     int positionNumber = lastLoadPositionNumber + offset;
13566     if (lastLoadPositionFP == NULL) {
13567         DisplayError(_("No position has been loaded yet"), 0);
13568         return FALSE;
13569     }
13570     if (positionNumber <= 0) {
13571         DisplayError(_("Can't back up any further"), 0);
13572         return FALSE;
13573     }
13574     return LoadPosition(lastLoadPositionFP, positionNumber,
13575                         lastLoadPositionTitle);
13576 }
13577
13578 /* Load the nth position from the given file */
13579 int
13580 LoadPositionFromFile (char *filename, int n, char *title)
13581 {
13582     FILE *f;
13583     char buf[MSG_SIZ];
13584
13585     if (strcmp(filename, "-") == 0) {
13586         return LoadPosition(stdin, n, "stdin");
13587     } else {
13588         f = fopen(filename, "rb");
13589         if (f == NULL) {
13590             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13591             DisplayError(buf, errno);
13592             return FALSE;
13593         } else {
13594             return LoadPosition(f, n, title);
13595         }
13596     }
13597 }
13598
13599 /* Load the nth position from the given open file, and close it */
13600 int
13601 LoadPosition (FILE *f, int positionNumber, char *title)
13602 {
13603     char *p, line[MSG_SIZ];
13604     Board initial_position;
13605     int i, j, fenMode, pn;
13606
13607     if (gameMode == Training )
13608         SetTrainingModeOff();
13609
13610     if (gameMode != BeginningOfGame) {
13611         Reset(FALSE, TRUE);
13612     }
13613     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13614         fclose(lastLoadPositionFP);
13615     }
13616     if (positionNumber == 0) positionNumber = 1;
13617     lastLoadPositionFP = f;
13618     lastLoadPositionNumber = positionNumber;
13619     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13620     if (first.pr == NoProc && !appData.noChessProgram) {
13621       StartChessProgram(&first);
13622       InitChessProgram(&first, FALSE);
13623     }
13624     pn = positionNumber;
13625     if (positionNumber < 0) {
13626         /* Negative position number means to seek to that byte offset */
13627         if (fseek(f, -positionNumber, 0) == -1) {
13628             DisplayError(_("Can't seek on position file"), 0);
13629             return FALSE;
13630         };
13631         pn = 1;
13632     } else {
13633         if (fseek(f, 0, 0) == -1) {
13634             if (f == lastLoadPositionFP ?
13635                 positionNumber == lastLoadPositionNumber + 1 :
13636                 positionNumber == 1) {
13637                 pn = 1;
13638             } else {
13639                 DisplayError(_("Can't seek on position file"), 0);
13640                 return FALSE;
13641             }
13642         }
13643     }
13644     /* See if this file is FEN or old-style xboard */
13645     if (fgets(line, MSG_SIZ, f) == NULL) {
13646         DisplayError(_("Position not found in file"), 0);
13647         return FALSE;
13648     }
13649     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13650     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13651
13652     if (pn >= 2) {
13653         if (fenMode || line[0] == '#') pn--;
13654         while (pn > 0) {
13655             /* skip positions before number pn */
13656             if (fgets(line, MSG_SIZ, f) == NULL) {
13657                 Reset(TRUE, TRUE);
13658                 DisplayError(_("Position not found in file"), 0);
13659                 return FALSE;
13660             }
13661             if (fenMode || line[0] == '#') pn--;
13662         }
13663     }
13664
13665     if (fenMode) {
13666         char *p;
13667         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13668             DisplayError(_("Bad FEN position in file"), 0);
13669             return FALSE;
13670         }
13671         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13672             sscanf(p+4, "%[^;]", bestMove);
13673         } else *bestMove = NULLCHAR;
13674         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13675             sscanf(p+4, "%[^;]", avoidMove);
13676         } else *avoidMove = NULLCHAR;
13677     } else {
13678         (void) fgets(line, MSG_SIZ, f);
13679         (void) fgets(line, MSG_SIZ, f);
13680
13681         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13682             (void) fgets(line, MSG_SIZ, f);
13683             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13684                 if (*p == ' ')
13685                   continue;
13686                 initial_position[i][j++] = CharToPiece(*p);
13687             }
13688         }
13689
13690         blackPlaysFirst = FALSE;
13691         if (!feof(f)) {
13692             (void) fgets(line, MSG_SIZ, f);
13693             if (strncmp(line, "black", strlen("black"))==0)
13694               blackPlaysFirst = TRUE;
13695         }
13696     }
13697     startedFromSetupPosition = TRUE;
13698
13699     CopyBoard(boards[0], initial_position);
13700     if (blackPlaysFirst) {
13701         currentMove = forwardMostMove = backwardMostMove = 1;
13702         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13703         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13704         CopyBoard(boards[1], initial_position);
13705         DisplayMessage("", _("Black to play"));
13706     } else {
13707         currentMove = forwardMostMove = backwardMostMove = 0;
13708         DisplayMessage("", _("White to play"));
13709     }
13710     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13711     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13712         SendToProgram("force\n", &first);
13713         SendBoard(&first, forwardMostMove);
13714     }
13715     if (appData.debugMode) {
13716 int i, j;
13717   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13718   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13719         fprintf(debugFP, "Load Position\n");
13720     }
13721
13722     if (positionNumber > 1) {
13723       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13724         DisplayTitle(line);
13725     } else {
13726         DisplayTitle(title);
13727     }
13728     gameMode = EditGame;
13729     ModeHighlight();
13730     ResetClocks();
13731     timeRemaining[0][1] = whiteTimeRemaining;
13732     timeRemaining[1][1] = blackTimeRemaining;
13733     DrawPosition(FALSE, boards[currentMove]);
13734
13735     return TRUE;
13736 }
13737
13738
13739 void
13740 CopyPlayerNameIntoFileName (char **dest, char *src)
13741 {
13742     while (*src != NULLCHAR && *src != ',') {
13743         if (*src == ' ') {
13744             *(*dest)++ = '_';
13745             src++;
13746         } else {
13747             *(*dest)++ = *src++;
13748         }
13749     }
13750 }
13751
13752 char *
13753 DefaultFileName (char *ext)
13754 {
13755     static char def[MSG_SIZ];
13756     char *p;
13757
13758     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13759         p = def;
13760         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13761         *p++ = '-';
13762         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13763         *p++ = '.';
13764         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13765     } else {
13766         def[0] = NULLCHAR;
13767     }
13768     return def;
13769 }
13770
13771 /* Save the current game to the given file */
13772 int
13773 SaveGameToFile (char *filename, int append)
13774 {
13775     FILE *f;
13776     char buf[MSG_SIZ];
13777     int result, i, t,tot=0;
13778
13779     if (strcmp(filename, "-") == 0) {
13780         return SaveGame(stdout, 0, NULL);
13781     } else {
13782         for(i=0; i<10; i++) { // upto 10 tries
13783              f = fopen(filename, append ? "a" : "w");
13784              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13785              if(f || errno != 13) break;
13786              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13787              tot += t;
13788         }
13789         if (f == NULL) {
13790             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13791             DisplayError(buf, errno);
13792             return FALSE;
13793         } else {
13794             safeStrCpy(buf, lastMsg, MSG_SIZ);
13795             DisplayMessage(_("Waiting for access to save file"), "");
13796             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13797             DisplayMessage(_("Saving game"), "");
13798             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13799             result = SaveGame(f, 0, NULL);
13800             DisplayMessage(buf, "");
13801             return result;
13802         }
13803     }
13804 }
13805
13806 char *
13807 SavePart (char *str)
13808 {
13809     static char buf[MSG_SIZ];
13810     char *p;
13811
13812     p = strchr(str, ' ');
13813     if (p == NULL) return str;
13814     strncpy(buf, str, p - str);
13815     buf[p - str] = NULLCHAR;
13816     return buf;
13817 }
13818
13819 #define PGN_MAX_LINE 75
13820
13821 #define PGN_SIDE_WHITE  0
13822 #define PGN_SIDE_BLACK  1
13823
13824 static int
13825 FindFirstMoveOutOfBook (int side)
13826 {
13827     int result = -1;
13828
13829     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13830         int index = backwardMostMove;
13831         int has_book_hit = 0;
13832
13833         if( (index % 2) != side ) {
13834             index++;
13835         }
13836
13837         while( index < forwardMostMove ) {
13838             /* Check to see if engine is in book */
13839             int depth = pvInfoList[index].depth;
13840             int score = pvInfoList[index].score;
13841             int in_book = 0;
13842
13843             if( depth <= 2 ) {
13844                 in_book = 1;
13845             }
13846             else if( score == 0 && depth == 63 ) {
13847                 in_book = 1; /* Zappa */
13848             }
13849             else if( score == 2 && depth == 99 ) {
13850                 in_book = 1; /* Abrok */
13851             }
13852
13853             has_book_hit += in_book;
13854
13855             if( ! in_book ) {
13856                 result = index;
13857
13858                 break;
13859             }
13860
13861             index += 2;
13862         }
13863     }
13864
13865     return result;
13866 }
13867
13868 void
13869 GetOutOfBookInfo (char * buf)
13870 {
13871     int oob[2];
13872     int i;
13873     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13874
13875     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13876     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13877
13878     *buf = '\0';
13879
13880     if( oob[0] >= 0 || oob[1] >= 0 ) {
13881         for( i=0; i<2; i++ ) {
13882             int idx = oob[i];
13883
13884             if( idx >= 0 ) {
13885                 if( i > 0 && oob[0] >= 0 ) {
13886                     strcat( buf, "   " );
13887                 }
13888
13889                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13890                 sprintf( buf+strlen(buf), "%s%.2f",
13891                     pvInfoList[idx].score >= 0 ? "+" : "",
13892                     pvInfoList[idx].score / 100.0 );
13893             }
13894         }
13895     }
13896 }
13897
13898 /* Save game in PGN style */
13899 static void
13900 SaveGamePGN2 (FILE *f)
13901 {
13902     int i, offset, linelen, newblock;
13903 //    char *movetext;
13904     char numtext[32];
13905     int movelen, numlen, blank;
13906     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13907
13908     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13909
13910     PrintPGNTags(f, &gameInfo);
13911
13912     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13913
13914     if (backwardMostMove > 0 || startedFromSetupPosition) {
13915         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13916         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13917         fprintf(f, "\n{--------------\n");
13918         PrintPosition(f, backwardMostMove);
13919         fprintf(f, "--------------}\n");
13920         free(fen);
13921     }
13922     else {
13923         /* [AS] Out of book annotation */
13924         if( appData.saveOutOfBookInfo ) {
13925             char buf[64];
13926
13927             GetOutOfBookInfo( buf );
13928
13929             if( buf[0] != '\0' ) {
13930                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13931             }
13932         }
13933
13934         fprintf(f, "\n");
13935     }
13936
13937     i = backwardMostMove;
13938     linelen = 0;
13939     newblock = TRUE;
13940
13941     while (i < forwardMostMove) {
13942         /* Print comments preceding this move */
13943         if (commentList[i] != NULL) {
13944             if (linelen > 0) fprintf(f, "\n");
13945             fprintf(f, "%s", commentList[i]);
13946             linelen = 0;
13947             newblock = TRUE;
13948         }
13949
13950         /* Format move number */
13951         if ((i % 2) == 0)
13952           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13953         else
13954           if (newblock)
13955             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13956           else
13957             numtext[0] = NULLCHAR;
13958
13959         numlen = strlen(numtext);
13960         newblock = FALSE;
13961
13962         /* Print move number */
13963         blank = linelen > 0 && numlen > 0;
13964         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13965             fprintf(f, "\n");
13966             linelen = 0;
13967             blank = 0;
13968         }
13969         if (blank) {
13970             fprintf(f, " ");
13971             linelen++;
13972         }
13973         fprintf(f, "%s", numtext);
13974         linelen += numlen;
13975
13976         /* Get move */
13977         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13978         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13979
13980         /* Print move */
13981         blank = linelen > 0 && movelen > 0;
13982         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13983             fprintf(f, "\n");
13984             linelen = 0;
13985             blank = 0;
13986         }
13987         if (blank) {
13988             fprintf(f, " ");
13989             linelen++;
13990         }
13991         fprintf(f, "%s", move_buffer);
13992         linelen += movelen;
13993
13994         /* [AS] Add PV info if present */
13995         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13996             /* [HGM] add time */
13997             char buf[MSG_SIZ]; int seconds;
13998
13999             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
14000
14001             if( seconds <= 0)
14002               buf[0] = 0;
14003             else
14004               if( seconds < 30 )
14005                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
14006               else
14007                 {
14008                   seconds = (seconds + 4)/10; // round to full seconds
14009                   if( seconds < 60 )
14010                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
14011                   else
14012                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
14013                 }
14014
14015             if(appData.cumulativeTimePGN) {
14016                 snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
14017             }
14018
14019             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
14020                       pvInfoList[i].score >= 0 ? "+" : "",
14021                       pvInfoList[i].score / 100.0,
14022                       pvInfoList[i].depth,
14023                       buf );
14024
14025             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
14026
14027             /* Print score/depth */
14028             blank = linelen > 0 && movelen > 0;
14029             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
14030                 fprintf(f, "\n");
14031                 linelen = 0;
14032                 blank = 0;
14033             }
14034             if (blank) {
14035                 fprintf(f, " ");
14036                 linelen++;
14037             }
14038             fprintf(f, "%s", move_buffer);
14039             linelen += movelen;
14040         }
14041
14042         i++;
14043     }
14044
14045     /* Start a new line */
14046     if (linelen > 0) fprintf(f, "\n");
14047
14048     /* Print comments after last move */
14049     if (commentList[i] != NULL) {
14050         fprintf(f, "%s\n", commentList[i]);
14051     }
14052
14053     /* Print result */
14054     if (gameInfo.resultDetails != NULL &&
14055         gameInfo.resultDetails[0] != NULLCHAR) {
14056         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
14057         if(gameInfo.result == GameUnfinished && appData.clockMode &&
14058            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
14059             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
14060         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
14061     } else {
14062         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14063     }
14064 }
14065
14066 /* Save game in PGN style and close the file */
14067 int
14068 SaveGamePGN (FILE *f)
14069 {
14070     SaveGamePGN2(f);
14071     fclose(f);
14072     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14073     return TRUE;
14074 }
14075
14076 /* Save game in old style and close the file */
14077 int
14078 SaveGameOldStyle (FILE *f)
14079 {
14080     int i, offset;
14081     time_t tm;
14082
14083     tm = time((time_t *) NULL);
14084
14085     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
14086     PrintOpponents(f);
14087
14088     if (backwardMostMove > 0 || startedFromSetupPosition) {
14089         fprintf(f, "\n[--------------\n");
14090         PrintPosition(f, backwardMostMove);
14091         fprintf(f, "--------------]\n");
14092     } else {
14093         fprintf(f, "\n");
14094     }
14095
14096     i = backwardMostMove;
14097     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
14098
14099     while (i < forwardMostMove) {
14100         if (commentList[i] != NULL) {
14101             fprintf(f, "[%s]\n", commentList[i]);
14102         }
14103
14104         if ((i % 2) == 1) {
14105             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
14106             i++;
14107         } else {
14108             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14109             i++;
14110             if (commentList[i] != NULL) {
14111                 fprintf(f, "\n");
14112                 continue;
14113             }
14114             if (i >= forwardMostMove) {
14115                 fprintf(f, "\n");
14116                 break;
14117             }
14118             fprintf(f, "%s\n", parseList[i]);
14119             i++;
14120         }
14121     }
14122
14123     if (commentList[i] != NULL) {
14124         fprintf(f, "[%s]\n", commentList[i]);
14125     }
14126
14127     /* This isn't really the old style, but it's close enough */
14128     if (gameInfo.resultDetails != NULL &&
14129         gameInfo.resultDetails[0] != NULLCHAR) {
14130         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14131                 gameInfo.resultDetails);
14132     } else {
14133         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14134     }
14135
14136     fclose(f);
14137     return TRUE;
14138 }
14139
14140 /* Save the current game to open file f and close the file */
14141 int
14142 SaveGame (FILE *f, int dummy, char *dummy2)
14143 {
14144     if (gameMode == EditPosition) EditPositionDone(TRUE);
14145     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14146     if (appData.oldSaveStyle)
14147       return SaveGameOldStyle(f);
14148     else
14149       return SaveGamePGN(f);
14150 }
14151
14152 /* Save the current position to the given file */
14153 int
14154 SavePositionToFile (char *filename)
14155 {
14156     FILE *f;
14157     char buf[MSG_SIZ];
14158
14159     if (strcmp(filename, "-") == 0) {
14160         return SavePosition(stdout, 0, NULL);
14161     } else {
14162         f = fopen(filename, "a");
14163         if (f == NULL) {
14164             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14165             DisplayError(buf, errno);
14166             return FALSE;
14167         } else {
14168             safeStrCpy(buf, lastMsg, MSG_SIZ);
14169             DisplayMessage(_("Waiting for access to save file"), "");
14170             flock(fileno(f), LOCK_EX); // [HGM] lock
14171             DisplayMessage(_("Saving position"), "");
14172             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14173             SavePosition(f, 0, NULL);
14174             DisplayMessage(buf, "");
14175             return TRUE;
14176         }
14177     }
14178 }
14179
14180 /* Save the current position to the given open file and close the file */
14181 int
14182 SavePosition (FILE *f, int dummy, char *dummy2)
14183 {
14184     time_t tm;
14185     char *fen;
14186
14187     if (gameMode == EditPosition) EditPositionDone(TRUE);
14188     if (appData.oldSaveStyle) {
14189         tm = time((time_t *) NULL);
14190
14191         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14192         PrintOpponents(f);
14193         fprintf(f, "[--------------\n");
14194         PrintPosition(f, currentMove);
14195         fprintf(f, "--------------]\n");
14196     } else {
14197         fen = PositionToFEN(currentMove, NULL, 1);
14198         fprintf(f, "%s\n", fen);
14199         free(fen);
14200     }
14201     fclose(f);
14202     return TRUE;
14203 }
14204
14205 void
14206 ReloadCmailMsgEvent (int unregister)
14207 {
14208 #if !WIN32
14209     static char *inFilename = NULL;
14210     static char *outFilename;
14211     int i;
14212     struct stat inbuf, outbuf;
14213     int status;
14214
14215     /* Any registered moves are unregistered if unregister is set, */
14216     /* i.e. invoked by the signal handler */
14217     if (unregister) {
14218         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14219             cmailMoveRegistered[i] = FALSE;
14220             if (cmailCommentList[i] != NULL) {
14221                 free(cmailCommentList[i]);
14222                 cmailCommentList[i] = NULL;
14223             }
14224         }
14225         nCmailMovesRegistered = 0;
14226     }
14227
14228     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14229         cmailResult[i] = CMAIL_NOT_RESULT;
14230     }
14231     nCmailResults = 0;
14232
14233     if (inFilename == NULL) {
14234         /* Because the filenames are static they only get malloced once  */
14235         /* and they never get freed                                      */
14236         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14237         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14238
14239         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14240         sprintf(outFilename, "%s.out", appData.cmailGameName);
14241     }
14242
14243     status = stat(outFilename, &outbuf);
14244     if (status < 0) {
14245         cmailMailedMove = FALSE;
14246     } else {
14247         status = stat(inFilename, &inbuf);
14248         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14249     }
14250
14251     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14252        counts the games, notes how each one terminated, etc.
14253
14254        It would be nice to remove this kludge and instead gather all
14255        the information while building the game list.  (And to keep it
14256        in the game list nodes instead of having a bunch of fixed-size
14257        parallel arrays.)  Note this will require getting each game's
14258        termination from the PGN tags, as the game list builder does
14259        not process the game moves.  --mann
14260        */
14261     cmailMsgLoaded = TRUE;
14262     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14263
14264     /* Load first game in the file or popup game menu */
14265     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14266
14267 #endif /* !WIN32 */
14268     return;
14269 }
14270
14271 int
14272 RegisterMove ()
14273 {
14274     FILE *f;
14275     char string[MSG_SIZ];
14276
14277     if (   cmailMailedMove
14278         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14279         return TRUE;            /* Allow free viewing  */
14280     }
14281
14282     /* Unregister move to ensure that we don't leave RegisterMove        */
14283     /* with the move registered when the conditions for registering no   */
14284     /* longer hold                                                       */
14285     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14286         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14287         nCmailMovesRegistered --;
14288
14289         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14290           {
14291               free(cmailCommentList[lastLoadGameNumber - 1]);
14292               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14293           }
14294     }
14295
14296     if (cmailOldMove == -1) {
14297         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14298         return FALSE;
14299     }
14300
14301     if (currentMove > cmailOldMove + 1) {
14302         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14303         return FALSE;
14304     }
14305
14306     if (currentMove < cmailOldMove) {
14307         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14308         return FALSE;
14309     }
14310
14311     if (forwardMostMove > currentMove) {
14312         /* Silently truncate extra moves */
14313         TruncateGame();
14314     }
14315
14316     if (   (currentMove == cmailOldMove + 1)
14317         || (   (currentMove == cmailOldMove)
14318             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14319                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14320         if (gameInfo.result != GameUnfinished) {
14321             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14322         }
14323
14324         if (commentList[currentMove] != NULL) {
14325             cmailCommentList[lastLoadGameNumber - 1]
14326               = StrSave(commentList[currentMove]);
14327         }
14328         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14329
14330         if (appData.debugMode)
14331           fprintf(debugFP, "Saving %s for game %d\n",
14332                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14333
14334         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14335
14336         f = fopen(string, "w");
14337         if (appData.oldSaveStyle) {
14338             SaveGameOldStyle(f); /* also closes the file */
14339
14340             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14341             f = fopen(string, "w");
14342             SavePosition(f, 0, NULL); /* also closes the file */
14343         } else {
14344             fprintf(f, "{--------------\n");
14345             PrintPosition(f, currentMove);
14346             fprintf(f, "--------------}\n\n");
14347
14348             SaveGame(f, 0, NULL); /* also closes the file*/
14349         }
14350
14351         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14352         nCmailMovesRegistered ++;
14353     } else if (nCmailGames == 1) {
14354         DisplayError(_("You have not made a move yet"), 0);
14355         return FALSE;
14356     }
14357
14358     return TRUE;
14359 }
14360
14361 void
14362 MailMoveEvent ()
14363 {
14364 #if !WIN32
14365     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14366     FILE *commandOutput;
14367     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14368     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14369     int nBuffers;
14370     int i;
14371     int archived;
14372     char *arcDir;
14373
14374     if (! cmailMsgLoaded) {
14375         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14376         return;
14377     }
14378
14379     if (nCmailGames == nCmailResults) {
14380         DisplayError(_("No unfinished games"), 0);
14381         return;
14382     }
14383
14384 #if CMAIL_PROHIBIT_REMAIL
14385     if (cmailMailedMove) {
14386       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);
14387         DisplayError(msg, 0);
14388         return;
14389     }
14390 #endif
14391
14392     if (! (cmailMailedMove || RegisterMove())) return;
14393
14394     if (   cmailMailedMove
14395         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14396       snprintf(string, MSG_SIZ, partCommandString,
14397                appData.debugMode ? " -v" : "", appData.cmailGameName);
14398         commandOutput = popen(string, "r");
14399
14400         if (commandOutput == NULL) {
14401             DisplayError(_("Failed to invoke cmail"), 0);
14402         } else {
14403             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14404                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14405             }
14406             if (nBuffers > 1) {
14407                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14408                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14409                 nBytes = MSG_SIZ - 1;
14410             } else {
14411                 (void) memcpy(msg, buffer, nBytes);
14412             }
14413             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14414
14415             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14416                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14417
14418                 archived = TRUE;
14419                 for (i = 0; i < nCmailGames; i ++) {
14420                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14421                         archived = FALSE;
14422                     }
14423                 }
14424                 if (   archived
14425                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14426                         != NULL)) {
14427                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14428                            arcDir,
14429                            appData.cmailGameName,
14430                            gameInfo.date);
14431                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14432                     cmailMsgLoaded = FALSE;
14433                 }
14434             }
14435
14436             DisplayInformation(msg);
14437             pclose(commandOutput);
14438         }
14439     } else {
14440         if ((*cmailMsg) != '\0') {
14441             DisplayInformation(cmailMsg);
14442         }
14443     }
14444
14445     return;
14446 #endif /* !WIN32 */
14447 }
14448
14449 char *
14450 CmailMsg ()
14451 {
14452 #if WIN32
14453     return NULL;
14454 #else
14455     int  prependComma = 0;
14456     char number[5];
14457     char string[MSG_SIZ];       /* Space for game-list */
14458     int  i;
14459
14460     if (!cmailMsgLoaded) return "";
14461
14462     if (cmailMailedMove) {
14463       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14464     } else {
14465         /* Create a list of games left */
14466       snprintf(string, MSG_SIZ, "[");
14467         for (i = 0; i < nCmailGames; i ++) {
14468             if (! (   cmailMoveRegistered[i]
14469                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14470                 if (prependComma) {
14471                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14472                 } else {
14473                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14474                     prependComma = 1;
14475                 }
14476
14477                 strcat(string, number);
14478             }
14479         }
14480         strcat(string, "]");
14481
14482         if (nCmailMovesRegistered + nCmailResults == 0) {
14483             switch (nCmailGames) {
14484               case 1:
14485                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14486                 break;
14487
14488               case 2:
14489                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14490                 break;
14491
14492               default:
14493                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14494                          nCmailGames);
14495                 break;
14496             }
14497         } else {
14498             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14499               case 1:
14500                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14501                          string);
14502                 break;
14503
14504               case 0:
14505                 if (nCmailResults == nCmailGames) {
14506                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14507                 } else {
14508                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14509                 }
14510                 break;
14511
14512               default:
14513                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14514                          string);
14515             }
14516         }
14517     }
14518     return cmailMsg;
14519 #endif /* WIN32 */
14520 }
14521
14522 void
14523 ResetGameEvent ()
14524 {
14525     if (gameMode == Training)
14526       SetTrainingModeOff();
14527
14528     Reset(TRUE, TRUE);
14529     cmailMsgLoaded = FALSE;
14530     if (appData.icsActive) {
14531       SendToICS(ics_prefix);
14532       SendToICS("refresh\n");
14533     }
14534 }
14535
14536 void
14537 ExitEvent (int status)
14538 {
14539     exiting++;
14540     if (exiting > 2) {
14541       /* Give up on clean exit */
14542       exit(status);
14543     }
14544     if (exiting > 1) {
14545       /* Keep trying for clean exit */
14546       return;
14547     }
14548
14549     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14550     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14551
14552     if (telnetISR != NULL) {
14553       RemoveInputSource(telnetISR);
14554     }
14555     if (icsPR != NoProc) {
14556       DestroyChildProcess(icsPR, TRUE);
14557     }
14558
14559     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14560     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14561
14562     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14563     /* make sure this other one finishes before killing it!                  */
14564     if(endingGame) { int count = 0;
14565         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14566         while(endingGame && count++ < 10) DoSleep(1);
14567         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14568     }
14569
14570     /* Kill off chess programs */
14571     if (first.pr != NoProc) {
14572         ExitAnalyzeMode();
14573
14574         DoSleep( appData.delayBeforeQuit );
14575         SendToProgram("quit\n", &first);
14576         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14577     }
14578     if (second.pr != NoProc) {
14579         DoSleep( appData.delayBeforeQuit );
14580         SendToProgram("quit\n", &second);
14581         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14582     }
14583     if (first.isr != NULL) {
14584         RemoveInputSource(first.isr);
14585     }
14586     if (second.isr != NULL) {
14587         RemoveInputSource(second.isr);
14588     }
14589
14590     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14591     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14592
14593     ShutDownFrontEnd();
14594     exit(status);
14595 }
14596
14597 void
14598 PauseEngine (ChessProgramState *cps)
14599 {
14600     SendToProgram("pause\n", cps);
14601     cps->pause = 2;
14602 }
14603
14604 void
14605 UnPauseEngine (ChessProgramState *cps)
14606 {
14607     SendToProgram("resume\n", cps);
14608     cps->pause = 1;
14609 }
14610
14611 void
14612 PauseEvent ()
14613 {
14614     if (appData.debugMode)
14615         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14616     if (pausing) {
14617         pausing = FALSE;
14618         ModeHighlight();
14619         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14620             StartClocks();
14621             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14622                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14623                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14624             }
14625             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14626             HandleMachineMove(stashedInputMove, stalledEngine);
14627             stalledEngine = NULL;
14628             return;
14629         }
14630         if (gameMode == MachinePlaysWhite ||
14631             gameMode == TwoMachinesPlay   ||
14632             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14633             if(first.pause)  UnPauseEngine(&first);
14634             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14635             if(second.pause) UnPauseEngine(&second);
14636             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14637             StartClocks();
14638         } else {
14639             DisplayBothClocks();
14640         }
14641         if (gameMode == PlayFromGameFile) {
14642             if (appData.timeDelay >= 0)
14643                 AutoPlayGameLoop();
14644         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14645             Reset(FALSE, TRUE);
14646             SendToICS(ics_prefix);
14647             SendToICS("refresh\n");
14648         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14649             ForwardInner(forwardMostMove);
14650         }
14651         pauseExamInvalid = FALSE;
14652     } else {
14653         switch (gameMode) {
14654           default:
14655             return;
14656           case IcsExamining:
14657             pauseExamForwardMostMove = forwardMostMove;
14658             pauseExamInvalid = FALSE;
14659             /* fall through */
14660           case IcsObserving:
14661           case IcsPlayingWhite:
14662           case IcsPlayingBlack:
14663             pausing = TRUE;
14664             ModeHighlight();
14665             return;
14666           case PlayFromGameFile:
14667             (void) StopLoadGameTimer();
14668             pausing = TRUE;
14669             ModeHighlight();
14670             break;
14671           case BeginningOfGame:
14672             if (appData.icsActive) return;
14673             /* else fall through */
14674           case MachinePlaysWhite:
14675           case MachinePlaysBlack:
14676           case TwoMachinesPlay:
14677             if (forwardMostMove == 0)
14678               return;           /* don't pause if no one has moved */
14679             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14680                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14681                 if(onMove->pause) {           // thinking engine can be paused
14682                     PauseEngine(onMove);      // do it
14683                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14684                         PauseEngine(onMove->other);
14685                     else
14686                         SendToProgram("easy\n", onMove->other);
14687                     StopClocks();
14688                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14689             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14690                 if(first.pause) {
14691                     PauseEngine(&first);
14692                     StopClocks();
14693                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14694             } else { // human on move, pause pondering by either method
14695                 if(first.pause)
14696                     PauseEngine(&first);
14697                 else if(appData.ponderNextMove)
14698                     SendToProgram("easy\n", &first);
14699                 StopClocks();
14700             }
14701             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14702           case AnalyzeMode:
14703             pausing = TRUE;
14704             ModeHighlight();
14705             break;
14706         }
14707     }
14708 }
14709
14710 void
14711 EditCommentEvent ()
14712 {
14713     char title[MSG_SIZ];
14714
14715     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14716       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14717     } else {
14718       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14719                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14720                parseList[currentMove - 1]);
14721     }
14722
14723     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14724 }
14725
14726
14727 void
14728 EditTagsEvent ()
14729 {
14730     char *tags = PGNTags(&gameInfo);
14731     bookUp = FALSE;
14732     EditTagsPopUp(tags, NULL);
14733     free(tags);
14734 }
14735
14736 void
14737 ToggleSecond ()
14738 {
14739   if(second.analyzing) {
14740     SendToProgram("exit\n", &second);
14741     second.analyzing = FALSE;
14742   } else {
14743     if (second.pr == NoProc) StartChessProgram(&second);
14744     InitChessProgram(&second, FALSE);
14745     FeedMovesToProgram(&second, currentMove);
14746
14747     SendToProgram("analyze\n", &second);
14748     second.analyzing = TRUE;
14749   }
14750 }
14751
14752 /* Toggle ShowThinking */
14753 void
14754 ToggleShowThinking()
14755 {
14756   appData.showThinking = !appData.showThinking;
14757   ShowThinkingEvent();
14758 }
14759
14760 int
14761 AnalyzeModeEvent ()
14762 {
14763     char buf[MSG_SIZ];
14764
14765     if (!first.analysisSupport) {
14766       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14767       DisplayError(buf, 0);
14768       return 0;
14769     }
14770     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14771     if (appData.icsActive) {
14772         if (gameMode != IcsObserving) {
14773           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14774             DisplayError(buf, 0);
14775             /* secure check */
14776             if (appData.icsEngineAnalyze) {
14777                 if (appData.debugMode)
14778                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14779                 ExitAnalyzeMode();
14780                 ModeHighlight();
14781             }
14782             return 0;
14783         }
14784         /* if enable, user wants to disable icsEngineAnalyze */
14785         if (appData.icsEngineAnalyze) {
14786                 ExitAnalyzeMode();
14787                 ModeHighlight();
14788                 return 0;
14789         }
14790         appData.icsEngineAnalyze = TRUE;
14791         if (appData.debugMode)
14792             fprintf(debugFP, "ICS engine analyze starting... \n");
14793     }
14794
14795     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14796     if (appData.noChessProgram || gameMode == AnalyzeMode)
14797       return 0;
14798
14799     if (gameMode != AnalyzeFile) {
14800         if (!appData.icsEngineAnalyze) {
14801                EditGameEvent();
14802                if (gameMode != EditGame) return 0;
14803         }
14804         if (!appData.showThinking) ToggleShowThinking();
14805         ResurrectChessProgram();
14806         SendToProgram("analyze\n", &first);
14807         first.analyzing = TRUE;
14808         /*first.maybeThinking = TRUE;*/
14809         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14810         EngineOutputPopUp();
14811     }
14812     if (!appData.icsEngineAnalyze) {
14813         gameMode = AnalyzeMode;
14814         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14815     }
14816     pausing = FALSE;
14817     ModeHighlight();
14818     SetGameInfo();
14819
14820     StartAnalysisClock();
14821     GetTimeMark(&lastNodeCountTime);
14822     lastNodeCount = 0;
14823     return 1;
14824 }
14825
14826 void
14827 AnalyzeFileEvent ()
14828 {
14829     if (appData.noChessProgram || gameMode == AnalyzeFile)
14830       return;
14831
14832     if (!first.analysisSupport) {
14833       char buf[MSG_SIZ];
14834       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14835       DisplayError(buf, 0);
14836       return;
14837     }
14838
14839     if (gameMode != AnalyzeMode) {
14840         keepInfo = 1; // mere annotating should not alter PGN tags
14841         EditGameEvent();
14842         keepInfo = 0;
14843         if (gameMode != EditGame) return;
14844         if (!appData.showThinking) ToggleShowThinking();
14845         ResurrectChessProgram();
14846         SendToProgram("analyze\n", &first);
14847         first.analyzing = TRUE;
14848         /*first.maybeThinking = TRUE;*/
14849         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14850         EngineOutputPopUp();
14851     }
14852     gameMode = AnalyzeFile;
14853     pausing = FALSE;
14854     ModeHighlight();
14855
14856     StartAnalysisClock();
14857     GetTimeMark(&lastNodeCountTime);
14858     lastNodeCount = 0;
14859     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14860     AnalysisPeriodicEvent(1);
14861 }
14862
14863 void
14864 MachineWhiteEvent ()
14865 {
14866     char buf[MSG_SIZ];
14867     char *bookHit = NULL;
14868
14869     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14870       return;
14871
14872
14873     if (gameMode == PlayFromGameFile ||
14874         gameMode == TwoMachinesPlay  ||
14875         gameMode == Training         ||
14876         gameMode == AnalyzeMode      ||
14877         gameMode == EndOfGame)
14878         EditGameEvent();
14879
14880     if (gameMode == EditPosition)
14881         EditPositionDone(TRUE);
14882
14883     if (!WhiteOnMove(currentMove)) {
14884         DisplayError(_("It is not White's turn"), 0);
14885         return;
14886     }
14887
14888     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14889       ExitAnalyzeMode();
14890
14891     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14892         gameMode == AnalyzeFile)
14893         TruncateGame();
14894
14895     ResurrectChessProgram();    /* in case it isn't running */
14896     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14897         gameMode = MachinePlaysWhite;
14898         ResetClocks();
14899     } else
14900     gameMode = MachinePlaysWhite;
14901     pausing = FALSE;
14902     ModeHighlight();
14903     SetGameInfo();
14904     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14905     DisplayTitle(buf);
14906     if (first.sendName) {
14907       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14908       SendToProgram(buf, &first);
14909     }
14910     if (first.sendTime) {
14911       if (first.useColors) {
14912         SendToProgram("black\n", &first); /*gnu kludge*/
14913       }
14914       SendTimeRemaining(&first, TRUE);
14915     }
14916     if (first.useColors) {
14917       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14918     }
14919     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14920     SetMachineThinkingEnables();
14921     first.maybeThinking = TRUE;
14922     StartClocks();
14923     firstMove = FALSE;
14924
14925     if (appData.autoFlipView && !flipView) {
14926       flipView = !flipView;
14927       DrawPosition(FALSE, NULL);
14928       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14929     }
14930
14931     if(bookHit) { // [HGM] book: simulate book reply
14932         static char bookMove[MSG_SIZ]; // a bit generous?
14933
14934         programStats.nodes = programStats.depth = programStats.time =
14935         programStats.score = programStats.got_only_move = 0;
14936         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14937
14938         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14939         strcat(bookMove, bookHit);
14940         savedMessage = bookMove; // args for deferred call
14941         savedState = &first;
14942         ScheduleDelayedEvent(DeferredBookMove, 1);
14943     }
14944 }
14945
14946 void
14947 MachineBlackEvent ()
14948 {
14949   char buf[MSG_SIZ];
14950   char *bookHit = NULL;
14951
14952     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14953         return;
14954
14955
14956     if (gameMode == PlayFromGameFile ||
14957         gameMode == TwoMachinesPlay  ||
14958         gameMode == Training         ||
14959         gameMode == AnalyzeMode      ||
14960         gameMode == EndOfGame)
14961         EditGameEvent();
14962
14963     if (gameMode == EditPosition)
14964         EditPositionDone(TRUE);
14965
14966     if (WhiteOnMove(currentMove)) {
14967         DisplayError(_("It is not Black's turn"), 0);
14968         return;
14969     }
14970
14971     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14972       ExitAnalyzeMode();
14973
14974     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14975         gameMode == AnalyzeFile)
14976         TruncateGame();
14977
14978     ResurrectChessProgram();    /* in case it isn't running */
14979     gameMode = MachinePlaysBlack;
14980     pausing = FALSE;
14981     ModeHighlight();
14982     SetGameInfo();
14983     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14984     DisplayTitle(buf);
14985     if (first.sendName) {
14986       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14987       SendToProgram(buf, &first);
14988     }
14989     if (first.sendTime) {
14990       if (first.useColors) {
14991         SendToProgram("white\n", &first); /*gnu kludge*/
14992       }
14993       SendTimeRemaining(&first, FALSE);
14994     }
14995     if (first.useColors) {
14996       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14997     }
14998     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14999     SetMachineThinkingEnables();
15000     first.maybeThinking = TRUE;
15001     StartClocks();
15002
15003     if (appData.autoFlipView && flipView) {
15004       flipView = !flipView;
15005       DrawPosition(FALSE, NULL);
15006       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
15007     }
15008     if(bookHit) { // [HGM] book: simulate book reply
15009         static char bookMove[MSG_SIZ]; // a bit generous?
15010
15011         programStats.nodes = programStats.depth = programStats.time =
15012         programStats.score = programStats.got_only_move = 0;
15013         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15014
15015         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15016         strcat(bookMove, bookHit);
15017         savedMessage = bookMove; // args for deferred call
15018         savedState = &first;
15019         ScheduleDelayedEvent(DeferredBookMove, 1);
15020     }
15021 }
15022
15023
15024 void
15025 DisplayTwoMachinesTitle ()
15026 {
15027     char buf[MSG_SIZ];
15028     if (appData.matchGames > 0) {
15029         if(appData.tourneyFile[0]) {
15030           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
15031                    gameInfo.white, _("vs."), gameInfo.black,
15032                    nextGame+1, appData.matchGames+1,
15033                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
15034         } else
15035         if (first.twoMachinesColor[0] == 'w') {
15036           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15037                    gameInfo.white, _("vs."),  gameInfo.black,
15038                    first.matchWins, second.matchWins,
15039                    matchGame - 1 - (first.matchWins + second.matchWins));
15040         } else {
15041           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
15042                    gameInfo.white, _("vs."), gameInfo.black,
15043                    second.matchWins, first.matchWins,
15044                    matchGame - 1 - (first.matchWins + second.matchWins));
15045         }
15046     } else {
15047       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
15048     }
15049     DisplayTitle(buf);
15050 }
15051
15052 void
15053 SettingsMenuIfReady ()
15054 {
15055   if (second.lastPing != second.lastPong) {
15056     DisplayMessage("", _("Waiting for second chess program"));
15057     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
15058     return;
15059   }
15060   ThawUI();
15061   DisplayMessage("", "");
15062   SettingsPopUp(&second);
15063 }
15064
15065 int
15066 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
15067 {
15068     char buf[MSG_SIZ];
15069     if (cps->pr == NoProc) {
15070         StartChessProgram(cps);
15071         if (cps->protocolVersion == 1) {
15072           retry();
15073           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
15074         } else {
15075           /* kludge: allow timeout for initial "feature" command */
15076           if(retry != TwoMachinesEventIfReady) FreezeUI();
15077           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
15078           DisplayMessage("", buf);
15079           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
15080         }
15081         return 1;
15082     }
15083     return 0;
15084 }
15085
15086 void
15087 TwoMachinesEvent P((void))
15088 {
15089     int i, move = forwardMostMove;
15090     char buf[MSG_SIZ];
15091     ChessProgramState *onmove;
15092     char *bookHit = NULL;
15093     static int stalling = 0;
15094     TimeMark now;
15095     long wait;
15096
15097     if (appData.noChessProgram) return;
15098
15099     switch (gameMode) {
15100       case TwoMachinesPlay:
15101         return;
15102       case MachinePlaysWhite:
15103       case MachinePlaysBlack:
15104         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15105             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15106             return;
15107         }
15108         /* fall through */
15109       case BeginningOfGame:
15110       case PlayFromGameFile:
15111       case EndOfGame:
15112         EditGameEvent();
15113         if (gameMode != EditGame) return;
15114         break;
15115       case EditPosition:
15116         EditPositionDone(TRUE);
15117         break;
15118       case AnalyzeMode:
15119       case AnalyzeFile:
15120         ExitAnalyzeMode();
15121         break;
15122       case EditGame:
15123       default:
15124         break;
15125     }
15126
15127 //    forwardMostMove = currentMove;
15128     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15129     startingEngine = TRUE;
15130
15131     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15132
15133     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15134     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15135       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15136       return;
15137     }
15138   if(!appData.epd) {
15139     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15140
15141     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15142                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15143         startingEngine = matchMode = FALSE;
15144         DisplayError("second engine does not play this", 0);
15145         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15146         EditGameEvent(); // switch back to EditGame mode
15147         return;
15148     }
15149
15150     if(!stalling) {
15151       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15152       SendToProgram("force\n", &second);
15153       stalling = 1;
15154       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15155       return;
15156     }
15157   }
15158     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15159     if(appData.matchPause>10000 || appData.matchPause<10)
15160                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15161     wait = SubtractTimeMarks(&now, &pauseStart);
15162     if(wait < appData.matchPause) {
15163         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15164         return;
15165     }
15166     // we are now committed to starting the game
15167     stalling = 0;
15168     DisplayMessage("", "");
15169   if(!appData.epd) {
15170     if (startedFromSetupPosition) {
15171         SendBoard(&second, backwardMostMove);
15172     if (appData.debugMode) {
15173         fprintf(debugFP, "Two Machines\n");
15174     }
15175     }
15176     for (i = backwardMostMove; i < forwardMostMove; i++) {
15177         SendMoveToProgram(i, &second);
15178     }
15179   }
15180
15181     gameMode = TwoMachinesPlay;
15182     pausing = startingEngine = FALSE;
15183     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15184     SetGameInfo();
15185     DisplayTwoMachinesTitle();
15186     firstMove = TRUE;
15187     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15188         onmove = &first;
15189     } else {
15190         onmove = &second;
15191     }
15192     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15193     SendToProgram(first.computerString, &first);
15194     if (first.sendName) {
15195       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15196       SendToProgram(buf, &first);
15197     }
15198   if(!appData.epd) {
15199     SendToProgram(second.computerString, &second);
15200     if (second.sendName) {
15201       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15202       SendToProgram(buf, &second);
15203     }
15204   }
15205
15206     if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
15207         ResetClocks();
15208         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15209         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15210     }
15211     if (onmove->sendTime) {
15212       if (onmove->useColors) {
15213         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15214       }
15215       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15216     }
15217     if (onmove->useColors) {
15218       SendToProgram(onmove->twoMachinesColor, onmove);
15219     }
15220     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15221 //    SendToProgram("go\n", onmove);
15222     onmove->maybeThinking = TRUE;
15223     SetMachineThinkingEnables();
15224
15225     StartClocks();
15226
15227     if(bookHit) { // [HGM] book: simulate book reply
15228         static char bookMove[MSG_SIZ]; // a bit generous?
15229
15230         programStats.nodes = programStats.depth = programStats.time =
15231         programStats.score = programStats.got_only_move = 0;
15232         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15233
15234         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15235         strcat(bookMove, bookHit);
15236         savedMessage = bookMove; // args for deferred call
15237         savedState = onmove;
15238         ScheduleDelayedEvent(DeferredBookMove, 1);
15239     }
15240 }
15241
15242 void
15243 TrainingEvent ()
15244 {
15245     if (gameMode == Training) {
15246       SetTrainingModeOff();
15247       gameMode = PlayFromGameFile;
15248       DisplayMessage("", _("Training mode off"));
15249     } else {
15250       gameMode = Training;
15251       animateTraining = appData.animate;
15252
15253       /* make sure we are not already at the end of the game */
15254       if (currentMove < forwardMostMove) {
15255         SetTrainingModeOn();
15256         DisplayMessage("", _("Training mode on"));
15257       } else {
15258         gameMode = PlayFromGameFile;
15259         DisplayError(_("Already at end of game"), 0);
15260       }
15261     }
15262     ModeHighlight();
15263 }
15264
15265 void
15266 IcsClientEvent ()
15267 {
15268     if (!appData.icsActive) return;
15269     switch (gameMode) {
15270       case IcsPlayingWhite:
15271       case IcsPlayingBlack:
15272       case IcsObserving:
15273       case IcsIdle:
15274       case BeginningOfGame:
15275       case IcsExamining:
15276         return;
15277
15278       case EditGame:
15279         break;
15280
15281       case EditPosition:
15282         EditPositionDone(TRUE);
15283         break;
15284
15285       case AnalyzeMode:
15286       case AnalyzeFile:
15287         ExitAnalyzeMode();
15288         break;
15289
15290       default:
15291         EditGameEvent();
15292         break;
15293     }
15294
15295     gameMode = IcsIdle;
15296     ModeHighlight();
15297     return;
15298 }
15299
15300 void
15301 EditGameEvent ()
15302 {
15303     int i;
15304
15305     switch (gameMode) {
15306       case Training:
15307         SetTrainingModeOff();
15308         break;
15309       case MachinePlaysWhite:
15310       case MachinePlaysBlack:
15311       case BeginningOfGame:
15312         SendToProgram("force\n", &first);
15313         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15314             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15315                 char buf[MSG_SIZ];
15316                 abortEngineThink = TRUE;
15317                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15318                 SendToProgram(buf, &first);
15319                 DisplayMessage("Aborting engine think", "");
15320                 FreezeUI();
15321             }
15322         }
15323         SetUserThinkingEnables();
15324         break;
15325       case PlayFromGameFile:
15326         (void) StopLoadGameTimer();
15327         if (gameFileFP != NULL) {
15328             gameFileFP = NULL;
15329         }
15330         break;
15331       case EditPosition:
15332         EditPositionDone(TRUE);
15333         break;
15334       case AnalyzeMode:
15335       case AnalyzeFile:
15336         ExitAnalyzeMode();
15337         SendToProgram("force\n", &first);
15338         break;
15339       case TwoMachinesPlay:
15340         GameEnds(EndOfFile, NULL, GE_PLAYER);
15341         ResurrectChessProgram();
15342         SetUserThinkingEnables();
15343         break;
15344       case EndOfGame:
15345         ResurrectChessProgram();
15346         break;
15347       case IcsPlayingBlack:
15348       case IcsPlayingWhite:
15349         DisplayError(_("Warning: You are still playing a game"), 0);
15350         break;
15351       case IcsObserving:
15352         DisplayError(_("Warning: You are still observing a game"), 0);
15353         break;
15354       case IcsExamining:
15355         DisplayError(_("Warning: You are still examining a game"), 0);
15356         break;
15357       case IcsIdle:
15358         break;
15359       case EditGame:
15360       default:
15361         return;
15362     }
15363
15364     pausing = FALSE;
15365     StopClocks();
15366     first.offeredDraw = second.offeredDraw = 0;
15367
15368     if (gameMode == PlayFromGameFile) {
15369         whiteTimeRemaining = timeRemaining[0][currentMove];
15370         blackTimeRemaining = timeRemaining[1][currentMove];
15371         DisplayTitle("");
15372     }
15373
15374     if (gameMode == MachinePlaysWhite ||
15375         gameMode == MachinePlaysBlack ||
15376         gameMode == TwoMachinesPlay ||
15377         gameMode == EndOfGame) {
15378         i = forwardMostMove;
15379         while (i > currentMove) {
15380             SendToProgram("undo\n", &first);
15381             i--;
15382         }
15383         if(!adjustedClock) {
15384         whiteTimeRemaining = timeRemaining[0][currentMove];
15385         blackTimeRemaining = timeRemaining[1][currentMove];
15386         DisplayBothClocks();
15387         }
15388         if (whiteFlag || blackFlag) {
15389             whiteFlag = blackFlag = 0;
15390         }
15391         DisplayTitle("");
15392     }
15393
15394     gameMode = EditGame;
15395     ModeHighlight();
15396     SetGameInfo();
15397 }
15398
15399 void
15400 EditPositionEvent ()
15401 {
15402     int i;
15403     if (gameMode == EditPosition) {
15404         EditGameEvent();
15405         return;
15406     }
15407
15408     EditGameEvent();
15409     if (gameMode != EditGame) return;
15410
15411     gameMode = EditPosition;
15412     ModeHighlight();
15413     SetGameInfo();
15414     CopyBoard(rightsBoard, nullBoard);
15415     if (currentMove > 0)
15416       CopyBoard(boards[0], boards[currentMove]);
15417     for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15418       rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15419
15420     blackPlaysFirst = !WhiteOnMove(currentMove);
15421     ResetClocks();
15422     currentMove = forwardMostMove = backwardMostMove = 0;
15423     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15424     DisplayMove(-1);
15425     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15426 }
15427
15428 void
15429 ExitAnalyzeMode ()
15430 {
15431     /* [DM] icsEngineAnalyze - possible call from other functions */
15432     if (appData.icsEngineAnalyze) {
15433         appData.icsEngineAnalyze = FALSE;
15434
15435         DisplayMessage("",_("Close ICS engine analyze..."));
15436     }
15437     if (first.analysisSupport && first.analyzing) {
15438       SendToBoth("exit\n");
15439       first.analyzing = second.analyzing = FALSE;
15440     }
15441     thinkOutput[0] = NULLCHAR;
15442 }
15443
15444 void
15445 EditPositionDone (Boolean fakeRights)
15446 {
15447     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15448
15449     startedFromSetupPosition = TRUE;
15450     InitChessProgram(&first, FALSE);
15451     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15452       int r, f;
15453       boards[0][EP_STATUS] = EP_NONE;
15454       for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
15455       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
15456         if(rightsBoard[r][f]) {
15457           ChessSquare p = boards[0][r][f];
15458           if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
15459           else if(p == king) boards[0][CASTLING][2] = f;
15460           else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
15461           else rightsBoard[r][f] = 2; // mark for second pass
15462         }
15463       }
15464       for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
15465         if(rightsBoard[r][f] == 2) {
15466           ChessSquare p = boards[0][r][f];
15467           if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
15468           if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
15469         }
15470       }
15471     }
15472     SendToProgram("force\n", &first);
15473     if (blackPlaysFirst) {
15474         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15475         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15476         currentMove = forwardMostMove = backwardMostMove = 1;
15477         CopyBoard(boards[1], boards[0]);
15478     } else {
15479         currentMove = forwardMostMove = backwardMostMove = 0;
15480     }
15481     SendBoard(&first, forwardMostMove);
15482     if (appData.debugMode) {
15483         fprintf(debugFP, "EditPosDone\n");
15484     }
15485     DisplayTitle("");
15486     DisplayMessage("", "");
15487     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15488     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15489     gameMode = EditGame;
15490     ModeHighlight();
15491     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15492     ClearHighlights(); /* [AS] */
15493 }
15494
15495 /* Pause for `ms' milliseconds */
15496 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15497 void
15498 TimeDelay (long ms)
15499 {
15500     TimeMark m1, m2;
15501
15502     GetTimeMark(&m1);
15503     do {
15504         GetTimeMark(&m2);
15505     } while (SubtractTimeMarks(&m2, &m1) < ms);
15506 }
15507
15508 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15509 void
15510 SendMultiLineToICS (char *buf)
15511 {
15512     char temp[MSG_SIZ+1], *p;
15513     int len;
15514
15515     len = strlen(buf);
15516     if (len > MSG_SIZ)
15517       len = MSG_SIZ;
15518
15519     strncpy(temp, buf, len);
15520     temp[len] = 0;
15521
15522     p = temp;
15523     while (*p) {
15524         if (*p == '\n' || *p == '\r')
15525           *p = ' ';
15526         ++p;
15527     }
15528
15529     strcat(temp, "\n");
15530     SendToICS(temp);
15531     SendToPlayer(temp, strlen(temp));
15532 }
15533
15534 void
15535 SetWhiteToPlayEvent ()
15536 {
15537     if (gameMode == EditPosition) {
15538         blackPlaysFirst = FALSE;
15539         DisplayBothClocks();    /* works because currentMove is 0 */
15540     } else if (gameMode == IcsExamining) {
15541         SendToICS(ics_prefix);
15542         SendToICS("tomove white\n");
15543     }
15544 }
15545
15546 void
15547 SetBlackToPlayEvent ()
15548 {
15549     if (gameMode == EditPosition) {
15550         blackPlaysFirst = TRUE;
15551         currentMove = 1;        /* kludge */
15552         DisplayBothClocks();
15553         currentMove = 0;
15554     } else if (gameMode == IcsExamining) {
15555         SendToICS(ics_prefix);
15556         SendToICS("tomove black\n");
15557     }
15558 }
15559
15560 void
15561 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15562 {
15563     char buf[MSG_SIZ];
15564     ChessSquare piece = boards[0][y][x];
15565     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15566     static int lastVariant;
15567     int baseRank = BOARD_HEIGHT-1, hasRights = 0;
15568
15569     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15570
15571     switch (selection) {
15572       case ClearBoard:
15573         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15574         MarkTargetSquares(1);
15575         CopyBoard(currentBoard, boards[0]);
15576         CopyBoard(menuBoard, initialPosition);
15577         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15578             SendToICS(ics_prefix);
15579             SendToICS("bsetup clear\n");
15580         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15581             SendToICS(ics_prefix);
15582             SendToICS("clearboard\n");
15583         } else {
15584             int nonEmpty = 0;
15585             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15586                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15587                 for (y = 0; y < BOARD_HEIGHT; y++) {
15588                     if (gameMode == IcsExamining) {
15589                         if (boards[currentMove][y][x] != EmptySquare) {
15590                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15591                                     AAA + x, ONE + y);
15592                             SendToICS(buf);
15593                         }
15594                     } else if(boards[0][y][x] != DarkSquare) {
15595                         if(boards[0][y][x] != p) nonEmpty++;
15596                         boards[0][y][x] = p;
15597                     }
15598                 }
15599             }
15600             CopyBoard(rightsBoard, nullBoard);
15601             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15602                 int r, i;
15603                 for(r = 0; r < BOARD_HEIGHT; r++) {
15604                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15605                     ChessSquare p = menuBoard[r][x];
15606                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15607                   }
15608                 }
15609                 menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
15610                 DisplayMessage("Clicking clock again restores position", "");
15611                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15612                 if(!nonEmpty) { // asked to clear an empty board
15613                     CopyBoard(boards[0], menuBoard);
15614                 } else
15615                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15616                     CopyBoard(boards[0], initialPosition);
15617                 } else
15618                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15619                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15620                     CopyBoard(boards[0], erasedBoard);
15621                 } else
15622                     CopyBoard(erasedBoard, currentBoard);
15623
15624                 for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
15625                     rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
15626             }
15627         }
15628         if (gameMode == EditPosition) {
15629             DrawPosition(FALSE, boards[0]);
15630         }
15631         break;
15632
15633       case WhitePlay:
15634         SetWhiteToPlayEvent();
15635         break;
15636
15637       case BlackPlay:
15638         SetBlackToPlayEvent();
15639         break;
15640
15641       case EmptySquare:
15642         if (gameMode == IcsExamining) {
15643             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15644             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15645             SendToICS(buf);
15646         } else {
15647             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15648                 if(x == BOARD_LEFT-2) {
15649                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15650                     boards[0][y][1] = 0;
15651                 } else
15652                 if(x == BOARD_RGHT+1) {
15653                     if(y >= gameInfo.holdingsSize) break;
15654                     boards[0][y][BOARD_WIDTH-2] = 0;
15655                 } else break;
15656             }
15657             boards[0][y][x] = EmptySquare;
15658             DrawPosition(FALSE, boards[0]);
15659         }
15660         break;
15661
15662       case PromotePiece:
15663         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15664            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15665             selection = (ChessSquare) (PROMOTED(piece));
15666         } else if(piece == EmptySquare) selection = WhiteSilver;
15667         else selection = (ChessSquare)((int)piece - 1);
15668         goto defaultlabel;
15669
15670       case DemotePiece:
15671         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15672            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15673             selection = (ChessSquare) (DEMOTED(piece));
15674         } else if(piece == EmptySquare) selection = BlackSilver;
15675         else selection = (ChessSquare)((int)piece + 1);
15676         goto defaultlabel;
15677
15678       case WhiteQueen:
15679       case BlackQueen:
15680         if(gameInfo.variant == VariantShatranj ||
15681            gameInfo.variant == VariantXiangqi  ||
15682            gameInfo.variant == VariantCourier  ||
15683            gameInfo.variant == VariantASEAN    ||
15684            gameInfo.variant == VariantMakruk     )
15685             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15686         goto defaultlabel;
15687
15688       case WhiteRook:
15689         baseRank = 0;
15690       case BlackRook:
15691         if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
15692         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15693         goto defaultlabel;
15694
15695       case WhiteKing:
15696         baseRank = 0;
15697       case BlackKing:
15698         if(gameInfo.variant == VariantXiangqi)
15699             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15700         if(gameInfo.variant == VariantKnightmate)
15701             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15702         if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
15703       default:
15704         defaultlabel:
15705         if (gameMode == IcsExamining) {
15706             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15707             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15708                      PieceToChar(selection), AAA + x, ONE + y);
15709             SendToICS(buf);
15710         } else {
15711             rightsBoard[y][x] = hasRights;
15712             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15713                 int n;
15714                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15715                     n = PieceToNumber(selection - BlackPawn);
15716                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15717                     boards[0][handSize-1-n][0] = selection;
15718                     boards[0][handSize-1-n][1]++;
15719                 } else
15720                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15721                     n = PieceToNumber(selection);
15722                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15723                     boards[0][n][BOARD_WIDTH-1] = selection;
15724                     boards[0][n][BOARD_WIDTH-2]++;
15725                 }
15726             } else
15727             boards[0][y][x] = selection;
15728             DrawPosition(TRUE, boards[0]);
15729             ClearHighlights();
15730             fromX = fromY = -1;
15731         }
15732         break;
15733     }
15734 }
15735
15736
15737 void
15738 DropMenuEvent (ChessSquare selection, int x, int y)
15739 {
15740     ChessMove moveType;
15741
15742     switch (gameMode) {
15743       case IcsPlayingWhite:
15744       case MachinePlaysBlack:
15745         if (!WhiteOnMove(currentMove)) {
15746             DisplayMoveError(_("It is Black's turn"));
15747             return;
15748         }
15749         moveType = WhiteDrop;
15750         break;
15751       case IcsPlayingBlack:
15752       case MachinePlaysWhite:
15753         if (WhiteOnMove(currentMove)) {
15754             DisplayMoveError(_("It is White's turn"));
15755             return;
15756         }
15757         moveType = BlackDrop;
15758         break;
15759       case EditGame:
15760         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15761         break;
15762       default:
15763         return;
15764     }
15765
15766     if (moveType == BlackDrop && selection < BlackPawn) {
15767       selection = (ChessSquare) ((int) selection
15768                                  + (int) BlackPawn - (int) WhitePawn);
15769     }
15770     if (boards[currentMove][y][x] != EmptySquare) {
15771         DisplayMoveError(_("That square is occupied"));
15772         return;
15773     }
15774
15775     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15776 }
15777
15778 void
15779 AcceptEvent ()
15780 {
15781     /* Accept a pending offer of any kind from opponent */
15782
15783     if (appData.icsActive) {
15784         SendToICS(ics_prefix);
15785         SendToICS("accept\n");
15786     } else if (cmailMsgLoaded) {
15787         if (currentMove == cmailOldMove &&
15788             commentList[cmailOldMove] != NULL &&
15789             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15790                    "Black offers a draw" : "White offers a draw")) {
15791             TruncateGame();
15792             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15793             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15794         } else {
15795             DisplayError(_("There is no pending offer on this move"), 0);
15796             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15797         }
15798     } else {
15799         /* Not used for offers from chess program */
15800     }
15801 }
15802
15803 void
15804 DeclineEvent ()
15805 {
15806     /* Decline a pending offer of any kind from opponent */
15807
15808     if (appData.icsActive) {
15809         SendToICS(ics_prefix);
15810         SendToICS("decline\n");
15811     } else if (cmailMsgLoaded) {
15812         if (currentMove == cmailOldMove &&
15813             commentList[cmailOldMove] != NULL &&
15814             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15815                    "Black offers a draw" : "White offers a draw")) {
15816 #ifdef NOTDEF
15817             AppendComment(cmailOldMove, "Draw declined", TRUE);
15818             DisplayComment(cmailOldMove - 1, "Draw declined");
15819 #endif /*NOTDEF*/
15820         } else {
15821             DisplayError(_("There is no pending offer on this move"), 0);
15822         }
15823     } else {
15824         /* Not used for offers from chess program */
15825     }
15826 }
15827
15828 void
15829 RematchEvent ()
15830 {
15831     /* Issue ICS rematch command */
15832     if (appData.icsActive) {
15833         SendToICS(ics_prefix);
15834         SendToICS("rematch\n");
15835     }
15836 }
15837
15838 void
15839 CallFlagEvent ()
15840 {
15841     /* Call your opponent's flag (claim a win on time) */
15842     if (appData.icsActive) {
15843         SendToICS(ics_prefix);
15844         SendToICS("flag\n");
15845     } else {
15846         switch (gameMode) {
15847           default:
15848             return;
15849           case MachinePlaysWhite:
15850             if (whiteFlag) {
15851                 if (blackFlag)
15852                   GameEnds(GameIsDrawn, "Both players ran out of time",
15853                            GE_PLAYER);
15854                 else
15855                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15856             } else {
15857                 DisplayError(_("Your opponent is not out of time"), 0);
15858             }
15859             break;
15860           case MachinePlaysBlack:
15861             if (blackFlag) {
15862                 if (whiteFlag)
15863                   GameEnds(GameIsDrawn, "Both players ran out of time",
15864                            GE_PLAYER);
15865                 else
15866                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15867             } else {
15868                 DisplayError(_("Your opponent is not out of time"), 0);
15869             }
15870             break;
15871         }
15872     }
15873 }
15874
15875 void
15876 ClockClick (int which)
15877 {       // [HGM] code moved to back-end from winboard.c
15878         if(which) { // black clock
15879           if (gameMode == EditPosition || gameMode == IcsExamining) {
15880             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15881             SetBlackToPlayEvent();
15882           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15883                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15884           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15885           } else if (shiftKey) {
15886             AdjustClock(which, -1);
15887           } else if (gameMode == IcsPlayingWhite ||
15888                      gameMode == MachinePlaysBlack) {
15889             CallFlagEvent();
15890           }
15891         } else { // white clock
15892           if (gameMode == EditPosition || gameMode == IcsExamining) {
15893             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15894             SetWhiteToPlayEvent();
15895           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15896                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15897           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15898           } else if (shiftKey) {
15899             AdjustClock(which, -1);
15900           } else if (gameMode == IcsPlayingBlack ||
15901                    gameMode == MachinePlaysWhite) {
15902             CallFlagEvent();
15903           }
15904         }
15905 }
15906
15907 void
15908 DrawEvent ()
15909 {
15910     /* Offer draw or accept pending draw offer from opponent */
15911
15912     if (appData.icsActive) {
15913         /* Note: tournament rules require draw offers to be
15914            made after you make your move but before you punch
15915            your clock.  Currently ICS doesn't let you do that;
15916            instead, you immediately punch your clock after making
15917            a move, but you can offer a draw at any time. */
15918
15919         SendToICS(ics_prefix);
15920         SendToICS("draw\n");
15921         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15922     } else if (cmailMsgLoaded) {
15923         if (currentMove == cmailOldMove &&
15924             commentList[cmailOldMove] != NULL &&
15925             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15926                    "Black offers a draw" : "White offers a draw")) {
15927             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15928             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15929         } else if (currentMove == cmailOldMove + 1) {
15930             char *offer = WhiteOnMove(cmailOldMove) ?
15931               "White offers a draw" : "Black offers a draw";
15932             AppendComment(currentMove, offer, TRUE);
15933             DisplayComment(currentMove - 1, offer);
15934             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15935         } else {
15936             DisplayError(_("You must make your move before offering a draw"), 0);
15937             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15938         }
15939     } else if (first.offeredDraw) {
15940         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15941     } else {
15942         if (first.sendDrawOffers) {
15943             SendToProgram("draw\n", &first);
15944             userOfferedDraw = TRUE;
15945         }
15946     }
15947 }
15948
15949 void
15950 AdjournEvent ()
15951 {
15952     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15953
15954     if (appData.icsActive) {
15955         SendToICS(ics_prefix);
15956         SendToICS("adjourn\n");
15957     } else {
15958         /* Currently GNU Chess doesn't offer or accept Adjourns */
15959     }
15960 }
15961
15962
15963 void
15964 AbortEvent ()
15965 {
15966     /* Offer Abort or accept pending Abort offer from opponent */
15967
15968     if (appData.icsActive) {
15969         SendToICS(ics_prefix);
15970         SendToICS("abort\n");
15971     } else {
15972         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15973     }
15974 }
15975
15976 void
15977 ResignEvent ()
15978 {
15979     /* Resign.  You can do this even if it's not your turn. */
15980
15981     if (appData.icsActive) {
15982         SendToICS(ics_prefix);
15983         SendToICS("resign\n");
15984     } else {
15985         switch (gameMode) {
15986           case MachinePlaysWhite:
15987             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15988             break;
15989           case MachinePlaysBlack:
15990             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15991             break;
15992           case EditGame:
15993             if (cmailMsgLoaded) {
15994                 TruncateGame();
15995                 if (WhiteOnMove(cmailOldMove)) {
15996                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15997                 } else {
15998                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15999                 }
16000                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
16001             }
16002             break;
16003           default:
16004             break;
16005         }
16006     }
16007 }
16008
16009
16010 void
16011 StopObservingEvent ()
16012 {
16013     /* Stop observing current games */
16014     SendToICS(ics_prefix);
16015     SendToICS("unobserve\n");
16016 }
16017
16018 void
16019 StopExaminingEvent ()
16020 {
16021     /* Stop observing current game */
16022     SendToICS(ics_prefix);
16023     SendToICS("unexamine\n");
16024 }
16025
16026 void
16027 ForwardInner (int target)
16028 {
16029     int limit; int oldSeekGraphUp = seekGraphUp;
16030
16031     if (appData.debugMode)
16032         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
16033                 target, currentMove, forwardMostMove);
16034
16035     if (gameMode == EditPosition)
16036       return;
16037
16038     seekGraphUp = FALSE;
16039     MarkTargetSquares(1);
16040     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16041
16042     if (gameMode == PlayFromGameFile && !pausing)
16043       PauseEvent();
16044
16045     if (gameMode == IcsExamining && pausing)
16046       limit = pauseExamForwardMostMove;
16047     else
16048       limit = forwardMostMove;
16049
16050     if (target > limit) target = limit;
16051
16052     if (target > 0 && moveList[target - 1][0]) {
16053         int fromX, fromY, toX, toY;
16054         toX = moveList[target - 1][2] - AAA;
16055         toY = moveList[target - 1][3] - ONE;
16056         if (moveList[target - 1][1] == '@') {
16057             if (appData.highlightLastMove) {
16058                 SetHighlights(-1, -1, toX, toY);
16059             }
16060         } else {
16061             fromX = moveList[target - 1][0] - AAA;
16062             fromY = moveList[target - 1][1] - ONE;
16063             if (target == currentMove + 1) {
16064                 if(moveList[target - 1][4] == ';') { // multi-leg
16065                     killX = moveList[target - 1][5] - AAA;
16066                     killY = moveList[target - 1][6] - ONE;
16067                 }
16068                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
16069                 killX = killY = -1;
16070             }
16071             if (appData.highlightLastMove) {
16072                 SetHighlights(fromX, fromY, toX, toY);
16073             }
16074         }
16075     }
16076     if (gameMode == EditGame || gameMode == AnalyzeMode ||
16077         gameMode == Training || gameMode == PlayFromGameFile ||
16078         gameMode == AnalyzeFile) {
16079         while (currentMove < target) {
16080             if(second.analyzing) SendMoveToProgram(currentMove, &second);
16081             SendMoveToProgram(currentMove++, &first);
16082         }
16083     } else {
16084         currentMove = target;
16085     }
16086
16087     if (gameMode == EditGame || gameMode == EndOfGame) {
16088         whiteTimeRemaining = timeRemaining[0][currentMove];
16089         blackTimeRemaining = timeRemaining[1][currentMove];
16090     }
16091     DisplayBothClocks();
16092     DisplayMove(currentMove - 1);
16093     DrawPosition(oldSeekGraphUp, boards[currentMove]);
16094     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16095     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
16096         DisplayComment(currentMove - 1, commentList[currentMove]);
16097     }
16098     ClearMap(); // [HGM] exclude: invalidate map
16099 }
16100
16101
16102 void
16103 ForwardEvent ()
16104 {
16105     if (gameMode == IcsExamining && !pausing) {
16106         SendToICS(ics_prefix);
16107         SendToICS("forward\n");
16108     } else {
16109         ForwardInner(currentMove + 1);
16110     }
16111 }
16112
16113 void
16114 ToEndEvent ()
16115 {
16116     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16117         /* to optimze, we temporarily turn off analysis mode while we feed
16118          * the remaining moves to the engine. Otherwise we get analysis output
16119          * after each move.
16120          */
16121         if (first.analysisSupport) {
16122           SendToProgram("exit\nforce\n", &first);
16123           first.analyzing = FALSE;
16124         }
16125     }
16126
16127     if (gameMode == IcsExamining && !pausing) {
16128         SendToICS(ics_prefix);
16129         SendToICS("forward 999999\n");
16130     } else {
16131         ForwardInner(forwardMostMove);
16132     }
16133
16134     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16135         /* we have fed all the moves, so reactivate analysis mode */
16136         SendToProgram("analyze\n", &first);
16137         first.analyzing = TRUE;
16138         /*first.maybeThinking = TRUE;*/
16139         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16140     }
16141 }
16142
16143 void
16144 BackwardInner (int target)
16145 {
16146     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16147
16148     if (appData.debugMode)
16149         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16150                 target, currentMove, forwardMostMove);
16151
16152     if (gameMode == EditPosition) return;
16153     seekGraphUp = FALSE;
16154     MarkTargetSquares(1);
16155     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16156     if (currentMove <= backwardMostMove) {
16157         ClearHighlights();
16158         DrawPosition(full_redraw, boards[currentMove]);
16159         return;
16160     }
16161     if (gameMode == PlayFromGameFile && !pausing)
16162       PauseEvent();
16163
16164     if (moveList[target][0]) {
16165         int fromX, fromY, toX, toY;
16166         toX = moveList[target][2] - AAA;
16167         toY = moveList[target][3] - ONE;
16168         if (moveList[target][1] == '@') {
16169             if (appData.highlightLastMove) {
16170                 SetHighlights(-1, -1, toX, toY);
16171             }
16172         } else {
16173             fromX = moveList[target][0] - AAA;
16174             fromY = moveList[target][1] - ONE;
16175             if (target == currentMove - 1) {
16176                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16177             }
16178             if (appData.highlightLastMove) {
16179                 SetHighlights(fromX, fromY, toX, toY);
16180             }
16181         }
16182     }
16183     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16184         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16185         while (currentMove > target) {
16186             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16187                 // null move cannot be undone. Reload program with move history before it.
16188                 int i;
16189                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16190                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16191                 }
16192                 SendBoard(&first, i);
16193               if(second.analyzing) SendBoard(&second, i);
16194                 for(currentMove=i; currentMove<target; currentMove++) {
16195                     SendMoveToProgram(currentMove, &first);
16196                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16197                 }
16198                 break;
16199             }
16200             SendToBoth("undo\n");
16201             currentMove--;
16202         }
16203     } else {
16204         currentMove = target;
16205     }
16206
16207     if (gameMode == EditGame || gameMode == EndOfGame) {
16208         whiteTimeRemaining = timeRemaining[0][currentMove];
16209         blackTimeRemaining = timeRemaining[1][currentMove];
16210     }
16211     DisplayBothClocks();
16212     DisplayMove(currentMove - 1);
16213     DrawPosition(full_redraw, boards[currentMove]);
16214     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16215     // [HGM] PV info: routine tests if comment empty
16216     DisplayComment(currentMove - 1, commentList[currentMove]);
16217     ClearMap(); // [HGM] exclude: invalidate map
16218 }
16219
16220 void
16221 BackwardEvent ()
16222 {
16223     if (gameMode == IcsExamining && !pausing) {
16224         SendToICS(ics_prefix);
16225         SendToICS("backward\n");
16226     } else {
16227         BackwardInner(currentMove - 1);
16228     }
16229 }
16230
16231 void
16232 ToStartEvent ()
16233 {
16234     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16235         /* to optimize, we temporarily turn off analysis mode while we undo
16236          * all the moves. Otherwise we get analysis output after each undo.
16237          */
16238         if (first.analysisSupport) {
16239           SendToProgram("exit\nforce\n", &first);
16240           first.analyzing = FALSE;
16241         }
16242     }
16243
16244     if (gameMode == IcsExamining && !pausing) {
16245         SendToICS(ics_prefix);
16246         SendToICS("backward 999999\n");
16247     } else {
16248         BackwardInner(backwardMostMove);
16249     }
16250
16251     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16252         /* we have fed all the moves, so reactivate analysis mode */
16253         SendToProgram("analyze\n", &first);
16254         first.analyzing = TRUE;
16255         /*first.maybeThinking = TRUE;*/
16256         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16257     }
16258 }
16259
16260 void
16261 ToNrEvent (int to)
16262 {
16263   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16264   if (to >= forwardMostMove) to = forwardMostMove;
16265   if (to <= backwardMostMove) to = backwardMostMove;
16266   if (to < currentMove) {
16267     BackwardInner(to);
16268   } else {
16269     ForwardInner(to);
16270   }
16271 }
16272
16273 void
16274 RevertEvent (Boolean annotate)
16275 {
16276     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16277         return;
16278     }
16279     if (gameMode != IcsExamining) {
16280         DisplayError(_("You are not examining a game"), 0);
16281         return;
16282     }
16283     if (pausing) {
16284         DisplayError(_("You can't revert while pausing"), 0);
16285         return;
16286     }
16287     SendToICS(ics_prefix);
16288     SendToICS("revert\n");
16289 }
16290
16291 void
16292 RetractMoveEvent ()
16293 {
16294     switch (gameMode) {
16295       case MachinePlaysWhite:
16296       case MachinePlaysBlack:
16297         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16298             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16299             return;
16300         }
16301         if (forwardMostMove < 2) return;
16302         currentMove = forwardMostMove = forwardMostMove - 2;
16303         whiteTimeRemaining = timeRemaining[0][currentMove];
16304         blackTimeRemaining = timeRemaining[1][currentMove];
16305         DisplayBothClocks();
16306         DisplayMove(currentMove - 1);
16307         ClearHighlights();/*!! could figure this out*/
16308         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16309         SendToProgram("remove\n", &first);
16310         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16311         break;
16312
16313       case BeginningOfGame:
16314       default:
16315         break;
16316
16317       case IcsPlayingWhite:
16318       case IcsPlayingBlack:
16319         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16320             SendToICS(ics_prefix);
16321             SendToICS("takeback 2\n");
16322         } else {
16323             SendToICS(ics_prefix);
16324             SendToICS("takeback 1\n");
16325         }
16326         break;
16327     }
16328 }
16329
16330 void
16331 MoveNowEvent ()
16332 {
16333     ChessProgramState *cps;
16334
16335     switch (gameMode) {
16336       case MachinePlaysWhite:
16337         if (!WhiteOnMove(forwardMostMove)) {
16338             DisplayError(_("It is your turn"), 0);
16339             return;
16340         }
16341         cps = &first;
16342         break;
16343       case MachinePlaysBlack:
16344         if (WhiteOnMove(forwardMostMove)) {
16345             DisplayError(_("It is your turn"), 0);
16346             return;
16347         }
16348         cps = &first;
16349         break;
16350       case TwoMachinesPlay:
16351         if (WhiteOnMove(forwardMostMove) ==
16352             (first.twoMachinesColor[0] == 'w')) {
16353             cps = &first;
16354         } else {
16355             cps = &second;
16356         }
16357         break;
16358       case BeginningOfGame:
16359       default:
16360         return;
16361     }
16362     SendToProgram("?\n", cps);
16363 }
16364
16365 void
16366 TruncateGameEvent ()
16367 {
16368     EditGameEvent();
16369     if (gameMode != EditGame) return;
16370     TruncateGame();
16371 }
16372
16373 void
16374 TruncateGame ()
16375 {
16376     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16377     if (forwardMostMove > currentMove) {
16378         if (gameInfo.resultDetails != NULL) {
16379             free(gameInfo.resultDetails);
16380             gameInfo.resultDetails = NULL;
16381             gameInfo.result = GameUnfinished;
16382         }
16383         forwardMostMove = currentMove;
16384         HistorySet(parseList, backwardMostMove, forwardMostMove,
16385                    currentMove-1);
16386     }
16387 }
16388
16389 void
16390 HintEvent ()
16391 {
16392     if (appData.noChessProgram) return;
16393     switch (gameMode) {
16394       case MachinePlaysWhite:
16395         if (WhiteOnMove(forwardMostMove)) {
16396             DisplayError(_("Wait until your turn."), 0);
16397             return;
16398         }
16399         break;
16400       case BeginningOfGame:
16401       case MachinePlaysBlack:
16402         if (!WhiteOnMove(forwardMostMove)) {
16403             DisplayError(_("Wait until your turn."), 0);
16404             return;
16405         }
16406         break;
16407       default:
16408         DisplayError(_("No hint available"), 0);
16409         return;
16410     }
16411     SendToProgram("hint\n", &first);
16412     hintRequested = TRUE;
16413 }
16414
16415 int
16416 SaveSelected (FILE *g, int dummy, char *dummy2)
16417 {
16418     ListGame * lg = (ListGame *) gameList.head;
16419     int nItem, cnt=0;
16420     FILE *f;
16421
16422     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16423         DisplayError(_("Game list not loaded or empty"), 0);
16424         return 0;
16425     }
16426
16427     creatingBook = TRUE; // suppresses stuff during load game
16428
16429     /* Get list size */
16430     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16431         if(lg->position >= 0) { // selected?
16432             LoadGame(f, nItem, "", TRUE);
16433             SaveGamePGN2(g); // leaves g open
16434             cnt++; DoEvents();
16435         }
16436         lg = (ListGame *) lg->node.succ;
16437     }
16438
16439     fclose(g);
16440     creatingBook = FALSE;
16441
16442     return cnt;
16443 }
16444
16445 void
16446 CreateBookEvent ()
16447 {
16448     ListGame * lg = (ListGame *) gameList.head;
16449     FILE *f, *g;
16450     int nItem;
16451     static int secondTime = FALSE;
16452
16453     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16454         DisplayError(_("Game list not loaded or empty"), 0);
16455         return;
16456     }
16457
16458     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16459         fclose(g);
16460         secondTime++;
16461         DisplayNote(_("Book file exists! Try again for overwrite."));
16462         return;
16463     }
16464
16465     creatingBook = TRUE;
16466     secondTime = FALSE;
16467
16468     /* Get list size */
16469     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16470         if(lg->position >= 0) {
16471             LoadGame(f, nItem, "", TRUE);
16472             AddGameToBook(TRUE);
16473             DoEvents();
16474         }
16475         lg = (ListGame *) lg->node.succ;
16476     }
16477
16478     creatingBook = FALSE;
16479     FlushBook();
16480 }
16481
16482 void
16483 BookEvent ()
16484 {
16485     if (appData.noChessProgram) return;
16486     switch (gameMode) {
16487       case MachinePlaysWhite:
16488         if (WhiteOnMove(forwardMostMove)) {
16489             DisplayError(_("Wait until your turn."), 0);
16490             return;
16491         }
16492         break;
16493       case BeginningOfGame:
16494       case MachinePlaysBlack:
16495         if (!WhiteOnMove(forwardMostMove)) {
16496             DisplayError(_("Wait until your turn."), 0);
16497             return;
16498         }
16499         break;
16500       case EditPosition:
16501         EditPositionDone(TRUE);
16502         break;
16503       case TwoMachinesPlay:
16504         return;
16505       default:
16506         break;
16507     }
16508     SendToProgram("bk\n", &first);
16509     bookOutput[0] = NULLCHAR;
16510     bookRequested = TRUE;
16511 }
16512
16513 void
16514 AboutGameEvent ()
16515 {
16516     char *tags = PGNTags(&gameInfo);
16517     TagsPopUp(tags, CmailMsg());
16518     free(tags);
16519 }
16520
16521 /* end button procedures */
16522
16523 void
16524 PrintPosition (FILE *fp, int move)
16525 {
16526     int i, j;
16527
16528     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16529         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16530             char c = PieceToChar(boards[move][i][j]);
16531             fputc(c == '?' ? '.' : c, fp);
16532             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16533         }
16534     }
16535     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16536       fprintf(fp, "white to play\n");
16537     else
16538       fprintf(fp, "black to play\n");
16539 }
16540
16541 void
16542 PrintOpponents (FILE *fp)
16543 {
16544     if (gameInfo.white != NULL) {
16545         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16546     } else {
16547         fprintf(fp, "\n");
16548     }
16549 }
16550
16551 /* Find last component of program's own name, using some heuristics */
16552 void
16553 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16554 {
16555     char *p, *q, c;
16556     int local = (strcmp(host, "localhost") == 0);
16557     while (!local && (p = strchr(prog, ';')) != NULL) {
16558         p++;
16559         while (*p == ' ') p++;
16560         prog = p;
16561     }
16562     if (*prog == '"' || *prog == '\'') {
16563         q = strchr(prog + 1, *prog);
16564     } else {
16565         q = strchr(prog, ' ');
16566     }
16567     if (q == NULL) q = prog + strlen(prog);
16568     p = q;
16569     while (p >= prog && *p != '/' && *p != '\\') p--;
16570     p++;
16571     if(p == prog && *p == '"') p++;
16572     c = *q; *q = 0;
16573     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16574     memcpy(buf, p, q - p);
16575     buf[q - p] = NULLCHAR;
16576     if (!local) {
16577         strcat(buf, "@");
16578         strcat(buf, host);
16579     }
16580 }
16581
16582 char *
16583 TimeControlTagValue ()
16584 {
16585     char buf[MSG_SIZ];
16586     if (!appData.clockMode) {
16587       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16588     } else if (movesPerSession > 0) {
16589       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16590     } else if (timeIncrement == 0) {
16591       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16592     } else {
16593       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16594     }
16595     return StrSave(buf);
16596 }
16597
16598 void
16599 SetGameInfo ()
16600 {
16601     /* This routine is used only for certain modes */
16602     VariantClass v = gameInfo.variant;
16603     ChessMove r = GameUnfinished;
16604     char *p = NULL;
16605
16606     if(keepInfo) return;
16607
16608     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16609         r = gameInfo.result;
16610         p = gameInfo.resultDetails;
16611         gameInfo.resultDetails = NULL;
16612     }
16613     ClearGameInfo(&gameInfo);
16614     gameInfo.variant = v;
16615
16616     switch (gameMode) {
16617       case MachinePlaysWhite:
16618         gameInfo.event = StrSave( appData.pgnEventHeader );
16619         gameInfo.site = StrSave(HostName());
16620         gameInfo.date = PGNDate();
16621         gameInfo.round = StrSave("-");
16622         gameInfo.white = StrSave(first.tidy);
16623         gameInfo.black = StrSave(UserName());
16624         gameInfo.timeControl = TimeControlTagValue();
16625         break;
16626
16627       case MachinePlaysBlack:
16628         gameInfo.event = StrSave( appData.pgnEventHeader );
16629         gameInfo.site = StrSave(HostName());
16630         gameInfo.date = PGNDate();
16631         gameInfo.round = StrSave("-");
16632         gameInfo.white = StrSave(UserName());
16633         gameInfo.black = StrSave(first.tidy);
16634         gameInfo.timeControl = TimeControlTagValue();
16635         break;
16636
16637       case TwoMachinesPlay:
16638         gameInfo.event = StrSave( appData.pgnEventHeader );
16639         gameInfo.site = StrSave(HostName());
16640         gameInfo.date = PGNDate();
16641         if (roundNr > 0) {
16642             char buf[MSG_SIZ];
16643             snprintf(buf, MSG_SIZ, "%d", roundNr);
16644             gameInfo.round = StrSave(buf);
16645         } else {
16646             gameInfo.round = StrSave("-");
16647         }
16648         if (first.twoMachinesColor[0] == 'w') {
16649             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16650             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16651         } else {
16652             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16653             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16654         }
16655         gameInfo.timeControl = TimeControlTagValue();
16656         break;
16657
16658       case EditGame:
16659         gameInfo.event = StrSave("Edited game");
16660         gameInfo.site = StrSave(HostName());
16661         gameInfo.date = PGNDate();
16662         gameInfo.round = StrSave("-");
16663         gameInfo.white = StrSave("-");
16664         gameInfo.black = StrSave("-");
16665         gameInfo.result = r;
16666         gameInfo.resultDetails = p;
16667         break;
16668
16669       case EditPosition:
16670         gameInfo.event = StrSave("Edited position");
16671         gameInfo.site = StrSave(HostName());
16672         gameInfo.date = PGNDate();
16673         gameInfo.round = StrSave("-");
16674         gameInfo.white = StrSave("-");
16675         gameInfo.black = StrSave("-");
16676         break;
16677
16678       case IcsPlayingWhite:
16679       case IcsPlayingBlack:
16680       case IcsObserving:
16681       case IcsExamining:
16682         break;
16683
16684       case PlayFromGameFile:
16685         gameInfo.event = StrSave("Game from non-PGN file");
16686         gameInfo.site = StrSave(HostName());
16687         gameInfo.date = PGNDate();
16688         gameInfo.round = StrSave("-");
16689         gameInfo.white = StrSave("?");
16690         gameInfo.black = StrSave("?");
16691         break;
16692
16693       default:
16694         break;
16695     }
16696 }
16697
16698 void
16699 ReplaceComment (int index, char *text)
16700 {
16701     int len;
16702     char *p;
16703     float score;
16704
16705     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16706        pvInfoList[index-1].depth == len &&
16707        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16708        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16709     while (*text == '\n') text++;
16710     len = strlen(text);
16711     while (len > 0 && text[len - 1] == '\n') len--;
16712
16713     if (commentList[index] != NULL)
16714       free(commentList[index]);
16715
16716     if (len == 0) {
16717         commentList[index] = NULL;
16718         return;
16719     }
16720   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16721       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16722       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16723     commentList[index] = (char *) malloc(len + 2);
16724     strncpy(commentList[index], text, len);
16725     commentList[index][len] = '\n';
16726     commentList[index][len + 1] = NULLCHAR;
16727   } else {
16728     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16729     char *p;
16730     commentList[index] = (char *) malloc(len + 7);
16731     safeStrCpy(commentList[index], "{\n", 3);
16732     safeStrCpy(commentList[index]+2, text, len+1);
16733     commentList[index][len+2] = NULLCHAR;
16734     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16735     strcat(commentList[index], "\n}\n");
16736   }
16737 }
16738
16739 void
16740 CrushCRs (char *text)
16741 {
16742   char *p = text;
16743   char *q = text;
16744   char ch;
16745
16746   do {
16747     ch = *p++;
16748     if (ch == '\r') continue;
16749     *q++ = ch;
16750   } while (ch != '\0');
16751 }
16752
16753 void
16754 AppendComment (int index, char *text, Boolean addBraces)
16755 /* addBraces  tells if we should add {} */
16756 {
16757     int oldlen, len;
16758     char *old;
16759
16760 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16761     if(addBraces == 3) addBraces = 0; else // force appending literally
16762     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16763
16764     CrushCRs(text);
16765     while (*text == '\n') text++;
16766     len = strlen(text);
16767     while (len > 0 && text[len - 1] == '\n') len--;
16768     text[len] = NULLCHAR;
16769
16770     if (len == 0) return;
16771
16772     if (commentList[index] != NULL) {
16773       Boolean addClosingBrace = addBraces;
16774         old = commentList[index];
16775         oldlen = strlen(old);
16776         while(commentList[index][oldlen-1] ==  '\n')
16777           commentList[index][--oldlen] = NULLCHAR;
16778         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16779         safeStrCpy(commentList[index], old, oldlen + len + 6);
16780         free(old);
16781         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16782         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16783           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16784           while (*text == '\n') { text++; len--; }
16785           commentList[index][--oldlen] = NULLCHAR;
16786       }
16787         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16788         else          strcat(commentList[index], "\n");
16789         strcat(commentList[index], text);
16790         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16791         else          strcat(commentList[index], "\n");
16792     } else {
16793         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16794         if(addBraces)
16795           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16796         else commentList[index][0] = NULLCHAR;
16797         strcat(commentList[index], text);
16798         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16799         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16800     }
16801 }
16802
16803 static char *
16804 FindStr (char * text, char * sub_text)
16805 {
16806     char * result = strstr( text, sub_text );
16807
16808     if( result != NULL ) {
16809         result += strlen( sub_text );
16810     }
16811
16812     return result;
16813 }
16814
16815 /* [AS] Try to extract PV info from PGN comment */
16816 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16817 char *
16818 GetInfoFromComment (int index, char * text)
16819 {
16820     char * sep = text, *p;
16821
16822     if( text != NULL && index > 0 ) {
16823         int score = 0;
16824         int depth = 0;
16825         int time = -1, sec = 0, deci;
16826         char * s_eval = FindStr( text, "[%eval " );
16827         char * s_emt = FindStr( text, "[%emt " );
16828 #if 0
16829         if( s_eval != NULL || s_emt != NULL ) {
16830 #else
16831         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16832 #endif
16833             /* New style */
16834             char delim;
16835
16836             if( s_eval != NULL ) {
16837                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16838                     return text;
16839                 }
16840
16841                 if( delim != ']' ) {
16842                     return text;
16843                 }
16844             }
16845
16846             if( s_emt != NULL ) {
16847             }
16848                 return text;
16849         }
16850         else {
16851             /* We expect something like: [+|-]nnn.nn/dd */
16852             int score_lo = 0;
16853
16854             if(*text != '{') return text; // [HGM] braces: must be normal comment
16855
16856             sep = strchr( text, '/' );
16857             if( sep == NULL || sep < (text+4) ) {
16858                 return text;
16859             }
16860
16861             p = text;
16862             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16863             if(p[1] == '(') { // comment starts with PV
16864                p = strchr(p, ')'); // locate end of PV
16865                if(p == NULL || sep < p+5) return text;
16866                // at this point we have something like "{(.*) +0.23/6 ..."
16867                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16868                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16869                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16870             }
16871             time = -1; sec = -1; deci = -1;
16872             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16873                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16874                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16875                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16876                 return text;
16877             }
16878
16879             if( score_lo < 0 || score_lo >= 100 ) {
16880                 return text;
16881             }
16882
16883             if(sec >= 0) time = 600*time + 10*sec; else
16884             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16885
16886             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16887
16888             /* [HGM] PV time: now locate end of PV info */
16889             while( *++sep >= '0' && *sep <= '9'); // strip depth
16890             if(time >= 0)
16891             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16892             if(sec >= 0)
16893             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16894             if(deci >= 0)
16895             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16896             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16897         }
16898
16899         if( depth <= 0 ) {
16900             return text;
16901         }
16902
16903         if( time < 0 ) {
16904             time = -1;
16905         }
16906
16907         pvInfoList[index-1].depth = depth;
16908         pvInfoList[index-1].score = score;
16909         pvInfoList[index-1].time  = 10*time; // centi-sec
16910         if(*sep == '}') *sep = 0; else *--sep = '{';
16911         if(p != text) {
16912             while(*p++ = *sep++)
16913                                 ;
16914             sep = text;
16915         } // squeeze out space between PV and comment, and return both
16916     }
16917     return sep;
16918 }
16919
16920 void
16921 SendToProgram (char *message, ChessProgramState *cps)
16922 {
16923     int count, outCount, error;
16924     char buf[MSG_SIZ];
16925
16926     if (cps->pr == NoProc) return;
16927     Attention(cps);
16928
16929     if (appData.debugMode) {
16930         TimeMark now;
16931         GetTimeMark(&now);
16932         fprintf(debugFP, "%ld >%-6s: %s",
16933                 SubtractTimeMarks(&now, &programStartTime),
16934                 cps->which, message);
16935         if(serverFP)
16936             fprintf(serverFP, "%ld >%-6s: %s",
16937                 SubtractTimeMarks(&now, &programStartTime),
16938                 cps->which, message), fflush(serverFP);
16939     }
16940
16941     count = strlen(message);
16942     outCount = OutputToProcess(cps->pr, message, count, &error);
16943     if (outCount < count && !exiting
16944                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16945       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16946       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16947         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16948             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16949                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16950                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16951                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16952             } else {
16953                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16954                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16955                 gameInfo.result = res;
16956             }
16957             gameInfo.resultDetails = StrSave(buf);
16958         }
16959         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16960         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16961     }
16962 }
16963
16964 void
16965 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16966 {
16967     char *end_str;
16968     char buf[MSG_SIZ];
16969     ChessProgramState *cps = (ChessProgramState *)closure;
16970
16971     if (isr != cps->isr) return; /* Killed intentionally */
16972     if (count <= 0) {
16973         if (count == 0) {
16974             RemoveInputSource(cps->isr);
16975             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16976                     _(cps->which), cps->program);
16977             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16978             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16979                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16980                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16981                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16982                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16983                 } else {
16984                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16985                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16986                     gameInfo.result = res;
16987                 }
16988                 gameInfo.resultDetails = StrSave(buf);
16989             }
16990             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16991             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16992         } else {
16993             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16994                     _(cps->which), cps->program);
16995             RemoveInputSource(cps->isr);
16996
16997             /* [AS] Program is misbehaving badly... kill it */
16998             if( count == -2 ) {
16999                 DestroyChildProcess( cps->pr, 9 );
17000                 cps->pr = NoProc;
17001             }
17002
17003             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
17004         }
17005         return;
17006     }
17007
17008     if ((end_str = strchr(message, '\r')) != NULL)
17009       *end_str = NULLCHAR;
17010     if ((end_str = strchr(message, '\n')) != NULL)
17011       *end_str = NULLCHAR;
17012
17013     if (appData.debugMode) {
17014         TimeMark now; int print = 1;
17015         char *quote = ""; char c; int i;
17016
17017         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
17018                 char start = message[0];
17019                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
17020                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
17021                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
17022                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
17023                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
17024                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
17025                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
17026                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
17027                    sscanf(message, "hint: %c", &c)!=1 &&
17028                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
17029                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
17030                     print = (appData.engineComments >= 2);
17031                 }
17032                 message[0] = start; // restore original message
17033         }
17034         if(print) {
17035                 GetTimeMark(&now);
17036                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
17037                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17038                         quote,
17039                         message);
17040                 if(serverFP)
17041                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
17042                         SubtractTimeMarks(&now, &programStartTime), cps->which,
17043                         quote,
17044                         message), fflush(serverFP);
17045         }
17046     }
17047
17048     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
17049     if (appData.icsEngineAnalyze) {
17050         if (strstr(message, "whisper") != NULL ||
17051              strstr(message, "kibitz") != NULL ||
17052             strstr(message, "tellics") != NULL) return;
17053     }
17054
17055     HandleMachineMove(message, cps);
17056 }
17057
17058
17059 void
17060 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
17061 {
17062     char buf[MSG_SIZ];
17063     int seconds;
17064
17065     if( timeControl_2 > 0 ) {
17066         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
17067             tc = timeControl_2;
17068         }
17069     }
17070     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
17071     inc /= cps->timeOdds;
17072     st  /= cps->timeOdds;
17073
17074     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
17075
17076     if (st > 0) {
17077       /* Set exact time per move, normally using st command */
17078       if (cps->stKludge) {
17079         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
17080         seconds = st % 60;
17081         if (seconds == 0) {
17082           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
17083         } else {
17084           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
17085         }
17086       } else {
17087         snprintf(buf, MSG_SIZ, "st %d\n", st);
17088       }
17089     } else {
17090       /* Set conventional or incremental time control, using level command */
17091       if (seconds == 0) {
17092         /* Note old gnuchess bug -- minutes:seconds used to not work.
17093            Fixed in later versions, but still avoid :seconds
17094            when seconds is 0. */
17095         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
17096       } else {
17097         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
17098                  seconds, inc/1000.);
17099       }
17100     }
17101     SendToProgram(buf, cps);
17102
17103     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
17104     /* Orthogonally, limit search to given depth */
17105     if (sd > 0) {
17106       if (cps->sdKludge) {
17107         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
17108       } else {
17109         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
17110       }
17111       SendToProgram(buf, cps);
17112     }
17113
17114     if(cps->nps >= 0) { /* [HGM] nps */
17115         if(cps->supportsNPS == FALSE)
17116           cps->nps = -1; // don't use if engine explicitly says not supported!
17117         else {
17118           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
17119           SendToProgram(buf, cps);
17120         }
17121     }
17122 }
17123
17124 ChessProgramState *
17125 WhitePlayer ()
17126 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
17127 {
17128     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
17129        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
17130         return &second;
17131     return &first;
17132 }
17133
17134 void
17135 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
17136 {
17137     char message[MSG_SIZ];
17138     long time, otime;
17139
17140     /* Note: this routine must be called when the clocks are stopped
17141        or when they have *just* been set or switched; otherwise
17142        it will be off by the time since the current tick started.
17143     */
17144     if (machineWhite) {
17145         time = whiteTimeRemaining / 10;
17146         otime = blackTimeRemaining / 10;
17147     } else {
17148         time = blackTimeRemaining / 10;
17149         otime = whiteTimeRemaining / 10;
17150     }
17151     /* [HGM] translate opponent's time by time-odds factor */
17152     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17153
17154     if (time <= 0) time = 1;
17155     if (otime <= 0) otime = 1;
17156
17157     snprintf(message, MSG_SIZ, "time %ld\n", time);
17158     SendToProgram(message, cps);
17159
17160     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17161     SendToProgram(message, cps);
17162 }
17163
17164 char *
17165 EngineDefinedVariant (ChessProgramState *cps, int n)
17166 {   // return name of n-th unknown variant that engine supports
17167     static char buf[MSG_SIZ];
17168     char *p, *s = cps->variants;
17169     if(!s) return NULL;
17170     do { // parse string from variants feature
17171       VariantClass v;
17172         p = strchr(s, ',');
17173         if(p) *p = NULLCHAR;
17174       v = StringToVariant(s);
17175       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17176         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17177             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17178                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17179                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17180                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17181             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17182         }
17183         if(p) *p++ = ',';
17184         if(n < 0) return buf;
17185     } while(s = p);
17186     return NULL;
17187 }
17188
17189 int
17190 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17191 {
17192   char buf[MSG_SIZ];
17193   int len = strlen(name);
17194   int val;
17195
17196   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17197     (*p) += len + 1;
17198     sscanf(*p, "%d", &val);
17199     *loc = (val != 0);
17200     while (**p && **p != ' ')
17201       (*p)++;
17202     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17203     SendToProgram(buf, cps);
17204     return TRUE;
17205   }
17206   return FALSE;
17207 }
17208
17209 int
17210 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17211 {
17212   char buf[MSG_SIZ];
17213   int len = strlen(name);
17214   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17215     (*p) += len + 1;
17216     sscanf(*p, "%d", loc);
17217     while (**p && **p != ' ') (*p)++;
17218     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17219     SendToProgram(buf, cps);
17220     return TRUE;
17221   }
17222   return FALSE;
17223 }
17224
17225 int
17226 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17227 {
17228   char buf[MSG_SIZ];
17229   int len = strlen(name);
17230   if (strncmp((*p), name, len) == 0
17231       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17232     (*p) += len + 2;
17233     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
17234     FREE(*loc); *loc = malloc(len);
17235     strncpy(*loc, *p, len);
17236     sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
17237     while (**p && **p != '\"') (*p)++;
17238     if (**p == '\"') (*p)++;
17239     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17240     SendToProgram(buf, cps);
17241     return TRUE;
17242   }
17243   return FALSE;
17244 }
17245
17246 int
17247 ParseOption (Option *opt, ChessProgramState *cps)
17248 // [HGM] options: process the string that defines an engine option, and determine
17249 // name, type, default value, and allowed value range
17250 {
17251         char *p, *q, buf[MSG_SIZ];
17252         int n, min = (-1)<<31, max = 1<<31, def;
17253
17254         opt->target = &opt->value;   // OK for spin/slider and checkbox
17255         if(p = strstr(opt->name, " -spin ")) {
17256             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17257             if(max < min) max = min; // enforce consistency
17258             if(def < min) def = min;
17259             if(def > max) def = max;
17260             opt->value = def;
17261             opt->min = min;
17262             opt->max = max;
17263             opt->type = Spin;
17264         } else if((p = strstr(opt->name, " -slider "))) {
17265             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17266             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17267             if(max < min) max = min; // enforce consistency
17268             if(def < min) def = min;
17269             if(def > max) def = max;
17270             opt->value = def;
17271             opt->min = min;
17272             opt->max = max;
17273             opt->type = Spin; // Slider;
17274         } else if((p = strstr(opt->name, " -string "))) {
17275             opt->textValue = p+9;
17276             opt->type = TextBox;
17277             opt->target = &opt->textValue;
17278         } else if((p = strstr(opt->name, " -file "))) {
17279             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17280             opt->target = opt->textValue = p+7;
17281             opt->type = FileName; // FileName;
17282             opt->target = &opt->textValue;
17283         } else if((p = strstr(opt->name, " -path "))) {
17284             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17285             opt->target = opt->textValue = p+7;
17286             opt->type = PathName; // PathName;
17287             opt->target = &opt->textValue;
17288         } else if(p = strstr(opt->name, " -check ")) {
17289             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17290             opt->value = (def != 0);
17291             opt->type = CheckBox;
17292         } else if(p = strstr(opt->name, " -combo ")) {
17293             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17294             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17295             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17296             opt->value = n = 0;
17297             while(q = StrStr(q, " /// ")) {
17298                 n++; *q = 0;    // count choices, and null-terminate each of them
17299                 q += 5;
17300                 if(*q == '*') { // remember default, which is marked with * prefix
17301                     q++;
17302                     opt->value = n;
17303                 }
17304                 cps->comboList[cps->comboCnt++] = q;
17305             }
17306             cps->comboList[cps->comboCnt++] = NULL;
17307             opt->max = n + 1;
17308             opt->type = ComboBox;
17309         } else if(p = strstr(opt->name, " -button")) {
17310             opt->type = Button;
17311         } else if(p = strstr(opt->name, " -save")) {
17312             opt->type = SaveButton;
17313         } else return FALSE;
17314         *p = 0; // terminate option name
17315         // now look if the command-line options define a setting for this engine option.
17316         if(cps->optionSettings && cps->optionSettings[0])
17317             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17318         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17319           snprintf(buf, MSG_SIZ, "option %s", p);
17320                 if(p = strstr(buf, ",")) *p = 0;
17321                 if(q = strchr(buf, '=')) switch(opt->type) {
17322                     case ComboBox:
17323                         for(n=0; n<opt->max; n++)
17324                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17325                         break;
17326                     case TextBox:
17327                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17328                         break;
17329                     case Spin:
17330                     case CheckBox:
17331                         opt->value = atoi(q+1);
17332                     default:
17333                         break;
17334                 }
17335                 strcat(buf, "\n");
17336                 SendToProgram(buf, cps);
17337         }
17338         *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
17339         if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
17340         return TRUE;
17341 }
17342
17343 void
17344 FeatureDone (ChessProgramState *cps, int val)
17345 {
17346   DelayedEventCallback cb = GetDelayedEvent();
17347   if ((cb == InitBackEnd3 && cps == &first) ||
17348       (cb == SettingsMenuIfReady && cps == &second) ||
17349       (cb == LoadEngine) ||
17350       (cb == TwoMachinesEventIfReady)) {
17351     CancelDelayedEvent();
17352     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17353   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17354   cps->initDone = val;
17355   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17356 }
17357
17358 /* Parse feature command from engine */
17359 void
17360 ParseFeatures (char *args, ChessProgramState *cps)
17361 {
17362   char *p = args;
17363   char *q = NULL;
17364   int val;
17365   char buf[MSG_SIZ];
17366
17367   for (;;) {
17368     while (*p == ' ') p++;
17369     if (*p == NULLCHAR) return;
17370
17371     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17372     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17373     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17374     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17375     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17376     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17377     if (BoolFeature(&p, "reuse", &val, cps)) {
17378       /* Engine can disable reuse, but can't enable it if user said no */
17379       if (!val) cps->reuse = FALSE;
17380       continue;
17381     }
17382     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17383     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17384       if (gameMode == TwoMachinesPlay) {
17385         DisplayTwoMachinesTitle();
17386       } else {
17387         DisplayTitle("");
17388       }
17389       continue;
17390     }
17391     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17392     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17393     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17394     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17395     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17396     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17397     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17398     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17399     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17400     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17401     if (IntFeature(&p, "done", &val, cps)) {
17402       FeatureDone(cps, val);
17403       continue;
17404     }
17405     /* Added by Tord: */
17406     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17407     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17408     /* End of additions by Tord */
17409
17410     /* [HGM] added features: */
17411     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17412     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17413     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17414     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17415     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17416     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17417     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17418     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17419         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17420         if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
17421         FREE(cps->option[cps->nrOptions].name);
17422         cps->option[cps->nrOptions].name = q; q = NULL;
17423         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17424           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17425             SendToProgram(buf, cps);
17426             continue;
17427         }
17428         if(cps->nrOptions >= MAX_OPTIONS) {
17429             cps->nrOptions--;
17430             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17431             DisplayError(buf, 0);
17432         }
17433         continue;
17434     }
17435     /* End of additions by HGM */
17436
17437     /* unknown feature: complain and skip */
17438     q = p;
17439     while (*q && *q != '=') q++;
17440     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17441     SendToProgram(buf, cps);
17442     p = q;
17443     if (*p == '=') {
17444       p++;
17445       if (*p == '\"') {
17446         p++;
17447         while (*p && *p != '\"') p++;
17448         if (*p == '\"') p++;
17449       } else {
17450         while (*p && *p != ' ') p++;
17451       }
17452     }
17453   }
17454
17455 }
17456
17457 void
17458 PeriodicUpdatesEvent (int newState)
17459 {
17460     if (newState == appData.periodicUpdates)
17461       return;
17462
17463     appData.periodicUpdates=newState;
17464
17465     /* Display type changes, so update it now */
17466 //    DisplayAnalysis();
17467
17468     /* Get the ball rolling again... */
17469     if (newState) {
17470         AnalysisPeriodicEvent(1);
17471         StartAnalysisClock();
17472     }
17473 }
17474
17475 void
17476 PonderNextMoveEvent (int newState)
17477 {
17478     if (newState == appData.ponderNextMove) return;
17479     if (gameMode == EditPosition) EditPositionDone(TRUE);
17480     if (newState) {
17481         SendToProgram("hard\n", &first);
17482         if (gameMode == TwoMachinesPlay) {
17483             SendToProgram("hard\n", &second);
17484         }
17485     } else {
17486         SendToProgram("easy\n", &first);
17487         thinkOutput[0] = NULLCHAR;
17488         if (gameMode == TwoMachinesPlay) {
17489             SendToProgram("easy\n", &second);
17490         }
17491     }
17492     appData.ponderNextMove = newState;
17493 }
17494
17495 void
17496 NewSettingEvent (int option, int *feature, char *command, int value)
17497 {
17498     char buf[MSG_SIZ];
17499
17500     if (gameMode == EditPosition) EditPositionDone(TRUE);
17501     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17502     if(feature == NULL || *feature) SendToProgram(buf, &first);
17503     if (gameMode == TwoMachinesPlay) {
17504         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17505     }
17506 }
17507
17508 void
17509 ShowThinkingEvent ()
17510 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17511 {
17512     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17513     int newState = appData.showThinking
17514         // [HGM] thinking: other features now need thinking output as well
17515         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17516
17517     if (oldState == newState) return;
17518     oldState = newState;
17519     if (gameMode == EditPosition) EditPositionDone(TRUE);
17520     if (oldState) {
17521         SendToProgram("post\n", &first);
17522         if (gameMode == TwoMachinesPlay) {
17523             SendToProgram("post\n", &second);
17524         }
17525     } else {
17526         SendToProgram("nopost\n", &first);
17527         thinkOutput[0] = NULLCHAR;
17528         if (gameMode == TwoMachinesPlay) {
17529             SendToProgram("nopost\n", &second);
17530         }
17531     }
17532 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17533 }
17534
17535 void
17536 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17537 {
17538   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17539   if (pr == NoProc) return;
17540   AskQuestion(title, question, replyPrefix, pr);
17541 }
17542
17543 void
17544 TypeInEvent (char firstChar)
17545 {
17546     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17547         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17548         gameMode == AnalyzeMode || gameMode == EditGame ||
17549         gameMode == EditPosition || gameMode == IcsExamining ||
17550         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17551         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17552                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17553                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17554         gameMode == Training) PopUpMoveDialog(firstChar);
17555 }
17556
17557 void
17558 TypeInDoneEvent (char *move)
17559 {
17560         Board board;
17561         int n, fromX, fromY, toX, toY;
17562         char promoChar;
17563         ChessMove moveType;
17564
17565         // [HGM] FENedit
17566         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17567                 EditPositionPasteFEN(move);
17568                 return;
17569         }
17570         // [HGM] movenum: allow move number to be typed in any mode
17571         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17572           ToNrEvent(2*n-1);
17573           return;
17574         }
17575         // undocumented kludge: allow command-line option to be typed in!
17576         // (potentially fatal, and does not implement the effect of the option.)
17577         // should only be used for options that are values on which future decisions will be made,
17578         // and definitely not on options that would be used during initialization.
17579         if(strstr(move, "!!! -") == move) {
17580             ParseArgsFromString(move+4);
17581             return;
17582         }
17583
17584       if (gameMode != EditGame && currentMove != forwardMostMove &&
17585         gameMode != Training) {
17586         DisplayMoveError(_("Displayed move is not current"));
17587       } else {
17588         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17589           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17590         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17591         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17592           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17593           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17594         } else {
17595           DisplayMoveError(_("Could not parse move"));
17596         }
17597       }
17598 }
17599
17600 void
17601 DisplayMove (int moveNumber)
17602 {
17603     char message[MSG_SIZ];
17604     char res[MSG_SIZ];
17605     char cpThinkOutput[MSG_SIZ];
17606
17607     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17608
17609     if (moveNumber == forwardMostMove - 1 ||
17610         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17611
17612         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17613
17614         if (strchr(cpThinkOutput, '\n')) {
17615             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17616         }
17617     } else {
17618         *cpThinkOutput = NULLCHAR;
17619     }
17620
17621     /* [AS] Hide thinking from human user */
17622     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17623         *cpThinkOutput = NULLCHAR;
17624         if( thinkOutput[0] != NULLCHAR ) {
17625             int i;
17626
17627             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17628                 cpThinkOutput[i] = '.';
17629             }
17630             cpThinkOutput[i] = NULLCHAR;
17631             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17632         }
17633     }
17634
17635     if (moveNumber == forwardMostMove - 1 &&
17636         gameInfo.resultDetails != NULL) {
17637         if (gameInfo.resultDetails[0] == NULLCHAR) {
17638           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17639         } else {
17640           snprintf(res, MSG_SIZ, " {%s} %s",
17641                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17642         }
17643     } else {
17644         res[0] = NULLCHAR;
17645     }
17646
17647     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17648         DisplayMessage(res, cpThinkOutput);
17649     } else {
17650       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17651                 WhiteOnMove(moveNumber) ? " " : ".. ",
17652                 parseList[moveNumber], res);
17653         DisplayMessage(message, cpThinkOutput);
17654     }
17655 }
17656
17657 void
17658 DisplayComment (int moveNumber, char *text)
17659 {
17660     char title[MSG_SIZ];
17661
17662     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17663       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17664     } else {
17665       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17666               WhiteOnMove(moveNumber) ? " " : ".. ",
17667               parseList[moveNumber]);
17668     }
17669     if (text != NULL && (appData.autoDisplayComment || commentUp))
17670         CommentPopUp(title, text);
17671 }
17672
17673 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17674  * might be busy thinking or pondering.  It can be omitted if your
17675  * gnuchess is configured to stop thinking immediately on any user
17676  * input.  However, that gnuchess feature depends on the FIONREAD
17677  * ioctl, which does not work properly on some flavors of Unix.
17678  */
17679 void
17680 Attention (ChessProgramState *cps)
17681 {
17682 #if ATTENTION
17683     if (!cps->useSigint) return;
17684     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17685     switch (gameMode) {
17686       case MachinePlaysWhite:
17687       case MachinePlaysBlack:
17688       case TwoMachinesPlay:
17689       case IcsPlayingWhite:
17690       case IcsPlayingBlack:
17691       case AnalyzeMode:
17692       case AnalyzeFile:
17693         /* Skip if we know it isn't thinking */
17694         if (!cps->maybeThinking) return;
17695         if (appData.debugMode)
17696           fprintf(debugFP, "Interrupting %s\n", cps->which);
17697         InterruptChildProcess(cps->pr);
17698         cps->maybeThinking = FALSE;
17699         break;
17700       default:
17701         break;
17702     }
17703 #endif /*ATTENTION*/
17704 }
17705
17706 int
17707 CheckFlags ()
17708 {
17709     if (whiteTimeRemaining <= 0) {
17710         if (!whiteFlag) {
17711             whiteFlag = TRUE;
17712             if (appData.icsActive) {
17713                 if (appData.autoCallFlag &&
17714                     gameMode == IcsPlayingBlack && !blackFlag) {
17715                   SendToICS(ics_prefix);
17716                   SendToICS("flag\n");
17717                 }
17718             } else {
17719                 if (blackFlag) {
17720                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17721                 } else {
17722                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17723                     if (appData.autoCallFlag) {
17724                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17725                         return TRUE;
17726                     }
17727                 }
17728             }
17729         }
17730     }
17731     if (blackTimeRemaining <= 0) {
17732         if (!blackFlag) {
17733             blackFlag = TRUE;
17734             if (appData.icsActive) {
17735                 if (appData.autoCallFlag &&
17736                     gameMode == IcsPlayingWhite && !whiteFlag) {
17737                   SendToICS(ics_prefix);
17738                   SendToICS("flag\n");
17739                 }
17740             } else {
17741                 if (whiteFlag) {
17742                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17743                 } else {
17744                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17745                     if (appData.autoCallFlag) {
17746                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17747                         return TRUE;
17748                     }
17749                 }
17750             }
17751         }
17752     }
17753     return FALSE;
17754 }
17755
17756 void
17757 CheckTimeControl ()
17758 {
17759     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17760         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17761
17762     /*
17763      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17764      */
17765     if ( !WhiteOnMove(forwardMostMove) ) {
17766         /* White made time control */
17767         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17768         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17769         /* [HGM] time odds: correct new time quota for time odds! */
17770                                             / WhitePlayer()->timeOdds;
17771         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17772     } else {
17773         lastBlack -= blackTimeRemaining;
17774         /* Black made time control */
17775         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17776                                             / WhitePlayer()->other->timeOdds;
17777         lastWhite = whiteTimeRemaining;
17778     }
17779 }
17780
17781 void
17782 DisplayBothClocks ()
17783 {
17784     int wom = gameMode == EditPosition ?
17785       !blackPlaysFirst : WhiteOnMove(currentMove);
17786     DisplayWhiteClock(whiteTimeRemaining, wom);
17787     DisplayBlackClock(blackTimeRemaining, !wom);
17788 }
17789
17790
17791 /* Timekeeping seems to be a portability nightmare.  I think everyone
17792    has ftime(), but I'm really not sure, so I'm including some ifdefs
17793    to use other calls if you don't.  Clocks will be less accurate if
17794    you have neither ftime nor gettimeofday.
17795 */
17796
17797 /* VS 2008 requires the #include outside of the function */
17798 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17799 #include <sys/timeb.h>
17800 #endif
17801
17802 /* Get the current time as a TimeMark */
17803 void
17804 GetTimeMark (TimeMark *tm)
17805 {
17806 #if HAVE_GETTIMEOFDAY
17807
17808     struct timeval timeVal;
17809     struct timezone timeZone;
17810
17811     gettimeofday(&timeVal, &timeZone);
17812     tm->sec = (long) timeVal.tv_sec;
17813     tm->ms = (int) (timeVal.tv_usec / 1000L);
17814
17815 #else /*!HAVE_GETTIMEOFDAY*/
17816 #if HAVE_FTIME
17817
17818 // include <sys/timeb.h> / moved to just above start of function
17819     struct timeb timeB;
17820
17821     ftime(&timeB);
17822     tm->sec = (long) timeB.time;
17823     tm->ms = (int) timeB.millitm;
17824
17825 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17826     tm->sec = (long) time(NULL);
17827     tm->ms = 0;
17828 #endif
17829 #endif
17830 }
17831
17832 /* Return the difference in milliseconds between two
17833    time marks.  We assume the difference will fit in a long!
17834 */
17835 long
17836 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17837 {
17838     return 1000L*(tm2->sec - tm1->sec) +
17839            (long) (tm2->ms - tm1->ms);
17840 }
17841
17842
17843 /*
17844  * Code to manage the game clocks.
17845  *
17846  * In tournament play, black starts the clock and then white makes a move.
17847  * We give the human user a slight advantage if he is playing white---the
17848  * clocks don't run until he makes his first move, so it takes zero time.
17849  * Also, we don't account for network lag, so we could get out of sync
17850  * with GNU Chess's clock -- but then, referees are always right.
17851  */
17852
17853 static TimeMark tickStartTM;
17854 static long intendedTickLength;
17855
17856 long
17857 NextTickLength (long timeRemaining)
17858 {
17859     long nominalTickLength, nextTickLength;
17860
17861     if (timeRemaining > 0L && timeRemaining <= 10000L)
17862       nominalTickLength = 100L;
17863     else
17864       nominalTickLength = 1000L;
17865     nextTickLength = timeRemaining % nominalTickLength;
17866     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17867
17868     return nextTickLength;
17869 }
17870
17871 /* Adjust clock one minute up or down */
17872 void
17873 AdjustClock (Boolean which, int dir)
17874 {
17875     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17876     if(which) blackTimeRemaining += 60000*dir;
17877     else      whiteTimeRemaining += 60000*dir;
17878     DisplayBothClocks();
17879     adjustedClock = TRUE;
17880 }
17881
17882 /* Stop clocks and reset to a fresh time control */
17883 void
17884 ResetClocks ()
17885 {
17886     (void) StopClockTimer();
17887     if (appData.icsActive) {
17888         whiteTimeRemaining = blackTimeRemaining = 0;
17889     } else if (searchTime) {
17890         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17891         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17892     } else { /* [HGM] correct new time quote for time odds */
17893         whiteTC = blackTC = fullTimeControlString;
17894         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17895         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17896     }
17897     if (whiteFlag || blackFlag) {
17898         DisplayTitle("");
17899         whiteFlag = blackFlag = FALSE;
17900     }
17901     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17902     DisplayBothClocks();
17903     adjustedClock = FALSE;
17904 }
17905
17906 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17907
17908 static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
17909
17910 /* Decrement running clock by amount of time that has passed */
17911 void
17912 DecrementClocks ()
17913 {
17914     long tRemaining;
17915     long lastTickLength, fudge;
17916     TimeMark now;
17917
17918     if (!appData.clockMode) return;
17919     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17920
17921     GetTimeMark(&now);
17922
17923     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17924
17925     /* Fudge if we woke up a little too soon */
17926     fudge = intendedTickLength - lastTickLength;
17927     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17928
17929     if (WhiteOnMove(forwardMostMove)) {
17930         if(whiteNPS >= 0) lastTickLength = 0;
17931          tRemaining = whiteTimeRemaining -= lastTickLength;
17932         if( tRemaining < 0 && !appData.icsActive) {
17933             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17934             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17935                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17936                 lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17937             }
17938         }
17939         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
17940         DisplayWhiteClock(whiteTimeRemaining - fudge,
17941                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17942         timeSuffix = 0;
17943     } else {
17944         if(blackNPS >= 0) lastTickLength = 0;
17945          tRemaining = blackTimeRemaining -= lastTickLength;
17946         if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17947             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17948             if(suddenDeath) {
17949                 blackStartMove = forwardMostMove;
17950                 lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17951             }
17952         }
17953         if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
17954         DisplayBlackClock(blackTimeRemaining - fudge,
17955                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17956         timeSuffix = 0;
17957     }
17958     if (CheckFlags()) return;
17959
17960     if(twoBoards) { // count down secondary board's clocks as well
17961         activePartnerTime -= lastTickLength;
17962         partnerUp = 1;
17963         if(activePartner == 'W')
17964             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17965         else
17966             DisplayBlackClock(activePartnerTime, TRUE);
17967         partnerUp = 0;
17968     }
17969
17970     tickStartTM = now;
17971     intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
17972     StartClockTimer(intendedTickLength);
17973
17974     /* if the time remaining has fallen below the alarm threshold, sound the
17975      * alarm. if the alarm has sounded and (due to a takeback or time control
17976      * with increment) the time remaining has increased to a level above the
17977      * threshold, reset the alarm so it can sound again.
17978      */
17979
17980     if (appData.icsActive && appData.icsAlarm) {
17981
17982         /* make sure we are dealing with the user's clock */
17983         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17984                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17985            )) return;
17986
17987         if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
17988             alarmSounded = FALSE;
17989         } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
17990             PlayAlarmSound();
17991             alarmSounded = TRUE;
17992         }
17993     }
17994 }
17995
17996
17997 /* A player has just moved, so stop the previously running
17998    clock and (if in clock mode) start the other one.
17999    We redisplay both clocks in case we're in ICS mode, because
18000    ICS gives us an update to both clocks after every move.
18001    Note that this routine is called *after* forwardMostMove
18002    is updated, so the last fractional tick must be subtracted
18003    from the color that is *not* on move now.
18004 */
18005 void
18006 SwitchClocks (int newMoveNr)
18007 {
18008     long lastTickLength;
18009     TimeMark now;
18010     int flagged = FALSE;
18011
18012     GetTimeMark(&now);
18013
18014     if (StopClockTimer() && appData.clockMode) {
18015         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18016         if (!WhiteOnMove(forwardMostMove)) {
18017             if(blackNPS >= 0) lastTickLength = 0;
18018             blackTimeRemaining -= lastTickLength;
18019            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18020 //         if(pvInfoList[forwardMostMove].time == -1)
18021                  pvInfoList[forwardMostMove].time =               // use GUI time
18022                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
18023         } else {
18024            if(whiteNPS >= 0) lastTickLength = 0;
18025            whiteTimeRemaining -= lastTickLength;
18026            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
18027 //         if(pvInfoList[forwardMostMove].time == -1)
18028                  pvInfoList[forwardMostMove].time =
18029                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
18030         }
18031         flagged = CheckFlags();
18032     }
18033     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
18034     CheckTimeControl();
18035
18036     if (flagged || !appData.clockMode) return;
18037
18038     switch (gameMode) {
18039       case MachinePlaysBlack:
18040       case MachinePlaysWhite:
18041       case BeginningOfGame:
18042         if (pausing) return;
18043         break;
18044
18045       case EditGame:
18046       case PlayFromGameFile:
18047       case IcsExamining:
18048         return;
18049
18050       default:
18051         break;
18052     }
18053
18054     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
18055         if(WhiteOnMove(forwardMostMove))
18056              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
18057         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
18058     }
18059
18060     tickStartTM = now;
18061     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18062       whiteTimeRemaining : blackTimeRemaining);
18063     StartClockTimer(intendedTickLength);
18064 }
18065
18066
18067 /* Stop both clocks */
18068 void
18069 StopClocks ()
18070 {
18071     long lastTickLength;
18072     TimeMark now;
18073
18074     if (!StopClockTimer()) return;
18075     if (!appData.clockMode) return;
18076
18077     GetTimeMark(&now);
18078
18079     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
18080     if (WhiteOnMove(forwardMostMove)) {
18081         if(whiteNPS >= 0) lastTickLength = 0;
18082         whiteTimeRemaining -= lastTickLength;
18083         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
18084     } else {
18085         if(blackNPS >= 0) lastTickLength = 0;
18086         blackTimeRemaining -= lastTickLength;
18087         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
18088     }
18089     CheckFlags();
18090 }
18091
18092 /* Start clock of player on move.  Time may have been reset, so
18093    if clock is already running, stop and restart it. */
18094 void
18095 StartClocks ()
18096 {
18097     (void) StopClockTimer(); /* in case it was running already */
18098     DisplayBothClocks();
18099     if (CheckFlags()) return;
18100
18101     if (!appData.clockMode) return;
18102     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
18103
18104     GetTimeMark(&tickStartTM);
18105     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
18106       whiteTimeRemaining : blackTimeRemaining);
18107
18108    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
18109     whiteNPS = blackNPS = -1;
18110     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
18111        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
18112         whiteNPS = first.nps;
18113     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
18114        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
18115         blackNPS = first.nps;
18116     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
18117         whiteNPS = second.nps;
18118     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
18119         blackNPS = second.nps;
18120     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
18121
18122     StartClockTimer(intendedTickLength);
18123 }
18124
18125 char *
18126 TimeString (long ms)
18127 {
18128     long second, minute, hour, day;
18129     char *sign = "";
18130     static char buf[40], moveTime[8];
18131
18132     if (ms > 0 && ms <= 9900) {
18133       /* convert milliseconds to tenths, rounding up */
18134       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
18135
18136       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
18137       return buf;
18138     }
18139
18140     /* convert milliseconds to seconds, rounding up */
18141     /* use floating point to avoid strangeness of integer division
18142        with negative dividends on many machines */
18143     second = (long) floor(((double) (ms + 999L)) / 1000.0);
18144
18145     if (second < 0) {
18146         sign = "-";
18147         second = -second;
18148     }
18149
18150     day = second / (60 * 60 * 24);
18151     second = second % (60 * 60 * 24);
18152     hour = second / (60 * 60);
18153     second = second % (60 * 60);
18154     minute = second / 60;
18155     second = second % 60;
18156
18157     if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
18158     else *moveTime = NULLCHAR;
18159
18160     if (day > 0)
18161       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
18162               sign, day, hour, minute, second, moveTime);
18163     else if (hour > 0)
18164       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
18165     else
18166       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
18167
18168     return buf;
18169 }
18170
18171
18172 /*
18173  * This is necessary because some C libraries aren't ANSI C compliant yet.
18174  */
18175 char *
18176 StrStr (char *string, char *match)
18177 {
18178     int i, length;
18179
18180     length = strlen(match);
18181
18182     for (i = strlen(string) - length; i >= 0; i--, string++)
18183       if (!strncmp(match, string, length))
18184         return string;
18185
18186     return NULL;
18187 }
18188
18189 char *
18190 StrCaseStr (char *string, char *match)
18191 {
18192     int i, j, length;
18193
18194     length = strlen(match);
18195
18196     for (i = strlen(string) - length; i >= 0; i--, string++) {
18197         for (j = 0; j < length; j++) {
18198             if (ToLower(match[j]) != ToLower(string[j]))
18199               break;
18200         }
18201         if (j == length) return string;
18202     }
18203
18204     return NULL;
18205 }
18206
18207 #ifndef _amigados
18208 int
18209 StrCaseCmp (char *s1, char *s2)
18210 {
18211     char c1, c2;
18212
18213     for (;;) {
18214         c1 = ToLower(*s1++);
18215         c2 = ToLower(*s2++);
18216         if (c1 > c2) return 1;
18217         if (c1 < c2) return -1;
18218         if (c1 == NULLCHAR) return 0;
18219     }
18220 }
18221
18222
18223 int
18224 ToLower (int c)
18225 {
18226     return isupper(c) ? tolower(c) : c;
18227 }
18228
18229
18230 int
18231 ToUpper (int c)
18232 {
18233     return islower(c) ? toupper(c) : c;
18234 }
18235 #endif /* !_amigados    */
18236
18237 char *
18238 StrSave (char *s)
18239 {
18240   char *ret;
18241
18242   if ((ret = (char *) malloc(strlen(s) + 1)))
18243     {
18244       safeStrCpy(ret, s, strlen(s)+1);
18245     }
18246   return ret;
18247 }
18248
18249 char *
18250 StrSavePtr (char *s, char **savePtr)
18251 {
18252     if (*savePtr) {
18253         free(*savePtr);
18254     }
18255     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18256       safeStrCpy(*savePtr, s, strlen(s)+1);
18257     }
18258     return(*savePtr);
18259 }
18260
18261 char *
18262 PGNDate ()
18263 {
18264     time_t clock;
18265     struct tm *tm;
18266     char buf[MSG_SIZ];
18267
18268     clock = time((time_t *)NULL);
18269     tm = localtime(&clock);
18270     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18271             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18272     return StrSave(buf);
18273 }
18274
18275
18276 char *
18277 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18278 {
18279     int i, j, fromX, fromY, toX, toY;
18280     int whiteToPlay, haveRights = nrCastlingRights;
18281     char buf[MSG_SIZ];
18282     char *p, *q;
18283     int emptycount;
18284     ChessSquare piece;
18285
18286     whiteToPlay = (gameMode == EditPosition) ?
18287       !blackPlaysFirst : (move % 2 == 0);
18288     p = buf;
18289
18290     /* Piece placement data */
18291     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18292         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18293         emptycount = 0;
18294         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18295             if (boards[move][i][j] == EmptySquare) {
18296                 emptycount++;
18297             } else { ChessSquare piece = boards[move][i][j];
18298                 if (emptycount > 0) {
18299                     if(emptycount<10) /* [HGM] can be >= 10 */
18300                         *p++ = '0' + emptycount;
18301                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18302                     emptycount = 0;
18303                 }
18304                 if(PieceToChar(piece) == '+') {
18305                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18306                     *p++ = '+';
18307                     piece = (ChessSquare)(CHUDEMOTED(piece));
18308                 }
18309                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18310                 if(*p = PieceSuffix(piece)) p++;
18311                 if(p[-1] == '~') {
18312                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18313                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18314                     *p++ = '~';
18315                 }
18316             }
18317         }
18318         if (emptycount > 0) {
18319             if(emptycount<10) /* [HGM] can be >= 10 */
18320                 *p++ = '0' + emptycount;
18321             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18322             emptycount = 0;
18323         }
18324         *p++ = '/';
18325     }
18326     *(p - 1) = ' ';
18327
18328     /* [HGM] print Crazyhouse or Shogi holdings */
18329     if( gameInfo.holdingsWidth ) {
18330         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18331         q = p;
18332         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18333             piece = boards[move][i][BOARD_WIDTH-1];
18334             if( piece != EmptySquare )
18335               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18336                   *p++ = PieceToChar(piece);
18337         }
18338         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18339             piece = boards[move][BOARD_HEIGHT-i-1][0];
18340             if( piece != EmptySquare )
18341               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18342                   *p++ = PieceToChar(piece);
18343         }
18344
18345         if( q == p ) *p++ = '-';
18346         *p++ = ']';
18347         *p++ = ' ';
18348     }
18349
18350     /* Active color */
18351     *p++ = whiteToPlay ? 'w' : 'b';
18352     *p++ = ' ';
18353
18354   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18355     haveRights = 0; q = p;
18356     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18357       piece = boards[move][0][i];
18358       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18359         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18360       }
18361     }
18362     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18363       piece = boards[move][BOARD_HEIGHT-1][i];
18364       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18365         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18366       }
18367     }
18368     if(p == q) *p++ = '-';
18369     *p++ = ' ';
18370   }
18371
18372   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18373     while(*p++ = *q++)
18374                       ;
18375     if(q != overrideCastling+1) p[-1] = ' '; else --p;
18376   } else {
18377   if(haveRights) {
18378      int handW=0, handB=0;
18379      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18380         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18381         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18382      }
18383      q = p;
18384      if(appData.fischerCastling) {
18385         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18386            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18387                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18388         } else {
18389        /* [HGM] write directly from rights */
18390            if(boards[move][CASTLING][2] != NoRights &&
18391               boards[move][CASTLING][0] != NoRights   )
18392                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18393            if(boards[move][CASTLING][2] != NoRights &&
18394               boards[move][CASTLING][1] != NoRights   )
18395                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18396         }
18397         if(handB) {
18398            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18399                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18400         } else {
18401            if(boards[move][CASTLING][5] != NoRights &&
18402               boards[move][CASTLING][3] != NoRights   )
18403                 *p++ = boards[move][CASTLING][3] + AAA;
18404            if(boards[move][CASTLING][5] != NoRights &&
18405               boards[move][CASTLING][4] != NoRights   )
18406                 *p++ = boards[move][CASTLING][4] + AAA;
18407         }
18408      } else {
18409
18410         /* [HGM] write true castling rights */
18411         if( nrCastlingRights == 6 ) {
18412             int q, k=0;
18413             if(boards[move][CASTLING][0] != NoRights &&
18414                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18415             q = (boards[move][CASTLING][1] != NoRights &&
18416                  boards[move][CASTLING][2] != NoRights  );
18417             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18418                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18419                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18420                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18421             }
18422             if(q) *p++ = 'Q';
18423             k = 0;
18424             if(boards[move][CASTLING][3] != NoRights &&
18425                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18426             q = (boards[move][CASTLING][4] != NoRights &&
18427                  boards[move][CASTLING][5] != NoRights  );
18428             if(handB) {
18429                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18430                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18431                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18432             }
18433             if(q) *p++ = 'q';
18434         }
18435      }
18436      if (q == p) *p++ = '-'; /* No castling rights */
18437      *p++ = ' ';
18438   }
18439
18440   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18441      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18442      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18443     /* En passant target square */
18444     if (move > backwardMostMove) {
18445         fromX = moveList[move - 1][0] - AAA;
18446         fromY = moveList[move - 1][1] - ONE;
18447         toX = moveList[move - 1][2] - AAA;
18448         toY = moveList[move - 1][3] - ONE;
18449         if ((whiteToPlay ? toY < fromY - 1 : toY > fromY + 1) &&
18450             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) ) {
18451             /* 2-square pawn move just happened */
18452             *p++ = (3*toX + 5*fromX + 4)/8 + AAA;
18453             *p++ = (3*toY + 5*fromY + 4)/8 + ONE;
18454             if(gameInfo.variant == VariantBerolina) {
18455                 *p++ = toX + AAA;
18456                 *p++ = toY + ONE;
18457             }
18458         } else {
18459             *p++ = '-';
18460         }
18461     } else if(move == backwardMostMove) {
18462         // [HGM] perhaps we should always do it like this, and forget the above?
18463         if((signed char)boards[move][EP_STATUS] >= 0) {
18464             *p++ = boards[move][EP_STATUS] + AAA;
18465             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18466         } else {
18467             *p++ = '-';
18468         }
18469     } else {
18470         *p++ = '-';
18471     }
18472     *p++ = ' ';
18473   }
18474   }
18475
18476     i = boards[move][CHECK_COUNT];
18477     if(i) {
18478         sprintf(p, "%d+%d ", i&255, i>>8);
18479         while(*p) p++;
18480     }
18481
18482     if(moveCounts)
18483     {   int i = 0, j=move;
18484
18485         /* [HGM] find reversible plies */
18486         if (appData.debugMode) { int k;
18487             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18488             for(k=backwardMostMove; k<=forwardMostMove; k++)
18489                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18490
18491         }
18492
18493         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18494         if( j == backwardMostMove ) i += initialRulePlies;
18495         sprintf(p, "%d ", i);
18496         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18497
18498         /* Fullmove number */
18499         sprintf(p, "%d", (move / 2) + 1);
18500     } else *--p = NULLCHAR;
18501
18502     return StrSave(buf);
18503 }
18504
18505 Boolean
18506 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18507 {
18508     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18509     char *p, c;
18510     int emptycount, virgin[BOARD_FILES];
18511     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18512
18513     p = fen;
18514
18515     for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
18516
18517     /* Piece placement data */
18518     for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
18519         j = 0;
18520         for (;;) {
18521             if (*p == '/' || *p == ' ' || *p == '[' ) {
18522                 if(j > w) w = j;
18523                 emptycount = gameInfo.boardWidth - j;
18524                 while (emptycount--)
18525                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18526                 if (*p == '/') p++;
18527                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18528                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18529                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18530                     }
18531                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18532                 }
18533                 break;
18534 #if(BOARD_FILES >= 10)*0
18535             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18536                 p++; emptycount=10;
18537                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18538                 while (emptycount--)
18539                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18540 #endif
18541             } else if (*p == '*') {
18542                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18543             } else if (isdigit(*p)) {
18544                 emptycount = *p++ - '0';
18545                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18546                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18547                 while (emptycount--)
18548                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18549             } else if (*p == '<') {
18550                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18551                 else if (i != 0 || !shuffle) return FALSE;
18552                 p++;
18553             } else if (shuffle && *p == '>') {
18554                 p++; // for now ignore closing shuffle range, and assume rank-end
18555             } else if (*p == '?') {
18556                 if (j >= gameInfo.boardWidth) return FALSE;
18557                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18558                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18559             } else if (*p == '+' || isalpha(*p)) {
18560                 char *q, *s = SUFFIXES;
18561                 if (j >= gameInfo.boardWidth) return FALSE;
18562                 if(*p=='+') {
18563                     char c = *++p;
18564                     if(q = strchr(s, p[1])) p++;
18565                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18566                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18567                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18568                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18569                 } else {
18570                     char c = *p++;
18571                     if(q = strchr(s, *p)) p++;
18572                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18573                 }
18574
18575                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18576                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18577                     piece = (ChessSquare) (PROMOTED(piece));
18578                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18579                     p++;
18580                 }
18581                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18582                 if(piece == king) wKingRank = i;
18583                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18584             } else {
18585                 return FALSE;
18586             }
18587         }
18588     }
18589     while (*p == '/' || *p == ' ') p++;
18590
18591     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18592
18593     /* [HGM] by default clear Crazyhouse holdings, if present */
18594     if(gameInfo.holdingsWidth) {
18595        for(i=0; i<BOARD_HEIGHT; i++) {
18596            board[i][0]             = EmptySquare; /* black holdings */
18597            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18598            board[i][1]             = (ChessSquare) 0; /* black counts */
18599            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18600        }
18601     }
18602
18603     /* [HGM] look for Crazyhouse holdings here */
18604     while(*p==' ') p++;
18605     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18606         int swap=0, wcnt=0, bcnt=0;
18607         if(*p == '[') p++;
18608         if(*p == '<') swap++, p++;
18609         if(*p == '-' ) p++; /* empty holdings */ else {
18610             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18611             /* if we would allow FEN reading to set board size, we would   */
18612             /* have to add holdings and shift the board read so far here   */
18613             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18614                 p++;
18615                 if((int) piece >= (int) BlackPawn ) {
18616                     i = (int)piece - (int)BlackPawn;
18617                     i = PieceToNumber((ChessSquare)i);
18618                     if( i >= gameInfo.holdingsSize ) return FALSE;
18619                     board[handSize-1-i][0] = piece; /* black holdings */
18620                     board[handSize-1-i][1]++;       /* black counts   */
18621                     bcnt++;
18622                 } else {
18623                     i = (int)piece - (int)WhitePawn;
18624                     i = PieceToNumber((ChessSquare)i);
18625                     if( i >= gameInfo.holdingsSize ) return FALSE;
18626                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18627                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18628                     wcnt++;
18629                 }
18630             }
18631             if(subst) { // substitute back-rank question marks by holdings pieces
18632                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18633                     int k, m, n = bcnt + 1;
18634                     if(board[0][j] == ClearBoard) {
18635                         if(!wcnt) return FALSE;
18636                         n = rand() % wcnt;
18637                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18638                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18639                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18640                             break;
18641                         }
18642                     }
18643                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18644                         if(!bcnt) return FALSE;
18645                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18646                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18647                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18648                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18649                             break;
18650                         }
18651                     }
18652                 }
18653                 subst = 0;
18654             }
18655         }
18656         if(*p == ']') p++;
18657     }
18658
18659     if(subst) return FALSE; // substitution requested, but no holdings
18660
18661     while(*p == ' ') p++;
18662
18663     /* Active color */
18664     c = *p++;
18665     if(appData.colorNickNames) {
18666       if( c == appData.colorNickNames[0] ) c = 'w'; else
18667       if( c == appData.colorNickNames[1] ) c = 'b';
18668     }
18669     switch (c) {
18670       case 'w':
18671         *blackPlaysFirst = FALSE;
18672         break;
18673       case 'b':
18674         *blackPlaysFirst = TRUE;
18675         break;
18676       default:
18677         return FALSE;
18678     }
18679
18680     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18681     /* return the extra info in global variiables             */
18682
18683     while(*p==' ') p++;
18684
18685     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18686         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18687         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18688     }
18689
18690     /* set defaults in case FEN is incomplete */
18691     board[EP_STATUS] = EP_UNKNOWN;
18692     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18693     for(i=0; i<nrCastlingRights; i++ ) {
18694         board[CASTLING][i] =
18695             appData.fischerCastling ? NoRights : initialRights[i];
18696     }   /* assume possible unless obviously impossible */
18697     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18698     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18699     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18700                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18701     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18702     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18703     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18704                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18705     FENrulePlies = 0;
18706
18707     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18708       char *q = p;
18709       int w=0, b=0;
18710       while(isalpha(*p)) {
18711         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18712         if(islower(*p)) b |= 1 << (*p++ - 'a');
18713       }
18714       if(*p == '-') p++;
18715       if(p != q) {
18716         board[TOUCHED_W] = ~w;
18717         board[TOUCHED_B] = ~b;
18718         while(*p == ' ') p++;
18719       }
18720     } else
18721
18722     if(nrCastlingRights) {
18723       int fischer = 0;
18724       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18725       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18726           /* castling indicator present, so default becomes no castlings */
18727           for(i=0; i<nrCastlingRights; i++ ) {
18728                  board[CASTLING][i] = NoRights;
18729           }
18730       }
18731       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18732              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18733              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18734              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18735         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18736
18737         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18738             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18739             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18740         }
18741         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18742             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18743         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18744                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18745         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18746                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18747         switch(c) {
18748           case'K':
18749               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18750               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18751               board[CASTLING][2] = whiteKingFile;
18752               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18753               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18754               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18755               break;
18756           case'Q':
18757               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18758               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18759               board[CASTLING][2] = whiteKingFile;
18760               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18761               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18762               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18763               break;
18764           case'k':
18765               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18766               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18767               board[CASTLING][5] = blackKingFile;
18768               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18769               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18770               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18771               break;
18772           case'q':
18773               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18774               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18775               board[CASTLING][5] = blackKingFile;
18776               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18777               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18778               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18779           case '-':
18780               break;
18781           default: /* FRC castlings */
18782               if(c >= 'a') { /* black rights */
18783                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18784                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18785                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18786                   if(i == BOARD_RGHT) break;
18787                   board[CASTLING][5] = i;
18788                   c -= AAA;
18789                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18790                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18791                   if(c > i)
18792                       board[CASTLING][3] = c;
18793                   else
18794                       board[CASTLING][4] = c;
18795               } else { /* white rights */
18796                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18797                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18798                     if(board[0][i] == WhiteKing) break;
18799                   if(i == BOARD_RGHT) break;
18800                   board[CASTLING][2] = i;
18801                   c -= AAA - 'a' + 'A';
18802                   if(board[0][c] >= WhiteKing) break;
18803                   if(c > i)
18804                       board[CASTLING][0] = c;
18805                   else
18806                       board[CASTLING][1] = c;
18807               }
18808         }
18809       }
18810       for(i=0; i<nrCastlingRights; i++)
18811         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18812       if(gameInfo.variant == VariantSChess)
18813         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18814       if(fischer && shuffle) appData.fischerCastling = TRUE;
18815     if (appData.debugMode) {
18816         fprintf(debugFP, "FEN castling rights:");
18817         for(i=0; i<nrCastlingRights; i++)
18818         fprintf(debugFP, " %d", board[CASTLING][i]);
18819         fprintf(debugFP, "\n");
18820     }
18821
18822       while(*p==' ') p++;
18823     }
18824
18825     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18826
18827     /* read e.p. field in games that know e.p. capture */
18828     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18829        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18830        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18831       if(*p=='-') {
18832         p++; board[EP_STATUS] = EP_NONE;
18833       } else {
18834          int d, r, c = *p - AAA;
18835
18836          if(c >= BOARD_LEFT && c < BOARD_RGHT) {
18837              p++;
18838              board[EP_STATUS] = board[EP_FILE] = c; r = 0;
18839              if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18840              d = (r < BOARD_HEIGHT << 1 ? 1 : -1); // assume double-push (P next to e.p. square nearer center)
18841              if(board[r+d][c] == EmptySquare) d *= 2; // but if no Pawn there, triple push
18842              board[LAST_TO] = 256*(r + d) + c;
18843              c = *p++ - AAA;
18844              if(c >= BOARD_LEFT && c < BOARD_RGHT) { // mover explicitly mentioned
18845                  if(*p >= '0' && *p <='9') r = board[EP_RANK] = *p++ - ONE;
18846                  board[LAST_TO] = 256*r + c;
18847                  if(!(board[EP_RANK]-r & 1)) board[EP_RANK] |= 128;
18848              }
18849          }
18850       }
18851     }
18852
18853     while(*p == ' ') p++;
18854
18855     board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
18856     if(sscanf(p, "%d+%d", &i, &j) == 2) {
18857         board[CHECK_COUNT] = i + 256*j;
18858         while(*p && *p != ' ') p++;
18859     }
18860
18861     c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
18862     if(c > 0) {
18863         FENrulePlies = i; /* 50-move ply counter */
18864         /* (The move number is still ignored)    */
18865         if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
18866     }
18867
18868     return TRUE;
18869 }
18870
18871 void
18872 EditPositionPasteFEN (char *fen)
18873 {
18874   if (fen != NULL) {
18875     Board initial_position;
18876
18877     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18878       DisplayError(_("Bad FEN position in clipboard"), 0);
18879       return ;
18880     } else {
18881       int savedBlackPlaysFirst = blackPlaysFirst;
18882       EditPositionEvent();
18883       blackPlaysFirst = savedBlackPlaysFirst;
18884       CopyBoard(boards[0], initial_position);
18885       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18886       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18887       DisplayBothClocks();
18888       DrawPosition(FALSE, boards[currentMove]);
18889     }
18890   }
18891 }
18892
18893 static char cseq[12] = "\\   ";
18894
18895 Boolean
18896 set_cont_sequence (char *new_seq)
18897 {
18898     int len;
18899     Boolean ret;
18900
18901     // handle bad attempts to set the sequence
18902         if (!new_seq)
18903                 return 0; // acceptable error - no debug
18904
18905     len = strlen(new_seq);
18906     ret = (len > 0) && (len < sizeof(cseq));
18907     if (ret)
18908       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18909     else if (appData.debugMode)
18910       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18911     return ret;
18912 }
18913
18914 /*
18915     reformat a source message so words don't cross the width boundary.  internal
18916     newlines are not removed.  returns the wrapped size (no null character unless
18917     included in source message).  If dest is NULL, only calculate the size required
18918     for the dest buffer.  lp argument indicats line position upon entry, and it's
18919     passed back upon exit.
18920 */
18921 int
18922 wrap (char *dest, char *src, int count, int width, int *lp)
18923 {
18924     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18925
18926     cseq_len = strlen(cseq);
18927     old_line = line = *lp;
18928     ansi = len = clen = 0;
18929
18930     for (i=0; i < count; i++)
18931     {
18932         if (src[i] == '\033')
18933             ansi = 1;
18934
18935         // if we hit the width, back up
18936         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18937         {
18938             // store i & len in case the word is too long
18939             old_i = i, old_len = len;
18940
18941             // find the end of the last word
18942             while (i && src[i] != ' ' && src[i] != '\n')
18943             {
18944                 i--;
18945                 len--;
18946             }
18947
18948             // word too long?  restore i & len before splitting it
18949             if ((old_i-i+clen) >= width)
18950             {
18951                 i = old_i;
18952                 len = old_len;
18953             }
18954
18955             // extra space?
18956             if (i && src[i-1] == ' ')
18957                 len--;
18958
18959             if (src[i] != ' ' && src[i] != '\n')
18960             {
18961                 i--;
18962                 if (len)
18963                     len--;
18964             }
18965
18966             // now append the newline and continuation sequence
18967             if (dest)
18968                 dest[len] = '\n';
18969             len++;
18970             if (dest)
18971                 strncpy(dest+len, cseq, cseq_len);
18972             len += cseq_len;
18973             line = cseq_len;
18974             clen = cseq_len;
18975             continue;
18976         }
18977
18978         if (dest)
18979             dest[len] = src[i];
18980         len++;
18981         if (!ansi)
18982             line++;
18983         if (src[i] == '\n')
18984             line = 0;
18985         if (src[i] == 'm')
18986             ansi = 0;
18987     }
18988     if (dest && appData.debugMode)
18989     {
18990         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18991             count, width, line, len, *lp);
18992         show_bytes(debugFP, src, count);
18993         fprintf(debugFP, "\ndest: ");
18994         show_bytes(debugFP, dest, len);
18995         fprintf(debugFP, "\n");
18996     }
18997     *lp = dest ? line : old_line;
18998
18999     return len;
19000 }
19001
19002 // [HGM] vari: routines for shelving variations
19003 Boolean modeRestore = FALSE;
19004
19005 void
19006 PushInner (int firstMove, int lastMove)
19007 {
19008         int i, j, nrMoves = lastMove - firstMove;
19009
19010         // push current tail of game on stack
19011         savedResult[storedGames] = gameInfo.result;
19012         savedDetails[storedGames] = gameInfo.resultDetails;
19013         gameInfo.resultDetails = NULL;
19014         savedFirst[storedGames] = firstMove;
19015         savedLast [storedGames] = lastMove;
19016         savedFramePtr[storedGames] = framePtr;
19017         framePtr -= nrMoves; // reserve space for the boards
19018         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
19019             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
19020             for(j=0; j<MOVE_LEN; j++)
19021                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
19022             for(j=0; j<2*MOVE_LEN; j++)
19023                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
19024             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
19025             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
19026             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
19027             pvInfoList[firstMove+i-1].depth = 0;
19028             commentList[framePtr+i] = commentList[firstMove+i];
19029             commentList[firstMove+i] = NULL;
19030         }
19031
19032         storedGames++;
19033         forwardMostMove = firstMove; // truncate game so we can start variation
19034 }
19035
19036 void
19037 PushTail (int firstMove, int lastMove)
19038 {
19039         if(appData.icsActive) { // only in local mode
19040                 forwardMostMove = currentMove; // mimic old ICS behavior
19041                 return;
19042         }
19043         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
19044
19045         PushInner(firstMove, lastMove);
19046         if(storedGames == 1) GreyRevert(FALSE);
19047         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
19048 }
19049
19050 void
19051 PopInner (Boolean annotate)
19052 {
19053         int i, j, nrMoves;
19054         char buf[8000], moveBuf[20];
19055
19056         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
19057         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
19058         nrMoves = savedLast[storedGames] - currentMove;
19059         if(annotate) {
19060                 int cnt = 10;
19061                 if(!WhiteOnMove(currentMove))
19062                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
19063                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
19064                 for(i=currentMove; i<forwardMostMove; i++) {
19065                         if(WhiteOnMove(i))
19066                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
19067                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
19068                         strcat(buf, moveBuf);
19069                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
19070                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
19071                 }
19072                 strcat(buf, ")");
19073         }
19074         for(i=1; i<=nrMoves; i++) { // copy last variation back
19075             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
19076             for(j=0; j<MOVE_LEN; j++)
19077                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
19078             for(j=0; j<2*MOVE_LEN; j++)
19079                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
19080             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
19081             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
19082             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
19083             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
19084             commentList[currentMove+i] = commentList[framePtr+i];
19085             commentList[framePtr+i] = NULL;
19086         }
19087         if(annotate) AppendComment(currentMove+1, buf, FALSE);
19088         framePtr = savedFramePtr[storedGames];
19089         gameInfo.result = savedResult[storedGames];
19090         if(gameInfo.resultDetails != NULL) {
19091             free(gameInfo.resultDetails);
19092       }
19093         gameInfo.resultDetails = savedDetails[storedGames];
19094         forwardMostMove = currentMove + nrMoves;
19095 }
19096
19097 Boolean
19098 PopTail (Boolean annotate)
19099 {
19100         if(appData.icsActive) return FALSE; // only in local mode
19101         if(!storedGames) return FALSE; // sanity
19102         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
19103
19104         PopInner(annotate);
19105         if(currentMove < forwardMostMove) ForwardEvent(); else
19106         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
19107
19108         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
19109         return TRUE;
19110 }
19111
19112 void
19113 CleanupTail ()
19114 {       // remove all shelved variations
19115         int i;
19116         for(i=0; i<storedGames; i++) {
19117             if(savedDetails[i])
19118                 free(savedDetails[i]);
19119             savedDetails[i] = NULL;
19120         }
19121         for(i=framePtr; i<MAX_MOVES; i++) {
19122                 if(commentList[i]) free(commentList[i]);
19123                 commentList[i] = NULL;
19124         }
19125         framePtr = MAX_MOVES-1;
19126         storedGames = 0;
19127 }
19128
19129 void
19130 LoadVariation (int index, char *text)
19131 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
19132         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
19133         int level = 0, move;
19134
19135         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
19136         // first find outermost bracketing variation
19137         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
19138             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
19139                 if(*p == '{') wait = '}'; else
19140                 if(*p == '[') wait = ']'; else
19141                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
19142                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
19143             }
19144             if(*p == wait) wait = NULLCHAR; // closing ]} found
19145             p++;
19146         }
19147         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
19148         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
19149         end[1] = NULLCHAR; // clip off comment beyond variation
19150         ToNrEvent(currentMove-1);
19151         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
19152         // kludge: use ParsePV() to append variation to game
19153         move = currentMove;
19154         ParsePV(start, TRUE, TRUE);
19155         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
19156         ClearPremoveHighlights();
19157         CommentPopDown();
19158         ToNrEvent(currentMove+1);
19159 }
19160
19161 int transparency[2];
19162
19163 void
19164 LoadTheme ()
19165 {
19166 #define BUF_SIZ (2*MSG_SIZ)
19167     char *p, *q, buf[BUF_SIZ];
19168     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
19169         snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
19170         ParseArgsFromString(buf);
19171         ActivateTheme(TRUE); // also redo colors
19172         return;
19173     }
19174     p = nickName;
19175     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
19176     {
19177         int len;
19178         q = appData.themeNames;
19179         snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
19180       if(appData.useBitmaps) {
19181         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
19182                 Shorten(appData.liteBackTextureFile));
19183         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
19184                 Shorten(appData.darkBackTextureFile),
19185                 appData.liteBackTextureMode,
19186                 appData.darkBackTextureMode );
19187       } else {
19188         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
19189       }
19190       if(!appData.useBitmaps || transparency[0]) {
19191         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
19192       }
19193       if(!appData.useBitmaps || transparency[1]) {
19194         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
19195       }
19196       if(appData.useBorder) {
19197         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
19198                 appData.border);
19199       } else {
19200         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
19201       }
19202       if(appData.useFont) {
19203         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19204                 appData.renderPiecesWithFont,
19205                 appData.fontToPieceTable,
19206                 Col2Text(9),    // appData.fontBackColorWhite
19207                 Col2Text(10) ); // appData.fontForeColorBlack
19208       } else {
19209         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
19210         if(appData.pieceDirectory[0]) {
19211           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
19212           if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
19213             snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
19214         }
19215         if(!appData.pieceDirectory[0] || !appData.trueColors)
19216           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
19217                 Col2Text(0),   // whitePieceColor
19218                 Col2Text(1) ); // blackPieceColor
19219       }
19220       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19221                 Col2Text(4),   // highlightSquareColor
19222                 Col2Text(5) ); // premoveHighlightColor
19223         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19224         if(insert != q) insert[-1] = NULLCHAR;
19225         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19226         if(q)   free(q);
19227     }
19228     ActivateTheme(FALSE);
19229 }