Change EPD reporting
[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
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299 char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
300 int solvingTime, totalTime;
301
302 /* States for ics_getting_history */
303 #define H_FALSE 0
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
309
310 /* whosays values for GameEnds */
311 #define GE_ICS 0
312 #define GE_ENGINE 1
313 #define GE_PLAYER 2
314 #define GE_FILE 3
315 #define GE_XBOARD 4
316 #define GE_ENGINE1 5
317 #define GE_ENGINE2 6
318
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
321
322 /* Different types of move when calling RegisterMove */
323 #define CMAIL_MOVE   0
324 #define CMAIL_RESIGN 1
325 #define CMAIL_DRAW   2
326 #define CMAIL_ACCEPT 3
327
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
332
333 /* Telnet protocol constants */
334 #define TN_WILL 0373
335 #define TN_WONT 0374
336 #define TN_DO   0375
337 #define TN_DONT 0376
338 #define TN_IAC  0377
339 #define TN_ECHO 0001
340 #define TN_SGA  0003
341 #define TN_PORT 23
342
343 char*
344 safeStrCpy (char *dst, const char *src, size_t count)
345 { // [HGM] made safe
346   int i;
347   assert( dst != NULL );
348   assert( src != NULL );
349   assert( count > 0 );
350
351   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352   if(  i == count && dst[count-1] != NULLCHAR)
353     {
354       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355       if(appData.debugMode)
356         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
357     }
358
359   return dst;
360 }
361
362 /* Some compiler can't cast u64 to double
363  * This function do the job for us:
364
365  * We use the highest bit for cast, this only
366  * works if the highest bit is not
367  * in use (This should not happen)
368  *
369  * We used this for all compiler
370  */
371 double
372 u64ToDouble (u64 value)
373 {
374   double r;
375   u64 tmp = value & u64Const(0x7fffffffffffffff);
376   r = (double)(s64)tmp;
377   if (value & u64Const(0x8000000000000000))
378        r +=  9.2233720368547758080e18; /* 2^63 */
379  return r;
380 }
381
382 /* Fake up flags for now, as we aren't keeping track of castling
383    availability yet. [HGM] Change of logic: the flag now only
384    indicates the type of castlings allowed by the rule of the game.
385    The actual rights themselves are maintained in the array
386    castlingRights, as part of the game history, and are not probed
387    by this function.
388  */
389 int
390 PosFlags (int index)
391 {
392   int flags = F_ALL_CASTLE_OK;
393   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394   switch (gameInfo.variant) {
395   case VariantSuicide:
396     flags &= ~F_ALL_CASTLE_OK;
397   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398     flags |= F_IGNORE_CHECK;
399   case VariantLosers:
400     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
401     break;
402   case VariantAtomic:
403     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
404     break;
405   case VariantKriegspiel:
406     flags |= F_KRIEGSPIEL_CAPTURE;
407     break;
408   case VariantCapaRandom:
409   case VariantFischeRandom:
410     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411   case VariantNoCastle:
412   case VariantShatranj:
413   case VariantCourier:
414   case VariantMakruk:
415   case VariantASEAN:
416   case VariantGrand:
417     flags &= ~F_ALL_CASTLE_OK;
418     break;
419   case VariantChu:
420   case VariantChuChess:
421   case VariantLion:
422     flags |= F_NULL_MOVE;
423     break;
424   default:
425     break;
426   }
427   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
428   return flags;
429 }
430
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
433
434 /*
435     [AS] Note: sometimes, the sscanf() function is used to parse the input
436     into a fixed-size buffer. Because of this, we must be prepared to
437     receive strings as long as the size of the input buffer, which is currently
438     set to 4K for Windows and 8K for the rest.
439     So, we must either allocate sufficiently large buffers here, or
440     reduce the size of the input buffer in the input reading part.
441 */
442
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446 char promoRestrict[MSG_SIZ];
447
448 ChessProgramState first, second, pairing;
449
450 /* premove variables */
451 int premoveToX = 0;
452 int premoveToY = 0;
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
456 int gotPremove = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
459
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
462
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
490
491 int have_sent_ICS_logon = 0;
492 int movesPerSession;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
504
505 /* animateTraining preserves the state of appData.animate
506  * when Training mode is activated. This allows the
507  * response to be animated when appData.animate == TRUE and
508  * appData.animateDragging == TRUE.
509  */
510 Boolean animateTraining;
511
512 GameInfo gameInfo;
513
514 AppData appData;
515
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
519 unsigned char initialRights[BOARD_FILES];
520 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int   initialRulePlies, FENrulePlies;
522 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
523 int loadFlag = 0;
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
526
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
530 int storedGames = 0;
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
536
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
542
543 ChessSquare  FIDEArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547         BlackKing, BlackBishop, BlackKnight, BlackRook }
548 };
549
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554         BlackKing, BlackKing, BlackKnight, BlackRook }
555 };
556
557 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
558     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560     { BlackRook, BlackMan, BlackBishop, BlackQueen,
561         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
562 };
563
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
568         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
569 };
570
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
576 };
577
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackMan, BlackFerz,
589         BlackKing, BlackMan, BlackKnight, BlackRook }
590 };
591
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackMan, BlackFerz,
596         BlackKing, BlackMan, BlackKnight, BlackRook }
597 };
598
599 ChessSquare  lionArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackLion, BlackBishop, BlackQueen,
603         BlackKing, BlackBishop, BlackKnight, BlackRook }
604 };
605
606
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
613 };
614
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
620 };
621
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
627 };
628
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
634 };
635
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
641 };
642
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
648 };
649
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
655 };
656
657 #ifdef GOTHIC
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
663 };
664 #else // !GOTHIC
665 #define GothicArray CapablancaArray
666 #endif // !GOTHIC
667
668 #ifdef FALCON
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
674 };
675 #else // !FALCON
676 #define FalconArray CapablancaArray
677 #endif // !FALCON
678
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
685
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
692 };
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
695       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
696     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
697       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
698     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
699       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
700     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
701       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
702     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
704     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
706 };
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
711
712
713 Board initialPosition;
714
715
716 /* Convert str to a rating. Checks for special cases of "----",
717
718    "++++", etc. Also strips ()'s */
719 int
720 string_to_rating (char *str)
721 {
722   while(*str && !isdigit(*str)) ++str;
723   if (!*str)
724     return 0;   /* One of the special "no rating" cases */
725   else
726     return atoi(str);
727 }
728
729 void
730 ClearProgramStats ()
731 {
732     /* Init programStats */
733     programStats.movelist[0] = 0;
734     programStats.depth = 0;
735     programStats.nr_moves = 0;
736     programStats.moves_left = 0;
737     programStats.nodes = 0;
738     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
739     programStats.score = 0;
740     programStats.got_only_move = 0;
741     programStats.got_fail = 0;
742     programStats.line_is_book = 0;
743 }
744
745 void
746 CommonEngineInit ()
747 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748     if (appData.firstPlaysBlack) {
749         first.twoMachinesColor = "black\n";
750         second.twoMachinesColor = "white\n";
751     } else {
752         first.twoMachinesColor = "white\n";
753         second.twoMachinesColor = "black\n";
754     }
755
756     first.other = &second;
757     second.other = &first;
758
759     { float norm = 1;
760         if(appData.timeOddsMode) {
761             norm = appData.timeOdds[0];
762             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
763         }
764         first.timeOdds  = appData.timeOdds[0]/norm;
765         second.timeOdds = appData.timeOdds[1]/norm;
766     }
767
768     if(programVersion) free(programVersion);
769     if (appData.noChessProgram) {
770         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771         sprintf(programVersion, "%s", PACKAGE_STRING);
772     } else {
773       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
776     }
777 }
778
779 void
780 UnloadEngine (ChessProgramState *cps)
781 {
782         /* Kill off first chess program */
783         if (cps->isr != NULL)
784           RemoveInputSource(cps->isr);
785         cps->isr = NULL;
786
787         if (cps->pr != NoProc) {
788             ExitAnalyzeMode();
789             DoSleep( appData.delayBeforeQuit );
790             SendToProgram("quit\n", cps);
791             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
792         }
793         cps->pr = NoProc;
794         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
795 }
796
797 void
798 ClearOptions (ChessProgramState *cps)
799 {
800     int i;
801     cps->nrOptions = cps->comboCnt = 0;
802     for(i=0; i<MAX_OPTIONS; i++) {
803         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804         cps->option[i].textValue = 0;
805     }
806 }
807
808 char *engineNames[] = {
809   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("first"),
812   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
814 N_("second")
815 };
816
817 void
818 InitEngine (ChessProgramState *cps, int n)
819 {   // [HGM] all engine initialiation put in a function that does one engine
820
821     ClearOptions(cps);
822
823     cps->which = engineNames[n];
824     cps->maybeThinking = FALSE;
825     cps->pr = NoProc;
826     cps->isr = NULL;
827     cps->sendTime = 2;
828     cps->sendDrawOffers = 1;
829
830     cps->program = appData.chessProgram[n];
831     cps->host = appData.host[n];
832     cps->dir = appData.directory[n];
833     cps->initString = appData.engInitString[n];
834     cps->computerString = appData.computerString[n];
835     cps->useSigint  = TRUE;
836     cps->useSigterm = TRUE;
837     cps->reuse = appData.reuse[n];
838     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
839     cps->useSetboard = FALSE;
840     cps->useSAN = FALSE;
841     cps->usePing = FALSE;
842     cps->lastPing = 0;
843     cps->lastPong = 0;
844     cps->usePlayother = FALSE;
845     cps->useColors = TRUE;
846     cps->useUsermove = FALSE;
847     cps->sendICS = FALSE;
848     cps->sendName = appData.icsActive;
849     cps->sdKludge = FALSE;
850     cps->stKludge = FALSE;
851     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852     TidyProgramName(cps->program, cps->host, cps->tidy);
853     cps->matchWins = 0;
854     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855     cps->analysisSupport = 2; /* detect */
856     cps->analyzing = FALSE;
857     cps->initDone = FALSE;
858     cps->reload = FALSE;
859     cps->pseudo = appData.pseudo[n];
860
861     /* New features added by Tord: */
862     cps->useFEN960 = FALSE;
863     cps->useOOCastle = TRUE;
864     /* End of new features added by Tord. */
865     cps->fenOverride  = appData.fenOverride[n];
866
867     /* [HGM] time odds: set factor for each machine */
868     cps->timeOdds  = appData.timeOdds[n];
869
870     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871     cps->accumulateTC = appData.accumulateTC[n];
872     cps->maxNrOfSessions = 1;
873
874     /* [HGM] debug */
875     cps->debug = FALSE;
876
877     cps->drawDepth = appData.drawDepth[n];
878     cps->supportsNPS = UNKNOWN;
879     cps->memSize = FALSE;
880     cps->maxCores = FALSE;
881     ASSIGN(cps->egtFormats, "");
882
883     /* [HGM] options */
884     cps->optionSettings  = appData.engOptions[n];
885
886     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887     cps->isUCI = appData.isUCI[n]; /* [AS] */
888     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
889     cps->highlight = 0;
890
891     if (appData.protocolVersion[n] > PROTOVER
892         || appData.protocolVersion[n] < 1)
893       {
894         char buf[MSG_SIZ];
895         int len;
896
897         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898                        appData.protocolVersion[n]);
899         if( (len >= MSG_SIZ) && appData.debugMode )
900           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
901
902         DisplayFatalError(buf, 0, 2);
903       }
904     else
905       {
906         cps->protocolVersion = appData.protocolVersion[n];
907       }
908
909     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
910     ParseFeatures(appData.featureDefaults, cps);
911 }
912
913 ChessProgramState *savCps;
914
915 GameMode oldMode;
916
917 void
918 LoadEngine ()
919 {
920     int i;
921     if(WaitForEngine(savCps, LoadEngine)) return;
922     CommonEngineInit(); // recalculate time odds
923     if(gameInfo.variant != StringToVariant(appData.variant)) {
924         // we changed variant when loading the engine; this forces us to reset
925         Reset(TRUE, savCps != &first);
926         oldMode = BeginningOfGame; // to prevent restoring old mode
927     }
928     InitChessProgram(savCps, FALSE);
929     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930     DisplayMessage("", "");
931     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
933     ThawUI();
934     SetGNUMode();
935     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
936 }
937
938 void
939 ReplaceEngine (ChessProgramState *cps, int n)
940 {
941     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
942     keepInfo = 1;
943     if(oldMode != BeginningOfGame) EditGameEvent();
944     keepInfo = 0;
945     UnloadEngine(cps);
946     appData.noChessProgram = FALSE;
947     appData.clockMode = TRUE;
948     InitEngine(cps, n);
949     UpdateLogos(TRUE);
950     if(n) return; // only startup first engine immediately; second can wait
951     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
952     LoadEngine();
953 }
954
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
957
958 static char resetOptions[] =
959         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
963
964 void
965 FloatToFront(char **list, char *engineLine)
966 {
967     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
968     int i=0;
969     if(appData.recentEngines <= 0) return;
970     TidyProgramName(engineLine, "localhost", tidy+1);
971     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972     strncpy(buf+1, *list, MSG_SIZ-50);
973     if(p = strstr(buf, tidy)) { // tidy name appears in list
974         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975         while(*p++ = *++q); // squeeze out
976     }
977     strcat(tidy, buf+1); // put list behind tidy name
978     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980     ASSIGN(*list, tidy+1);
981 }
982
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
984
985 void
986 Load (ChessProgramState *cps, int i)
987 {
988     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994         appData.firstProtocolVersion = PROTOVER;
995         ParseArgsFromString(buf);
996         SwapEngines(i);
997         ReplaceEngine(cps, i);
998         FloatToFront(&appData.recentEngineList, engineLine);
999         if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
1000         return;
1001     }
1002     p = engineName;
1003     while(q = strchr(p, SLASH)) p = q+1;
1004     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1005     if(engineDir[0] != NULLCHAR) {
1006         ASSIGN(appData.directory[i], engineDir); p = engineName;
1007     } else if(p != engineName) { // derive directory from engine path, when not given
1008         p[-1] = 0;
1009         ASSIGN(appData.directory[i], engineName);
1010         p[-1] = SLASH;
1011         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1012     } else { ASSIGN(appData.directory[i], "."); }
1013     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1014     if(params[0]) {
1015         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1016         snprintf(command, MSG_SIZ, "%s %s", p, params);
1017         p = command;
1018     }
1019     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1020     ASSIGN(appData.chessProgram[i], p);
1021     appData.isUCI[i] = isUCI;
1022     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1023     appData.hasOwnBookUCI[i] = hasBook;
1024     if(!nickName[0]) useNick = FALSE;
1025     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1026     if(addToList) {
1027         int len;
1028         char quote;
1029         q = firstChessProgramNames;
1030         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1031         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1032         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1033                         quote, p, quote, appData.directory[i],
1034                         useNick ? " -fn \"" : "",
1035                         useNick ? nickName : "",
1036                         useNick ? "\"" : "",
1037                         v1 ? " -firstProtocolVersion 1" : "",
1038                         hasBook ? "" : " -fNoOwnBookUCI",
1039                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1040                         storeVariant ? " -variant " : "",
1041                         storeVariant ? VariantName(gameInfo.variant) : "");
1042         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1043         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1044         if(insert != q) insert[-1] = NULLCHAR;
1045         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1046         if(q)   free(q);
1047         FloatToFront(&appData.recentEngineList, buf);
1048     }
1049     ReplaceEngine(cps, i);
1050 }
1051
1052 void
1053 InitTimeControls ()
1054 {
1055     int matched, min, sec;
1056     /*
1057      * Parse timeControl resource
1058      */
1059     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1060                           appData.movesPerSession)) {
1061         char buf[MSG_SIZ];
1062         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1063         DisplayFatalError(buf, 0, 2);
1064     }
1065
1066     /*
1067      * Parse searchTime resource
1068      */
1069     if (*appData.searchTime != NULLCHAR) {
1070         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1071         if (matched == 1) {
1072             searchTime = min * 60;
1073         } else if (matched == 2) {
1074             searchTime = min * 60 + sec;
1075         } else {
1076             char buf[MSG_SIZ];
1077             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1078             DisplayFatalError(buf, 0, 2);
1079         }
1080     }
1081 }
1082
1083 void
1084 InitBackEnd1 ()
1085 {
1086
1087     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1088     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1089
1090     GetTimeMark(&programStartTime);
1091     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1092     appData.seedBase = random() + (random()<<15);
1093     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1094
1095     ClearProgramStats();
1096     programStats.ok_to_send = 1;
1097     programStats.seen_stat = 0;
1098
1099     /*
1100      * Initialize game list
1101      */
1102     ListNew(&gameList);
1103
1104
1105     /*
1106      * Internet chess server status
1107      */
1108     if (appData.icsActive) {
1109         appData.matchMode = FALSE;
1110         appData.matchGames = 0;
1111 #if ZIPPY
1112         appData.noChessProgram = !appData.zippyPlay;
1113 #else
1114         appData.zippyPlay = FALSE;
1115         appData.zippyTalk = FALSE;
1116         appData.noChessProgram = TRUE;
1117 #endif
1118         if (*appData.icsHelper != NULLCHAR) {
1119             appData.useTelnet = TRUE;
1120             appData.telnetProgram = appData.icsHelper;
1121         }
1122     } else {
1123         appData.zippyTalk = appData.zippyPlay = FALSE;
1124     }
1125
1126     /* [AS] Initialize pv info list [HGM] and game state */
1127     {
1128         int i, j;
1129
1130         for( i=0; i<=framePtr; i++ ) {
1131             pvInfoList[i].depth = -1;
1132             boards[i][EP_STATUS] = EP_NONE;
1133             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1134         }
1135     }
1136
1137     InitTimeControls();
1138
1139     /* [AS] Adjudication threshold */
1140     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1141
1142     InitEngine(&first, 0);
1143     InitEngine(&second, 1);
1144     CommonEngineInit();
1145
1146     pairing.which = "pairing"; // pairing engine
1147     pairing.pr = NoProc;
1148     pairing.isr = NULL;
1149     pairing.program = appData.pairingEngine;
1150     pairing.host = "localhost";
1151     pairing.dir = ".";
1152
1153     if (appData.icsActive) {
1154         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1155     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1156         appData.clockMode = FALSE;
1157         first.sendTime = second.sendTime = 0;
1158     }
1159
1160 #if ZIPPY
1161     /* Override some settings from environment variables, for backward
1162        compatibility.  Unfortunately it's not feasible to have the env
1163        vars just set defaults, at least in xboard.  Ugh.
1164     */
1165     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1166       ZippyInit();
1167     }
1168 #endif
1169
1170     if (!appData.icsActive) {
1171       char buf[MSG_SIZ];
1172       int len;
1173
1174       /* Check for variants that are supported only in ICS mode,
1175          or not at all.  Some that are accepted here nevertheless
1176          have bugs; see comments below.
1177       */
1178       VariantClass variant = StringToVariant(appData.variant);
1179       switch (variant) {
1180       case VariantBughouse:     /* need four players and two boards */
1181       case VariantKriegspiel:   /* need to hide pieces and move details */
1182         /* case VariantFischeRandom: (Fabien: moved below) */
1183         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1184         if( (len >= MSG_SIZ) && appData.debugMode )
1185           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1186
1187         DisplayFatalError(buf, 0, 2);
1188         return;
1189
1190       case VariantUnknown:
1191       case VariantLoadable:
1192       case Variant29:
1193       case Variant30:
1194       case Variant31:
1195       case Variant32:
1196       case Variant33:
1197       case Variant34:
1198       case Variant35:
1199       case Variant36:
1200       default:
1201         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1202         if( (len >= MSG_SIZ) && appData.debugMode )
1203           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1204
1205         DisplayFatalError(buf, 0, 2);
1206         return;
1207
1208       case VariantNormal:     /* definitely works! */
1209         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1210           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1211           return;
1212         }
1213       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1214       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1215       case VariantGothic:     /* [HGM] should work */
1216       case VariantCapablanca: /* [HGM] should work */
1217       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1218       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1219       case VariantChu:        /* [HGM] experimental */
1220       case VariantKnightmate: /* [HGM] should work */
1221       case VariantCylinder:   /* [HGM] untested */
1222       case VariantFalcon:     /* [HGM] untested */
1223       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1224                                  offboard interposition not understood */
1225       case VariantWildCastle: /* pieces not automatically shuffled */
1226       case VariantNoCastle:   /* pieces not automatically shuffled */
1227       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1228       case VariantLosers:     /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantSuicide:    /* should work except for win condition,
1231                                  and doesn't know captures are mandatory */
1232       case VariantGiveaway:   /* should work except for win condition,
1233                                  and doesn't know captures are mandatory */
1234       case VariantTwoKings:   /* should work */
1235       case VariantAtomic:     /* should work except for win condition */
1236       case Variant3Check:     /* should work except for win condition */
1237       case VariantShatranj:   /* should work except for all win conditions */
1238       case VariantMakruk:     /* should work except for draw countdown */
1239       case VariantASEAN :     /* should work except for draw countdown */
1240       case VariantBerolina:   /* might work if TestLegality is off */
1241       case VariantCapaRandom: /* should work */
1242       case VariantJanus:      /* should work */
1243       case VariantSuper:      /* experimental */
1244       case VariantGreat:      /* experimental, requires legality testing to be off */
1245       case VariantSChess:     /* S-Chess, should work */
1246       case VariantGrand:      /* should work */
1247       case VariantSpartan:    /* should work */
1248       case VariantLion:       /* should work */
1249       case VariantChuChess:   /* should work */
1250         break;
1251       }
1252     }
1253
1254 }
1255
1256 int
1257 NextIntegerFromString (char ** str, long * value)
1258 {
1259     int result = -1;
1260     char * s = *str;
1261
1262     while( *s == ' ' || *s == '\t' ) {
1263         s++;
1264     }
1265
1266     *value = 0;
1267
1268     if( *s >= '0' && *s <= '9' ) {
1269         while( *s >= '0' && *s <= '9' ) {
1270             *value = *value * 10 + (*s - '0');
1271             s++;
1272         }
1273
1274         result = 0;
1275     }
1276
1277     *str = s;
1278
1279     return result;
1280 }
1281
1282 int
1283 NextTimeControlFromString (char ** str, long * value)
1284 {
1285     long temp;
1286     int result = NextIntegerFromString( str, &temp );
1287
1288     if( result == 0 ) {
1289         *value = temp * 60; /* Minutes */
1290         if( **str == ':' ) {
1291             (*str)++;
1292             result = NextIntegerFromString( str, &temp );
1293             *value += temp; /* Seconds */
1294         }
1295     }
1296
1297     return result;
1298 }
1299
1300 int
1301 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1302 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1303     int result = -1, type = 0; long temp, temp2;
1304
1305     if(**str != ':') return -1; // old params remain in force!
1306     (*str)++;
1307     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1308     if( NextIntegerFromString( str, &temp ) ) return -1;
1309     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1310
1311     if(**str != '/') {
1312         /* time only: incremental or sudden-death time control */
1313         if(**str == '+') { /* increment follows; read it */
1314             (*str)++;
1315             if(**str == '!') type = *(*str)++; // Bronstein TC
1316             if(result = NextIntegerFromString( str, &temp2)) return -1;
1317             *inc = temp2 * 1000;
1318             if(**str == '.') { // read fraction of increment
1319                 char *start = ++(*str);
1320                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1321                 temp2 *= 1000;
1322                 while(start++ < *str) temp2 /= 10;
1323                 *inc += temp2;
1324             }
1325         } else *inc = 0;
1326         *moves = 0; *tc = temp * 1000; *incType = type;
1327         return 0;
1328     }
1329
1330     (*str)++; /* classical time control */
1331     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1332
1333     if(result == 0) {
1334         *moves = temp;
1335         *tc    = temp2 * 1000;
1336         *inc   = 0;
1337         *incType = type;
1338     }
1339     return result;
1340 }
1341
1342 int
1343 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1344 {   /* [HGM] get time to add from the multi-session time-control string */
1345     int incType, moves=1; /* kludge to force reading of first session */
1346     long time, increment;
1347     char *s = tcString;
1348
1349     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1350     do {
1351         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1352         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1353         if(movenr == -1) return time;    /* last move before new session     */
1354         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1355         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1356         if(!moves) return increment;     /* current session is incremental   */
1357         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1358     } while(movenr >= -1);               /* try again for next session       */
1359
1360     return 0; // no new time quota on this move
1361 }
1362
1363 int
1364 ParseTimeControl (char *tc, float ti, int mps)
1365 {
1366   long tc1;
1367   long tc2;
1368   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1369   int min, sec=0;
1370
1371   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1372   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1373       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1374   if(ti > 0) {
1375
1376     if(mps)
1377       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1378     else
1379       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1380   } else {
1381     if(mps)
1382       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1383     else
1384       snprintf(buf, MSG_SIZ, ":%s", mytc);
1385   }
1386   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1387
1388   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1389     return FALSE;
1390   }
1391
1392   if( *tc == '/' ) {
1393     /* Parse second time control */
1394     tc++;
1395
1396     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1397       return FALSE;
1398     }
1399
1400     if( tc2 == 0 ) {
1401       return FALSE;
1402     }
1403
1404     timeControl_2 = tc2 * 1000;
1405   }
1406   else {
1407     timeControl_2 = 0;
1408   }
1409
1410   if( tc1 == 0 ) {
1411     return FALSE;
1412   }
1413
1414   timeControl = tc1 * 1000;
1415
1416   if (ti >= 0) {
1417     timeIncrement = ti * 1000;  /* convert to ms */
1418     movesPerSession = 0;
1419   } else {
1420     timeIncrement = 0;
1421     movesPerSession = mps;
1422   }
1423   return TRUE;
1424 }
1425
1426 void
1427 InitBackEnd2 ()
1428 {
1429     if (appData.debugMode) {
1430 #    ifdef __GIT_VERSION
1431       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1432 #    else
1433       fprintf(debugFP, "Version: %s\n", programVersion);
1434 #    endif
1435     }
1436     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1437
1438     set_cont_sequence(appData.wrapContSeq);
1439     if (appData.matchGames > 0) {
1440         appData.matchMode = TRUE;
1441     } else if (appData.matchMode) {
1442         appData.matchGames = 1;
1443     }
1444     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1445         appData.matchGames = appData.sameColorGames;
1446     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1447         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1448         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1449     }
1450     Reset(TRUE, FALSE);
1451     if (appData.noChessProgram || first.protocolVersion == 1) {
1452       InitBackEnd3();
1453     } else {
1454       /* kludge: allow timeout for initial "feature" commands */
1455       FreezeUI();
1456       DisplayMessage("", _("Starting chess program"));
1457       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1458     }
1459 }
1460
1461 int
1462 CalculateIndex (int index, int gameNr)
1463 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1464     int res;
1465     if(index > 0) return index; // fixed nmber
1466     if(index == 0) return 1;
1467     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1468     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1469     return res;
1470 }
1471
1472 int
1473 LoadGameOrPosition (int gameNr)
1474 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1475     if (*appData.loadGameFile != NULLCHAR) {
1476         if (!LoadGameFromFile(appData.loadGameFile,
1477                 CalculateIndex(appData.loadGameIndex, gameNr),
1478                               appData.loadGameFile, FALSE)) {
1479             DisplayFatalError(_("Bad game file"), 0, 1);
1480             return 0;
1481         }
1482     } else if (*appData.loadPositionFile != NULLCHAR) {
1483         if (!LoadPositionFromFile(appData.loadPositionFile,
1484                 CalculateIndex(appData.loadPositionIndex, gameNr),
1485                                   appData.loadPositionFile)) {
1486             DisplayFatalError(_("Bad position file"), 0, 1);
1487             return 0;
1488         }
1489     }
1490     return 1;
1491 }
1492
1493 void
1494 ReserveGame (int gameNr, char resChar)
1495 {
1496     FILE *tf = fopen(appData.tourneyFile, "r+");
1497     char *p, *q, c, buf[MSG_SIZ];
1498     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1499     safeStrCpy(buf, lastMsg, MSG_SIZ);
1500     DisplayMessage(_("Pick new game"), "");
1501     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1502     ParseArgsFromFile(tf);
1503     p = q = appData.results;
1504     if(appData.debugMode) {
1505       char *r = appData.participants;
1506       fprintf(debugFP, "results = '%s'\n", p);
1507       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1508       fprintf(debugFP, "\n");
1509     }
1510     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1511     nextGame = q - p;
1512     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1513     safeStrCpy(q, p, strlen(p) + 2);
1514     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1515     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1516     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1517         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1518         q[nextGame] = '*';
1519     }
1520     fseek(tf, -(strlen(p)+4), SEEK_END);
1521     c = fgetc(tf);
1522     if(c != '"') // depending on DOS or Unix line endings we can be one off
1523          fseek(tf, -(strlen(p)+2), SEEK_END);
1524     else fseek(tf, -(strlen(p)+3), SEEK_END);
1525     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1526     DisplayMessage(buf, "");
1527     free(p); appData.results = q;
1528     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1529        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1530       int round = appData.defaultMatchGames * appData.tourneyType;
1531       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1532          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1533         UnloadEngine(&first);  // next game belongs to other pairing;
1534         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1535     }
1536     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1537 }
1538
1539 void
1540 MatchEvent (int mode)
1541 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1542         int dummy;
1543         if(matchMode) { // already in match mode: switch it off
1544             abortMatch = TRUE;
1545             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1546             return;
1547         }
1548 //      if(gameMode != BeginningOfGame) {
1549 //          DisplayError(_("You can only start a match from the initial position."), 0);
1550 //          return;
1551 //      }
1552         abortMatch = FALSE;
1553         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1554         /* Set up machine vs. machine match */
1555         nextGame = 0;
1556         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1557         if(appData.tourneyFile[0]) {
1558             ReserveGame(-1, 0);
1559             if(nextGame > appData.matchGames) {
1560                 char buf[MSG_SIZ];
1561                 if(strchr(appData.results, '*') == NULL) {
1562                     FILE *f;
1563                     appData.tourneyCycles++;
1564                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1565                         fclose(f);
1566                         NextTourneyGame(-1, &dummy);
1567                         ReserveGame(-1, 0);
1568                         if(nextGame <= appData.matchGames) {
1569                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1570                             matchMode = mode;
1571                             ScheduleDelayedEvent(NextMatchGame, 10000);
1572                             return;
1573                         }
1574                     }
1575                 }
1576                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1577                 DisplayError(buf, 0);
1578                 appData.tourneyFile[0] = 0;
1579                 return;
1580             }
1581         } else
1582         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1583             DisplayFatalError(_("Can't have a match with no chess programs"),
1584                               0, 2);
1585             return;
1586         }
1587         matchMode = mode;
1588         matchGame = roundNr = 1;
1589         first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
1590         NextMatchGame();
1591 }
1592
1593 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1594
1595 void
1596 InitBackEnd3 P((void))
1597 {
1598     GameMode initialMode;
1599     char buf[MSG_SIZ];
1600     int err, len;
1601
1602     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1603        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1604         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1605        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1606        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1607         char c, *q = first.variants, *p = strchr(q, ',');
1608         if(p) *p = NULLCHAR;
1609         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1610             int w, h, s;
1611             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1612                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1613             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1614             Reset(TRUE, FALSE);         // and re-initialize
1615         }
1616         if(p) *p = ',';
1617     }
1618
1619     InitChessProgram(&first, startedFromSetupPosition);
1620
1621     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1622         free(programVersion);
1623         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1624         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1625         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1626     }
1627
1628     if (appData.icsActive) {
1629 #ifdef WIN32
1630         /* [DM] Make a console window if needed [HGM] merged ifs */
1631         ConsoleCreate();
1632 #endif
1633         err = establish();
1634         if (err != 0)
1635           {
1636             if (*appData.icsCommPort != NULLCHAR)
1637               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1638                              appData.icsCommPort);
1639             else
1640               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1641                         appData.icsHost, appData.icsPort);
1642
1643             if( (len >= MSG_SIZ) && appData.debugMode )
1644               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1645
1646             DisplayFatalError(buf, err, 1);
1647             return;
1648         }
1649         SetICSMode();
1650         telnetISR =
1651           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1652         fromUserISR =
1653           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1654         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1655             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1656     } else if (appData.noChessProgram) {
1657         SetNCPMode();
1658     } else {
1659         SetGNUMode();
1660     }
1661
1662     if (*appData.cmailGameName != NULLCHAR) {
1663         SetCmailMode();
1664         OpenLoopback(&cmailPR);
1665         cmailISR =
1666           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1667     }
1668
1669     ThawUI();
1670     DisplayMessage("", "");
1671     if (StrCaseCmp(appData.initialMode, "") == 0) {
1672       initialMode = BeginningOfGame;
1673       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1674         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1675         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1676         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1677         ModeHighlight();
1678       }
1679     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1680       initialMode = TwoMachinesPlay;
1681     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1682       initialMode = AnalyzeFile;
1683     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1684       initialMode = AnalyzeMode;
1685     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1686       initialMode = MachinePlaysWhite;
1687     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1688       initialMode = MachinePlaysBlack;
1689     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1690       initialMode = EditGame;
1691     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1692       initialMode = EditPosition;
1693     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1694       initialMode = Training;
1695     } else {
1696       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1697       if( (len >= MSG_SIZ) && appData.debugMode )
1698         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1699
1700       DisplayFatalError(buf, 0, 2);
1701       return;
1702     }
1703
1704     if (appData.matchMode) {
1705         if(appData.tourneyFile[0]) { // start tourney from command line
1706             FILE *f;
1707             if(f = fopen(appData.tourneyFile, "r")) {
1708                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1709                 fclose(f);
1710                 appData.clockMode = TRUE;
1711                 SetGNUMode();
1712             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1713         }
1714         MatchEvent(TRUE);
1715     } else if (*appData.cmailGameName != NULLCHAR) {
1716         /* Set up cmail mode */
1717         ReloadCmailMsgEvent(TRUE);
1718     } else {
1719         /* Set up other modes */
1720         if (initialMode == AnalyzeFile) {
1721           if (*appData.loadGameFile == NULLCHAR) {
1722             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1723             return;
1724           }
1725         }
1726         if (*appData.loadGameFile != NULLCHAR) {
1727             (void) LoadGameFromFile(appData.loadGameFile,
1728                                     appData.loadGameIndex,
1729                                     appData.loadGameFile, TRUE);
1730         } else if (*appData.loadPositionFile != NULLCHAR) {
1731             (void) LoadPositionFromFile(appData.loadPositionFile,
1732                                         appData.loadPositionIndex,
1733                                         appData.loadPositionFile);
1734             /* [HGM] try to make self-starting even after FEN load */
1735             /* to allow automatic setup of fairy variants with wtm */
1736             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1737                 gameMode = BeginningOfGame;
1738                 setboardSpoiledMachineBlack = 1;
1739             }
1740             /* [HGM] loadPos: make that every new game uses the setup */
1741             /* from file as long as we do not switch variant          */
1742             if(!blackPlaysFirst) {
1743                 startedFromPositionFile = TRUE;
1744                 CopyBoard(filePosition, boards[0]);
1745                 CopyBoard(initialPosition, boards[0]);
1746             }
1747         } else if(*appData.fen != NULLCHAR) {
1748             if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
1749                 startedFromPositionFile = TRUE;
1750                 Reset(TRUE, TRUE);
1751             }
1752         }
1753         if (initialMode == AnalyzeMode) {
1754           if (appData.noChessProgram) {
1755             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1756             return;
1757           }
1758           if (appData.icsActive) {
1759             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1760             return;
1761           }
1762           AnalyzeModeEvent();
1763         } else if (initialMode == AnalyzeFile) {
1764           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1765           ShowThinkingEvent();
1766           AnalyzeFileEvent();
1767           AnalysisPeriodicEvent(1);
1768         } else if (initialMode == MachinePlaysWhite) {
1769           if (appData.noChessProgram) {
1770             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1771                               0, 2);
1772             return;
1773           }
1774           if (appData.icsActive) {
1775             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1776                               0, 2);
1777             return;
1778           }
1779           MachineWhiteEvent();
1780         } else if (initialMode == MachinePlaysBlack) {
1781           if (appData.noChessProgram) {
1782             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1783                               0, 2);
1784             return;
1785           }
1786           if (appData.icsActive) {
1787             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1788                               0, 2);
1789             return;
1790           }
1791           MachineBlackEvent();
1792         } else if (initialMode == TwoMachinesPlay) {
1793           if (appData.noChessProgram) {
1794             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1795                               0, 2);
1796             return;
1797           }
1798           if (appData.icsActive) {
1799             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1800                               0, 2);
1801             return;
1802           }
1803           TwoMachinesEvent();
1804         } else if (initialMode == EditGame) {
1805           EditGameEvent();
1806         } else if (initialMode == EditPosition) {
1807           EditPositionEvent();
1808         } else if (initialMode == Training) {
1809           if (*appData.loadGameFile == NULLCHAR) {
1810             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1811             return;
1812           }
1813           TrainingEvent();
1814         }
1815     }
1816 }
1817
1818 void
1819 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1820 {
1821     DisplayBook(current+1);
1822
1823     MoveHistorySet( movelist, first, last, current, pvInfoList );
1824
1825     EvalGraphSet( first, last, current, pvInfoList );
1826
1827     MakeEngineOutputTitle();
1828 }
1829
1830 /*
1831  * Establish will establish a contact to a remote host.port.
1832  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1833  *  used to talk to the host.
1834  * Returns 0 if okay, error code if not.
1835  */
1836 int
1837 establish ()
1838 {
1839     char buf[MSG_SIZ];
1840
1841     if (*appData.icsCommPort != NULLCHAR) {
1842         /* Talk to the host through a serial comm port */
1843         return OpenCommPort(appData.icsCommPort, &icsPR);
1844
1845     } else if (*appData.gateway != NULLCHAR) {
1846         if (*appData.remoteShell == NULLCHAR) {
1847             /* Use the rcmd protocol to run telnet program on a gateway host */
1848             snprintf(buf, sizeof(buf), "%s %s %s",
1849                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1850             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1851
1852         } else {
1853             /* Use the rsh program to run telnet program on a gateway host */
1854             if (*appData.remoteUser == NULLCHAR) {
1855                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1856                         appData.gateway, appData.telnetProgram,
1857                         appData.icsHost, appData.icsPort);
1858             } else {
1859                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1860                         appData.remoteShell, appData.gateway,
1861                         appData.remoteUser, appData.telnetProgram,
1862                         appData.icsHost, appData.icsPort);
1863             }
1864             return StartChildProcess(buf, "", &icsPR);
1865
1866         }
1867     } else if (appData.useTelnet) {
1868         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1869
1870     } else {
1871         /* TCP socket interface differs somewhat between
1872            Unix and NT; handle details in the front end.
1873            */
1874         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1875     }
1876 }
1877
1878 void
1879 EscapeExpand (char *p, char *q)
1880 {       // [HGM] initstring: routine to shape up string arguments
1881         while(*p++ = *q++) if(p[-1] == '\\')
1882             switch(*q++) {
1883                 case 'n': p[-1] = '\n'; break;
1884                 case 'r': p[-1] = '\r'; break;
1885                 case 't': p[-1] = '\t'; break;
1886                 case '\\': p[-1] = '\\'; break;
1887                 case 0: *p = 0; return;
1888                 default: p[-1] = q[-1]; break;
1889             }
1890 }
1891
1892 void
1893 show_bytes (FILE *fp, char *buf, int count)
1894 {
1895     while (count--) {
1896         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1897             fprintf(fp, "\\%03o", *buf & 0xff);
1898         } else {
1899             putc(*buf, fp);
1900         }
1901         buf++;
1902     }
1903     fflush(fp);
1904 }
1905
1906 /* Returns an errno value */
1907 int
1908 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1909 {
1910     char buf[8192], *p, *q, *buflim;
1911     int left, newcount, outcount;
1912
1913     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1914         *appData.gateway != NULLCHAR) {
1915         if (appData.debugMode) {
1916             fprintf(debugFP, ">ICS: ");
1917             show_bytes(debugFP, message, count);
1918             fprintf(debugFP, "\n");
1919         }
1920         return OutputToProcess(pr, message, count, outError);
1921     }
1922
1923     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1924     p = message;
1925     q = buf;
1926     left = count;
1927     newcount = 0;
1928     while (left) {
1929         if (q >= buflim) {
1930             if (appData.debugMode) {
1931                 fprintf(debugFP, ">ICS: ");
1932                 show_bytes(debugFP, buf, newcount);
1933                 fprintf(debugFP, "\n");
1934             }
1935             outcount = OutputToProcess(pr, buf, newcount, outError);
1936             if (outcount < newcount) return -1; /* to be sure */
1937             q = buf;
1938             newcount = 0;
1939         }
1940         if (*p == '\n') {
1941             *q++ = '\r';
1942             newcount++;
1943         } else if (((unsigned char) *p) == TN_IAC) {
1944             *q++ = (char) TN_IAC;
1945             newcount ++;
1946         }
1947         *q++ = *p++;
1948         newcount++;
1949         left--;
1950     }
1951     if (appData.debugMode) {
1952         fprintf(debugFP, ">ICS: ");
1953         show_bytes(debugFP, buf, newcount);
1954         fprintf(debugFP, "\n");
1955     }
1956     outcount = OutputToProcess(pr, buf, newcount, outError);
1957     if (outcount < newcount) return -1; /* to be sure */
1958     return count;
1959 }
1960
1961 void
1962 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1963 {
1964     int outError, outCount;
1965     static int gotEof = 0;
1966     static FILE *ini;
1967
1968     /* Pass data read from player on to ICS */
1969     if (count > 0) {
1970         gotEof = 0;
1971         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1972         if (outCount < count) {
1973             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1974         }
1975         if(have_sent_ICS_logon == 2) {
1976           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1977             fprintf(ini, "%s", message);
1978             have_sent_ICS_logon = 3;
1979           } else
1980             have_sent_ICS_logon = 1;
1981         } else if(have_sent_ICS_logon == 3) {
1982             fprintf(ini, "%s", message);
1983             fclose(ini);
1984           have_sent_ICS_logon = 1;
1985         }
1986     } else if (count < 0) {
1987         RemoveInputSource(isr);
1988         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1989     } else if (gotEof++ > 0) {
1990         RemoveInputSource(isr);
1991         DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
1992     }
1993 }
1994
1995 void
1996 KeepAlive ()
1997 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1998     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1999     connectionAlive = FALSE; // only sticks if no response to 'date' command.
2000     SendToICS("date\n");
2001     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
2002 }
2003
2004 /* added routine for printf style output to ics */
2005 void
2006 ics_printf (char *format, ...)
2007 {
2008     char buffer[MSG_SIZ];
2009     va_list args;
2010
2011     va_start(args, format);
2012     vsnprintf(buffer, sizeof(buffer), format, args);
2013     buffer[sizeof(buffer)-1] = '\0';
2014     SendToICS(buffer);
2015     va_end(args);
2016 }
2017
2018 void
2019 SendToICS (char *s)
2020 {
2021     int count, outCount, outError;
2022
2023     if (icsPR == NoProc) return;
2024
2025     count = strlen(s);
2026     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2027     if (outCount < count) {
2028         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2029     }
2030 }
2031
2032 /* This is used for sending logon scripts to the ICS. Sending
2033    without a delay causes problems when using timestamp on ICC
2034    (at least on my machine). */
2035 void
2036 SendToICSDelayed (char *s, long msdelay)
2037 {
2038     int count, outCount, outError;
2039
2040     if (icsPR == NoProc) return;
2041
2042     count = strlen(s);
2043     if (appData.debugMode) {
2044         fprintf(debugFP, ">ICS: ");
2045         show_bytes(debugFP, s, count);
2046         fprintf(debugFP, "\n");
2047     }
2048     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2049                                       msdelay);
2050     if (outCount < count) {
2051         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2052     }
2053 }
2054
2055
2056 /* Remove all highlighting escape sequences in s
2057    Also deletes any suffix starting with '('
2058    */
2059 char *
2060 StripHighlightAndTitle (char *s)
2061 {
2062     static char retbuf[MSG_SIZ];
2063     char *p = retbuf;
2064
2065     while (*s != NULLCHAR) {
2066         while (*s == '\033') {
2067             while (*s != NULLCHAR && !isalpha(*s)) s++;
2068             if (*s != NULLCHAR) s++;
2069         }
2070         while (*s != NULLCHAR && *s != '\033') {
2071             if (*s == '(' || *s == '[') {
2072                 *p = NULLCHAR;
2073                 return retbuf;
2074             }
2075             *p++ = *s++;
2076         }
2077     }
2078     *p = NULLCHAR;
2079     return retbuf;
2080 }
2081
2082 /* Remove all highlighting escape sequences in s */
2083 char *
2084 StripHighlight (char *s)
2085 {
2086     static char retbuf[MSG_SIZ];
2087     char *p = retbuf;
2088
2089     while (*s != NULLCHAR) {
2090         while (*s == '\033') {
2091             while (*s != NULLCHAR && !isalpha(*s)) s++;
2092             if (*s != NULLCHAR) s++;
2093         }
2094         while (*s != NULLCHAR && *s != '\033') {
2095             *p++ = *s++;
2096         }
2097     }
2098     *p = NULLCHAR;
2099     return retbuf;
2100 }
2101
2102 char engineVariant[MSG_SIZ];
2103 char *variantNames[] = VARIANT_NAMES;
2104 char *
2105 VariantName (VariantClass v)
2106 {
2107     if(v == VariantUnknown || *engineVariant) return engineVariant;
2108     return variantNames[v];
2109 }
2110
2111
2112 /* Identify a variant from the strings the chess servers use or the
2113    PGN Variant tag names we use. */
2114 VariantClass
2115 StringToVariant (char *e)
2116 {
2117     char *p;
2118     int wnum = -1;
2119     VariantClass v = VariantNormal;
2120     int i, found = FALSE;
2121     char buf[MSG_SIZ], c;
2122     int len;
2123
2124     if (!e) return v;
2125
2126     /* [HGM] skip over optional board-size prefixes */
2127     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2128         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2129         while( *e++ != '_');
2130     }
2131
2132     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2133         v = VariantNormal;
2134         found = TRUE;
2135     } else
2136     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2137       if (p = StrCaseStr(e, variantNames[i])) {
2138         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2139         v = (VariantClass) i;
2140         found = TRUE;
2141         break;
2142       }
2143     }
2144
2145     if (!found) {
2146       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2147           || StrCaseStr(e, "wild/fr")
2148           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2149         v = VariantFischeRandom;
2150       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2151                  (i = 1, p = StrCaseStr(e, "w"))) {
2152         p += i;
2153         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2154         if (isdigit(*p)) {
2155           wnum = atoi(p);
2156         } else {
2157           wnum = -1;
2158         }
2159         switch (wnum) {
2160         case 0: /* FICS only, actually */
2161         case 1:
2162           /* Castling legal even if K starts on d-file */
2163           v = VariantWildCastle;
2164           break;
2165         case 2:
2166         case 3:
2167         case 4:
2168           /* Castling illegal even if K & R happen to start in
2169              normal positions. */
2170           v = VariantNoCastle;
2171           break;
2172         case 5:
2173         case 7:
2174         case 8:
2175         case 10:
2176         case 11:
2177         case 12:
2178         case 13:
2179         case 14:
2180         case 15:
2181         case 18:
2182         case 19:
2183           /* Castling legal iff K & R start in normal positions */
2184           v = VariantNormal;
2185           break;
2186         case 6:
2187         case 20:
2188         case 21:
2189           /* Special wilds for position setup; unclear what to do here */
2190           v = VariantLoadable;
2191           break;
2192         case 9:
2193           /* Bizarre ICC game */
2194           v = VariantTwoKings;
2195           break;
2196         case 16:
2197           v = VariantKriegspiel;
2198           break;
2199         case 17:
2200           v = VariantLosers;
2201           break;
2202         case 22:
2203           v = VariantFischeRandom;
2204           break;
2205         case 23:
2206           v = VariantCrazyhouse;
2207           break;
2208         case 24:
2209           v = VariantBughouse;
2210           break;
2211         case 25:
2212           v = Variant3Check;
2213           break;
2214         case 26:
2215           /* Not quite the same as FICS suicide! */
2216           v = VariantGiveaway;
2217           break;
2218         case 27:
2219           v = VariantAtomic;
2220           break;
2221         case 28:
2222           v = VariantShatranj;
2223           break;
2224
2225         /* Temporary names for future ICC types.  The name *will* change in
2226            the next xboard/WinBoard release after ICC defines it. */
2227         case 29:
2228           v = Variant29;
2229           break;
2230         case 30:
2231           v = Variant30;
2232           break;
2233         case 31:
2234           v = Variant31;
2235           break;
2236         case 32:
2237           v = Variant32;
2238           break;
2239         case 33:
2240           v = Variant33;
2241           break;
2242         case 34:
2243           v = Variant34;
2244           break;
2245         case 35:
2246           v = Variant35;
2247           break;
2248         case 36:
2249           v = Variant36;
2250           break;
2251         case 37:
2252           v = VariantShogi;
2253           break;
2254         case 38:
2255           v = VariantXiangqi;
2256           break;
2257         case 39:
2258           v = VariantCourier;
2259           break;
2260         case 40:
2261           v = VariantGothic;
2262           break;
2263         case 41:
2264           v = VariantCapablanca;
2265           break;
2266         case 42:
2267           v = VariantKnightmate;
2268           break;
2269         case 43:
2270           v = VariantFairy;
2271           break;
2272         case 44:
2273           v = VariantCylinder;
2274           break;
2275         case 45:
2276           v = VariantFalcon;
2277           break;
2278         case 46:
2279           v = VariantCapaRandom;
2280           break;
2281         case 47:
2282           v = VariantBerolina;
2283           break;
2284         case 48:
2285           v = VariantJanus;
2286           break;
2287         case 49:
2288           v = VariantSuper;
2289           break;
2290         case 50:
2291           v = VariantGreat;
2292           break;
2293         case -1:
2294           /* Found "wild" or "w" in the string but no number;
2295              must assume it's normal chess. */
2296           v = VariantNormal;
2297           break;
2298         default:
2299           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2300           if( (len >= MSG_SIZ) && appData.debugMode )
2301             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2302
2303           DisplayError(buf, 0);
2304           v = VariantUnknown;
2305           break;
2306         }
2307       }
2308     }
2309     if (appData.debugMode) {
2310       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2311               e, wnum, VariantName(v));
2312     }
2313     return v;
2314 }
2315
2316 static int leftover_start = 0, leftover_len = 0;
2317 char star_match[STAR_MATCH_N][MSG_SIZ];
2318
2319 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2320    advance *index beyond it, and set leftover_start to the new value of
2321    *index; else return FALSE.  If pattern contains the character '*', it
2322    matches any sequence of characters not containing '\r', '\n', or the
2323    character following the '*' (if any), and the matched sequence(s) are
2324    copied into star_match.
2325    */
2326 int
2327 looking_at ( char *buf, int *index, char *pattern)
2328 {
2329     char *bufp = &buf[*index], *patternp = pattern;
2330     int star_count = 0;
2331     char *matchp = star_match[0];
2332
2333     for (;;) {
2334         if (*patternp == NULLCHAR) {
2335             *index = leftover_start = bufp - buf;
2336             *matchp = NULLCHAR;
2337             return TRUE;
2338         }
2339         if (*bufp == NULLCHAR) return FALSE;
2340         if (*patternp == '*') {
2341             if (*bufp == *(patternp + 1)) {
2342                 *matchp = NULLCHAR;
2343                 matchp = star_match[++star_count];
2344                 patternp += 2;
2345                 bufp++;
2346                 continue;
2347             } else if (*bufp == '\n' || *bufp == '\r') {
2348                 patternp++;
2349                 if (*patternp == NULLCHAR)
2350                   continue;
2351                 else
2352                   return FALSE;
2353             } else {
2354                 *matchp++ = *bufp++;
2355                 continue;
2356             }
2357         }
2358         if (*patternp != *bufp) return FALSE;
2359         patternp++;
2360         bufp++;
2361     }
2362 }
2363
2364 void
2365 SendToPlayer (char *data, int length)
2366 {
2367     int error, outCount;
2368     outCount = OutputToProcess(NoProc, data, length, &error);
2369     if (outCount < length) {
2370         DisplayFatalError(_("Error writing to display"), error, 1);
2371     }
2372 }
2373
2374 void
2375 PackHolding (char packed[], char *holding)
2376 {
2377     char *p = holding;
2378     char *q = packed;
2379     int runlength = 0;
2380     int curr = 9999;
2381     do {
2382         if (*p == curr) {
2383             runlength++;
2384         } else {
2385             switch (runlength) {
2386               case 0:
2387                 break;
2388               case 1:
2389                 *q++ = curr;
2390                 break;
2391               case 2:
2392                 *q++ = curr;
2393                 *q++ = curr;
2394                 break;
2395               default:
2396                 sprintf(q, "%d", runlength);
2397                 while (*q) q++;
2398                 *q++ = curr;
2399                 break;
2400             }
2401             runlength = 1;
2402             curr = *p;
2403         }
2404     } while (*p++);
2405     *q = NULLCHAR;
2406 }
2407
2408 /* Telnet protocol requests from the front end */
2409 void
2410 TelnetRequest (unsigned char ddww, unsigned char option)
2411 {
2412     unsigned char msg[3];
2413     int outCount, outError;
2414
2415     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2416
2417     if (appData.debugMode) {
2418         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2419         switch (ddww) {
2420           case TN_DO:
2421             ddwwStr = "DO";
2422             break;
2423           case TN_DONT:
2424             ddwwStr = "DONT";
2425             break;
2426           case TN_WILL:
2427             ddwwStr = "WILL";
2428             break;
2429           case TN_WONT:
2430             ddwwStr = "WONT";
2431             break;
2432           default:
2433             ddwwStr = buf1;
2434             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2435             break;
2436         }
2437         switch (option) {
2438           case TN_ECHO:
2439             optionStr = "ECHO";
2440             break;
2441           default:
2442             optionStr = buf2;
2443             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2444             break;
2445         }
2446         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2447     }
2448     msg[0] = TN_IAC;
2449     msg[1] = ddww;
2450     msg[2] = option;
2451     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2452     if (outCount < 3) {
2453         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2454     }
2455 }
2456
2457 void
2458 DoEcho ()
2459 {
2460     if (!appData.icsActive) return;
2461     TelnetRequest(TN_DO, TN_ECHO);
2462 }
2463
2464 void
2465 DontEcho ()
2466 {
2467     if (!appData.icsActive) return;
2468     TelnetRequest(TN_DONT, TN_ECHO);
2469 }
2470
2471 void
2472 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2473 {
2474     /* put the holdings sent to us by the server on the board holdings area */
2475     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2476     char p;
2477     ChessSquare piece;
2478
2479     if(gameInfo.holdingsWidth < 2)  return;
2480     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2481         return; // prevent overwriting by pre-board holdings
2482
2483     if( (int)lowestPiece >= BlackPawn ) {
2484         holdingsColumn = 0;
2485         countsColumn = 1;
2486         holdingsStartRow = BOARD_HEIGHT-1;
2487         direction = -1;
2488     } else {
2489         holdingsColumn = BOARD_WIDTH-1;
2490         countsColumn = BOARD_WIDTH-2;
2491         holdingsStartRow = 0;
2492         direction = 1;
2493     }
2494
2495     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2496         board[i][holdingsColumn] = EmptySquare;
2497         board[i][countsColumn]   = (ChessSquare) 0;
2498     }
2499     while( (p=*holdings++) != NULLCHAR ) {
2500         piece = CharToPiece( ToUpper(p) );
2501         if(piece == EmptySquare) continue;
2502         /*j = (int) piece - (int) WhitePawn;*/
2503         j = PieceToNumber(piece);
2504         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2505         if(j < 0) continue;               /* should not happen */
2506         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2507         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2508         board[holdingsStartRow+j*direction][countsColumn]++;
2509     }
2510 }
2511
2512
2513 void
2514 VariantSwitch (Board board, VariantClass newVariant)
2515 {
2516    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2517    static Board oldBoard;
2518
2519    startedFromPositionFile = FALSE;
2520    if(gameInfo.variant == newVariant) return;
2521
2522    /* [HGM] This routine is called each time an assignment is made to
2523     * gameInfo.variant during a game, to make sure the board sizes
2524     * are set to match the new variant. If that means adding or deleting
2525     * holdings, we shift the playing board accordingly
2526     * This kludge is needed because in ICS observe mode, we get boards
2527     * of an ongoing game without knowing the variant, and learn about the
2528     * latter only later. This can be because of the move list we requested,
2529     * in which case the game history is refilled from the beginning anyway,
2530     * but also when receiving holdings of a crazyhouse game. In the latter
2531     * case we want to add those holdings to the already received position.
2532     */
2533
2534
2535    if (appData.debugMode) {
2536      fprintf(debugFP, "Switch board from %s to %s\n",
2537              VariantName(gameInfo.variant), VariantName(newVariant));
2538      setbuf(debugFP, NULL);
2539    }
2540    shuffleOpenings = 0;       /* [HGM] shuffle */
2541    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2542    switch(newVariant)
2543      {
2544      case VariantShogi:
2545        newWidth = 9;  newHeight = 9;
2546        gameInfo.holdingsSize = 7;
2547      case VariantBughouse:
2548      case VariantCrazyhouse:
2549        newHoldingsWidth = 2; break;
2550      case VariantGreat:
2551        newWidth = 10;
2552      case VariantSuper:
2553        newHoldingsWidth = 2;
2554        gameInfo.holdingsSize = 8;
2555        break;
2556      case VariantGothic:
2557      case VariantCapablanca:
2558      case VariantCapaRandom:
2559        newWidth = 10;
2560      default:
2561        newHoldingsWidth = gameInfo.holdingsSize = 0;
2562      };
2563
2564    if(newWidth  != gameInfo.boardWidth  ||
2565       newHeight != gameInfo.boardHeight ||
2566       newHoldingsWidth != gameInfo.holdingsWidth ) {
2567
2568      /* shift position to new playing area, if needed */
2569      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2570        for(i=0; i<BOARD_HEIGHT; i++)
2571          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2572            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2573              board[i][j];
2574        for(i=0; i<newHeight; i++) {
2575          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2576          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2577        }
2578      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2579        for(i=0; i<BOARD_HEIGHT; i++)
2580          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2581            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2582              board[i][j];
2583      }
2584      board[HOLDINGS_SET] = 0;
2585      gameInfo.boardWidth  = newWidth;
2586      gameInfo.boardHeight = newHeight;
2587      gameInfo.holdingsWidth = newHoldingsWidth;
2588      gameInfo.variant = newVariant;
2589      InitDrawingSizes(-2, 0);
2590    } else gameInfo.variant = newVariant;
2591    CopyBoard(oldBoard, board);   // remember correctly formatted board
2592      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2593    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2594 }
2595
2596 static int loggedOn = FALSE;
2597
2598 /*-- Game start info cache: --*/
2599 int gs_gamenum;
2600 char gs_kind[MSG_SIZ];
2601 static char player1Name[128] = "";
2602 static char player2Name[128] = "";
2603 static char cont_seq[] = "\n\\   ";
2604 static int player1Rating = -1;
2605 static int player2Rating = -1;
2606 /*----------------------------*/
2607
2608 ColorClass curColor = ColorNormal;
2609 int suppressKibitz = 0;
2610
2611 // [HGM] seekgraph
2612 Boolean soughtPending = FALSE;
2613 Boolean seekGraphUp;
2614 #define MAX_SEEK_ADS 200
2615 #define SQUARE 0x80
2616 char *seekAdList[MAX_SEEK_ADS];
2617 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2618 float tcList[MAX_SEEK_ADS];
2619 char colorList[MAX_SEEK_ADS];
2620 int nrOfSeekAds = 0;
2621 int minRating = 1010, maxRating = 2800;
2622 int hMargin = 10, vMargin = 20, h, w;
2623 extern int squareSize, lineGap;
2624
2625 void
2626 PlotSeekAd (int i)
2627 {
2628         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2629         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2630         if(r < minRating+100 && r >=0 ) r = minRating+100;
2631         if(r > maxRating) r = maxRating;
2632         if(tc < 1.f) tc = 1.f;
2633         if(tc > 95.f) tc = 95.f;
2634         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2635         y = ((double)r - minRating)/(maxRating - minRating)
2636             * (h-vMargin-squareSize/8-1) + vMargin;
2637         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2638         if(strstr(seekAdList[i], " u ")) color = 1;
2639         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2640            !strstr(seekAdList[i], "bullet") &&
2641            !strstr(seekAdList[i], "blitz") &&
2642            !strstr(seekAdList[i], "standard") ) color = 2;
2643         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2644         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2645 }
2646
2647 void
2648 PlotSingleSeekAd (int i)
2649 {
2650         PlotSeekAd(i);
2651 }
2652
2653 void
2654 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2655 {
2656         char buf[MSG_SIZ], *ext = "";
2657         VariantClass v = StringToVariant(type);
2658         if(strstr(type, "wild")) {
2659             ext = type + 4; // append wild number
2660             if(v == VariantFischeRandom) type = "chess960"; else
2661             if(v == VariantLoadable) type = "setup"; else
2662             type = VariantName(v);
2663         }
2664         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2665         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2666             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2667             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2668             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2669             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2670             seekNrList[nrOfSeekAds] = nr;
2671             zList[nrOfSeekAds] = 0;
2672             seekAdList[nrOfSeekAds++] = StrSave(buf);
2673             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2674         }
2675 }
2676
2677 void
2678 EraseSeekDot (int i)
2679 {
2680     int x = xList[i], y = yList[i], d=squareSize/4, k;
2681     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2682     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2683     // now replot every dot that overlapped
2684     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2685         int xx = xList[k], yy = yList[k];
2686         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2687             DrawSeekDot(xx, yy, colorList[k]);
2688     }
2689 }
2690
2691 void
2692 RemoveSeekAd (int nr)
2693 {
2694         int i;
2695         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2696             EraseSeekDot(i);
2697             if(seekAdList[i]) free(seekAdList[i]);
2698             seekAdList[i] = seekAdList[--nrOfSeekAds];
2699             seekNrList[i] = seekNrList[nrOfSeekAds];
2700             ratingList[i] = ratingList[nrOfSeekAds];
2701             colorList[i]  = colorList[nrOfSeekAds];
2702             tcList[i] = tcList[nrOfSeekAds];
2703             xList[i]  = xList[nrOfSeekAds];
2704             yList[i]  = yList[nrOfSeekAds];
2705             zList[i]  = zList[nrOfSeekAds];
2706             seekAdList[nrOfSeekAds] = NULL;
2707             break;
2708         }
2709 }
2710
2711 Boolean
2712 MatchSoughtLine (char *line)
2713 {
2714     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2715     int nr, base, inc, u=0; char dummy;
2716
2717     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2718        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2719        (u=1) &&
2720        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2721         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2722         // match: compact and save the line
2723         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2724         return TRUE;
2725     }
2726     return FALSE;
2727 }
2728
2729 int
2730 DrawSeekGraph ()
2731 {
2732     int i;
2733     if(!seekGraphUp) return FALSE;
2734     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2735     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2736
2737     DrawSeekBackground(0, 0, w, h);
2738     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2739     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2740     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2741         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2742         yy = h-1-yy;
2743         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2744         if(i%500 == 0) {
2745             char buf[MSG_SIZ];
2746             snprintf(buf, MSG_SIZ, "%d", i);
2747             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2748         }
2749     }
2750     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2751     for(i=1; i<100; i+=(i<10?1:5)) {
2752         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2753         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2754         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2755             char buf[MSG_SIZ];
2756             snprintf(buf, MSG_SIZ, "%d", i);
2757             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2758         }
2759     }
2760     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2761     return TRUE;
2762 }
2763
2764 int
2765 SeekGraphClick (ClickType click, int x, int y, int moving)
2766 {
2767     static int lastDown = 0, displayed = 0, lastSecond;
2768     if(y < 0) return FALSE;
2769     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2770         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2771         if(!seekGraphUp) return FALSE;
2772         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2773         DrawPosition(TRUE, NULL);
2774         return TRUE;
2775     }
2776     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2777         if(click == Release || moving) return FALSE;
2778         nrOfSeekAds = 0;
2779         soughtPending = TRUE;
2780         SendToICS(ics_prefix);
2781         SendToICS("sought\n"); // should this be "sought all"?
2782     } else { // issue challenge based on clicked ad
2783         int dist = 10000; int i, closest = 0, second = 0;
2784         for(i=0; i<nrOfSeekAds; i++) {
2785             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2786             if(d < dist) { dist = d; closest = i; }
2787             second += (d - zList[i] < 120); // count in-range ads
2788             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2789         }
2790         if(dist < 120) {
2791             char buf[MSG_SIZ];
2792             second = (second > 1);
2793             if(displayed != closest || second != lastSecond) {
2794                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2795                 lastSecond = second; displayed = closest;
2796             }
2797             if(click == Press) {
2798                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2799                 lastDown = closest;
2800                 return TRUE;
2801             } // on press 'hit', only show info
2802             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2803             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2804             SendToICS(ics_prefix);
2805             SendToICS(buf);
2806             return TRUE; // let incoming board of started game pop down the graph
2807         } else if(click == Release) { // release 'miss' is ignored
2808             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2809             if(moving == 2) { // right up-click
2810                 nrOfSeekAds = 0; // refresh graph
2811                 soughtPending = TRUE;
2812                 SendToICS(ics_prefix);
2813                 SendToICS("sought\n"); // should this be "sought all"?
2814             }
2815             return TRUE;
2816         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2817         // press miss or release hit 'pop down' seek graph
2818         seekGraphUp = FALSE;
2819         DrawPosition(TRUE, NULL);
2820     }
2821     return TRUE;
2822 }
2823
2824 void
2825 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2826 {
2827 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2828 #define STARTED_NONE 0
2829 #define STARTED_MOVES 1
2830 #define STARTED_BOARD 2
2831 #define STARTED_OBSERVE 3
2832 #define STARTED_HOLDINGS 4
2833 #define STARTED_CHATTER 5
2834 #define STARTED_COMMENT 6
2835 #define STARTED_MOVES_NOHIDE 7
2836
2837     static int started = STARTED_NONE;
2838     static char parse[20000];
2839     static int parse_pos = 0;
2840     static char buf[BUF_SIZE + 1];
2841     static int firstTime = TRUE, intfSet = FALSE;
2842     static ColorClass prevColor = ColorNormal;
2843     static int savingComment = FALSE;
2844     static int cmatch = 0; // continuation sequence match
2845     char *bp;
2846     char str[MSG_SIZ];
2847     int i, oldi;
2848     int buf_len;
2849     int next_out;
2850     int tkind;
2851     int backup;    /* [DM] For zippy color lines */
2852     char *p;
2853     char talker[MSG_SIZ]; // [HGM] chat
2854     int channel, collective=0;
2855
2856     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2857
2858     if (appData.debugMode) {
2859       if (!error) {
2860         fprintf(debugFP, "<ICS: ");
2861         show_bytes(debugFP, data, count);
2862         fprintf(debugFP, "\n");
2863       }
2864     }
2865
2866     if (appData.debugMode) { int f = forwardMostMove;
2867         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2868                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2869                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2870     }
2871     if (count > 0) {
2872         /* If last read ended with a partial line that we couldn't parse,
2873            prepend it to the new read and try again. */
2874         if (leftover_len > 0) {
2875             for (i=0; i<leftover_len; i++)
2876               buf[i] = buf[leftover_start + i];
2877         }
2878
2879     /* copy new characters into the buffer */
2880     bp = buf + leftover_len;
2881     buf_len=leftover_len;
2882     for (i=0; i<count; i++)
2883     {
2884         // ignore these
2885         if (data[i] == '\r')
2886             continue;
2887
2888         // join lines split by ICS?
2889         if (!appData.noJoin)
2890         {
2891             /*
2892                 Joining just consists of finding matches against the
2893                 continuation sequence, and discarding that sequence
2894                 if found instead of copying it.  So, until a match
2895                 fails, there's nothing to do since it might be the
2896                 complete sequence, and thus, something we don't want
2897                 copied.
2898             */
2899             if (data[i] == cont_seq[cmatch])
2900             {
2901                 cmatch++;
2902                 if (cmatch == strlen(cont_seq))
2903                 {
2904                     cmatch = 0; // complete match.  just reset the counter
2905
2906                     /*
2907                         it's possible for the ICS to not include the space
2908                         at the end of the last word, making our [correct]
2909                         join operation fuse two separate words.  the server
2910                         does this when the space occurs at the width setting.
2911                     */
2912                     if (!buf_len || buf[buf_len-1] != ' ')
2913                     {
2914                         *bp++ = ' ';
2915                         buf_len++;
2916                     }
2917                 }
2918                 continue;
2919             }
2920             else if (cmatch)
2921             {
2922                 /*
2923                     match failed, so we have to copy what matched before
2924                     falling through and copying this character.  In reality,
2925                     this will only ever be just the newline character, but
2926                     it doesn't hurt to be precise.
2927                 */
2928                 strncpy(bp, cont_seq, cmatch);
2929                 bp += cmatch;
2930                 buf_len += cmatch;
2931                 cmatch = 0;
2932             }
2933         }
2934
2935         // copy this char
2936         *bp++ = data[i];
2937         buf_len++;
2938     }
2939
2940         buf[buf_len] = NULLCHAR;
2941 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2942         next_out = 0;
2943         leftover_start = 0;
2944
2945         i = 0;
2946         while (i < buf_len) {
2947             /* Deal with part of the TELNET option negotiation
2948                protocol.  We refuse to do anything beyond the
2949                defaults, except that we allow the WILL ECHO option,
2950                which ICS uses to turn off password echoing when we are
2951                directly connected to it.  We reject this option
2952                if localLineEditing mode is on (always on in xboard)
2953                and we are talking to port 23, which might be a real
2954                telnet server that will try to keep WILL ECHO on permanently.
2955              */
2956             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2957                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2958                 unsigned char option;
2959                 oldi = i;
2960                 switch ((unsigned char) buf[++i]) {
2961                   case TN_WILL:
2962                     if (appData.debugMode)
2963                       fprintf(debugFP, "\n<WILL ");
2964                     switch (option = (unsigned char) buf[++i]) {
2965                       case TN_ECHO:
2966                         if (appData.debugMode)
2967                           fprintf(debugFP, "ECHO ");
2968                         /* Reply only if this is a change, according
2969                            to the protocol rules. */
2970                         if (remoteEchoOption) break;
2971                         if (appData.localLineEditing &&
2972                             atoi(appData.icsPort) == TN_PORT) {
2973                             TelnetRequest(TN_DONT, TN_ECHO);
2974                         } else {
2975                             EchoOff();
2976                             TelnetRequest(TN_DO, TN_ECHO);
2977                             remoteEchoOption = TRUE;
2978                         }
2979                         break;
2980                       default:
2981                         if (appData.debugMode)
2982                           fprintf(debugFP, "%d ", option);
2983                         /* Whatever this is, we don't want it. */
2984                         TelnetRequest(TN_DONT, option);
2985                         break;
2986                     }
2987                     break;
2988                   case TN_WONT:
2989                     if (appData.debugMode)
2990                       fprintf(debugFP, "\n<WONT ");
2991                     switch (option = (unsigned char) buf[++i]) {
2992                       case TN_ECHO:
2993                         if (appData.debugMode)
2994                           fprintf(debugFP, "ECHO ");
2995                         /* Reply only if this is a change, according
2996                            to the protocol rules. */
2997                         if (!remoteEchoOption) break;
2998                         EchoOn();
2999                         TelnetRequest(TN_DONT, TN_ECHO);
3000                         remoteEchoOption = FALSE;
3001                         break;
3002                       default:
3003                         if (appData.debugMode)
3004                           fprintf(debugFP, "%d ", (unsigned char) option);
3005                         /* Whatever this is, it must already be turned
3006                            off, because we never agree to turn on
3007                            anything non-default, so according to the
3008                            protocol rules, we don't reply. */
3009                         break;
3010                     }
3011                     break;
3012                   case TN_DO:
3013                     if (appData.debugMode)
3014                       fprintf(debugFP, "\n<DO ");
3015                     switch (option = (unsigned char) buf[++i]) {
3016                       default:
3017                         /* Whatever this is, we refuse to do it. */
3018                         if (appData.debugMode)
3019                           fprintf(debugFP, "%d ", option);
3020                         TelnetRequest(TN_WONT, option);
3021                         break;
3022                     }
3023                     break;
3024                   case TN_DONT:
3025                     if (appData.debugMode)
3026                       fprintf(debugFP, "\n<DONT ");
3027                     switch (option = (unsigned char) buf[++i]) {
3028                       default:
3029                         if (appData.debugMode)
3030                           fprintf(debugFP, "%d ", option);
3031                         /* Whatever this is, we are already not doing
3032                            it, because we never agree to do anything
3033                            non-default, so according to the protocol
3034                            rules, we don't reply. */
3035                         break;
3036                     }
3037                     break;
3038                   case TN_IAC:
3039                     if (appData.debugMode)
3040                       fprintf(debugFP, "\n<IAC ");
3041                     /* Doubled IAC; pass it through */
3042                     i--;
3043                     break;
3044                   default:
3045                     if (appData.debugMode)
3046                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3047                     /* Drop all other telnet commands on the floor */
3048                     break;
3049                 }
3050                 if (oldi > next_out)
3051                   SendToPlayer(&buf[next_out], oldi - next_out);
3052                 if (++i > next_out)
3053                   next_out = i;
3054                 continue;
3055             }
3056
3057             /* OK, this at least will *usually* work */
3058             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3059                 loggedOn = TRUE;
3060             }
3061
3062             if (loggedOn && !intfSet) {
3063                 if (ics_type == ICS_ICC) {
3064                   snprintf(str, MSG_SIZ,
3065                           "/set-quietly interface %s\n/set-quietly style 12\n",
3066                           programVersion);
3067                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3068                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3069                 } else if (ics_type == ICS_CHESSNET) {
3070                   snprintf(str, MSG_SIZ, "/style 12\n");
3071                 } else {
3072                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3073                   strcat(str, programVersion);
3074                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3075                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3076                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3077 #ifdef WIN32
3078                   strcat(str, "$iset nohighlight 1\n");
3079 #endif
3080                   strcat(str, "$iset lock 1\n$style 12\n");
3081                 }
3082                 SendToICS(str);
3083                 NotifyFrontendLogin();
3084                 intfSet = TRUE;
3085             }
3086
3087             if (started == STARTED_COMMENT) {
3088                 /* Accumulate characters in comment */
3089                 parse[parse_pos++] = buf[i];
3090                 if (buf[i] == '\n') {
3091                     parse[parse_pos] = NULLCHAR;
3092                     if(chattingPartner>=0) {
3093                         char mess[MSG_SIZ];
3094                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3095                         OutputChatMessage(chattingPartner, mess);
3096                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3097                             int p;
3098                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3099                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3100                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3101                                 OutputChatMessage(p, mess);
3102                                 break;
3103                             }
3104                         }
3105                         chattingPartner = -1;
3106                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3107                         collective = 0;
3108                     } else
3109                     if(!suppressKibitz) // [HGM] kibitz
3110                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3111                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3112                         int nrDigit = 0, nrAlph = 0, j;
3113                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3114                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3115                         parse[parse_pos] = NULLCHAR;
3116                         // try to be smart: if it does not look like search info, it should go to
3117                         // ICS interaction window after all, not to engine-output window.
3118                         for(j=0; j<parse_pos; j++) { // count letters and digits
3119                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3120                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3121                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3122                         }
3123                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3124                             int depth=0; float score;
3125                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3126                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3127                                 pvInfoList[forwardMostMove-1].depth = depth;
3128                                 pvInfoList[forwardMostMove-1].score = 100*score;
3129                             }
3130                             OutputKibitz(suppressKibitz, parse);
3131                         } else {
3132                             char tmp[MSG_SIZ];
3133                             if(gameMode == IcsObserving) // restore original ICS messages
3134                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3135                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3136                             else
3137                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3138                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3139                             SendToPlayer(tmp, strlen(tmp));
3140                         }
3141                         next_out = i+1; // [HGM] suppress printing in ICS window
3142                     }
3143                     started = STARTED_NONE;
3144                 } else {
3145                     /* Don't match patterns against characters in comment */
3146                     i++;
3147                     continue;
3148                 }
3149             }
3150             if (started == STARTED_CHATTER) {
3151                 if (buf[i] != '\n') {
3152                     /* Don't match patterns against characters in chatter */
3153                     i++;
3154                     continue;
3155                 }
3156                 started = STARTED_NONE;
3157                 if(suppressKibitz) next_out = i+1;
3158             }
3159
3160             /* Kludge to deal with rcmd protocol */
3161             if (firstTime && looking_at(buf, &i, "\001*")) {
3162                 DisplayFatalError(&buf[1], 0, 1);
3163                 continue;
3164             } else {
3165                 firstTime = FALSE;
3166             }
3167
3168             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3169                 ics_type = ICS_ICC;
3170                 ics_prefix = "/";
3171                 if (appData.debugMode)
3172                   fprintf(debugFP, "ics_type %d\n", ics_type);
3173                 continue;
3174             }
3175             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3176                 ics_type = ICS_FICS;
3177                 ics_prefix = "$";
3178                 if (appData.debugMode)
3179                   fprintf(debugFP, "ics_type %d\n", ics_type);
3180                 continue;
3181             }
3182             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3183                 ics_type = ICS_CHESSNET;
3184                 ics_prefix = "/";
3185                 if (appData.debugMode)
3186                   fprintf(debugFP, "ics_type %d\n", ics_type);
3187                 continue;
3188             }
3189
3190             if (!loggedOn &&
3191                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3192                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3193                  looking_at(buf, &i, "will be \"*\""))) {
3194               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3195               continue;
3196             }
3197
3198             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3199               char buf[MSG_SIZ];
3200               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3201               DisplayIcsInteractionTitle(buf);
3202               have_set_title = TRUE;
3203             }
3204
3205             /* skip finger notes */
3206             if (started == STARTED_NONE &&
3207                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3208                  (buf[i] == '1' && buf[i+1] == '0')) &&
3209                 buf[i+2] == ':' && buf[i+3] == ' ') {
3210               started = STARTED_CHATTER;
3211               i += 3;
3212               continue;
3213             }
3214
3215             oldi = i;
3216             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3217             if(appData.seekGraph) {
3218                 if(soughtPending && MatchSoughtLine(buf+i)) {
3219                     i = strstr(buf+i, "rated") - buf;
3220                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                     next_out = leftover_start = i;
3222                     started = STARTED_CHATTER;
3223                     suppressKibitz = TRUE;
3224                     continue;
3225                 }
3226                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3227                         && looking_at(buf, &i, "* ads displayed")) {
3228                     soughtPending = FALSE;
3229                     seekGraphUp = TRUE;
3230                     DrawSeekGraph();
3231                     continue;
3232                 }
3233                 if(appData.autoRefresh) {
3234                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3235                         int s = (ics_type == ICS_ICC); // ICC format differs
3236                         if(seekGraphUp)
3237                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3238                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3239                         looking_at(buf, &i, "*% "); // eat prompt
3240                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3241                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3242                         next_out = i; // suppress
3243                         continue;
3244                     }
3245                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3246                         char *p = star_match[0];
3247                         while(*p) {
3248                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3249                             while(*p && *p++ != ' '); // next
3250                         }
3251                         looking_at(buf, &i, "*% "); // eat prompt
3252                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3253                         next_out = i;
3254                         continue;
3255                     }
3256                 }
3257             }
3258
3259             /* skip formula vars */
3260             if (started == STARTED_NONE &&
3261                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3262               started = STARTED_CHATTER;
3263               i += 3;
3264               continue;
3265             }
3266
3267             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3268             if (appData.autoKibitz && started == STARTED_NONE &&
3269                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3270                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3271                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3272                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3273                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3274                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3275                         suppressKibitz = TRUE;
3276                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3277                         next_out = i;
3278                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3279                                 && (gameMode == IcsPlayingWhite)) ||
3280                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3281                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3282                             started = STARTED_CHATTER; // own kibitz we simply discard
3283                         else {
3284                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3285                             parse_pos = 0; parse[0] = NULLCHAR;
3286                             savingComment = TRUE;
3287                             suppressKibitz = gameMode != IcsObserving ? 2 :
3288                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3289                         }
3290                         continue;
3291                 } else
3292                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3293                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3294                          && atoi(star_match[0])) {
3295                     // suppress the acknowledgements of our own autoKibitz
3296                     char *p;
3297                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3298                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3299                     SendToPlayer(star_match[0], strlen(star_match[0]));
3300                     if(looking_at(buf, &i, "*% ")) // eat prompt
3301                         suppressKibitz = FALSE;
3302                     next_out = i;
3303                     continue;
3304                 }
3305             } // [HGM] kibitz: end of patch
3306
3307             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3308
3309             // [HGM] chat: intercept tells by users for which we have an open chat window
3310             channel = -1;
3311             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3312                                            looking_at(buf, &i, "* whispers:") ||
3313                                            looking_at(buf, &i, "* kibitzes:") ||
3314                                            looking_at(buf, &i, "* shouts:") ||
3315                                            looking_at(buf, &i, "* c-shouts:") ||
3316                                            looking_at(buf, &i, "--> * ") ||
3317                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3318                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3319                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3320                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3321                 int p;
3322                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3323                 chattingPartner = -1; collective = 0;
3324
3325                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3326                 for(p=0; p<MAX_CHAT; p++) {
3327                     collective = 1;
3328                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3329                     talker[0] = '['; strcat(talker, "] ");
3330                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3331                     chattingPartner = p; break;
3332                     }
3333                 } else
3334                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3335                 for(p=0; p<MAX_CHAT; p++) {
3336                     collective = 1;
3337                     if(!strcmp("kibitzes", chatPartner[p])) {
3338                         talker[0] = '['; strcat(talker, "] ");
3339                         chattingPartner = p; break;
3340                     }
3341                 } else
3342                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3343                 for(p=0; p<MAX_CHAT; p++) {
3344                     collective = 1;
3345                     if(!strcmp("whispers", chatPartner[p])) {
3346                         talker[0] = '['; strcat(talker, "] ");
3347                         chattingPartner = p; break;
3348                     }
3349                 } else
3350                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3351                   if(buf[i-8] == '-' && buf[i-3] == 't')
3352                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3353                     collective = 1;
3354                     if(!strcmp("c-shouts", chatPartner[p])) {
3355                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3356                         chattingPartner = p; break;
3357                     }
3358                   }
3359                   if(chattingPartner < 0)
3360                   for(p=0; p<MAX_CHAT; p++) {
3361                     collective = 1;
3362                     if(!strcmp("shouts", chatPartner[p])) {
3363                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3364                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3365                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3366                         chattingPartner = p; break;
3367                     }
3368                   }
3369                 }
3370                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3371                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3372                     talker[0] = 0;
3373                     Colorize(ColorTell, FALSE);
3374                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3375                     collective |= 2;
3376                     chattingPartner = p; break;
3377                 }
3378                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3379                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3380                     started = STARTED_COMMENT;
3381                     parse_pos = 0; parse[0] = NULLCHAR;
3382                     savingComment = 3 + chattingPartner; // counts as TRUE
3383                     if(collective == 3) i = oldi; else {
3384                         suppressKibitz = TRUE;
3385                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3386                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3387                         continue;
3388                     }
3389                 }
3390             } // [HGM] chat: end of patch
3391
3392           backup = i;
3393             if (appData.zippyTalk || appData.zippyPlay) {
3394                 /* [DM] Backup address for color zippy lines */
3395 #if ZIPPY
3396                if (loggedOn == TRUE)
3397                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3398                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3399 #endif
3400             } // [DM] 'else { ' deleted
3401                 if (
3402                     /* Regular tells and says */
3403                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3404                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3405                     looking_at(buf, &i, "* says: ") ||
3406                     /* Don't color "message" or "messages" output */
3407                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3408                     looking_at(buf, &i, "*. * at *:*: ") ||
3409                     looking_at(buf, &i, "--* (*:*): ") ||
3410                     /* Message notifications (same color as tells) */
3411                     looking_at(buf, &i, "* has left a message ") ||
3412                     looking_at(buf, &i, "* just sent you a message:\n") ||
3413                     /* Whispers and kibitzes */
3414                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3415                     looking_at(buf, &i, "* kibitzes: ") ||
3416                     /* Channel tells */
3417                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3418
3419                   if (tkind == 1 && strchr(star_match[0], ':')) {
3420                       /* Avoid "tells you:" spoofs in channels */
3421                      tkind = 3;
3422                   }
3423                   if (star_match[0][0] == NULLCHAR ||
3424                       strchr(star_match[0], ' ') ||
3425                       (tkind == 3 && strchr(star_match[1], ' '))) {
3426                     /* Reject bogus matches */
3427                     i = oldi;
3428                   } else {
3429                     if (appData.colorize) {
3430                       if (oldi > next_out) {
3431                         SendToPlayer(&buf[next_out], oldi - next_out);
3432                         next_out = oldi;
3433                       }
3434                       switch (tkind) {
3435                       case 1:
3436                         Colorize(ColorTell, FALSE);
3437                         curColor = ColorTell;
3438                         break;
3439                       case 2:
3440                         Colorize(ColorKibitz, FALSE);
3441                         curColor = ColorKibitz;
3442                         break;
3443                       case 3:
3444                         p = strrchr(star_match[1], '(');
3445                         if (p == NULL) {
3446                           p = star_match[1];
3447                         } else {
3448                           p++;
3449                         }
3450                         if (atoi(p) == 1) {
3451                           Colorize(ColorChannel1, FALSE);
3452                           curColor = ColorChannel1;
3453                         } else {
3454                           Colorize(ColorChannel, FALSE);
3455                           curColor = ColorChannel;
3456                         }
3457                         break;
3458                       case 5:
3459                         curColor = ColorNormal;
3460                         break;
3461                       }
3462                     }
3463                     if (started == STARTED_NONE && appData.autoComment &&
3464                         (gameMode == IcsObserving ||
3465                          gameMode == IcsPlayingWhite ||
3466                          gameMode == IcsPlayingBlack)) {
3467                       parse_pos = i - oldi;
3468                       memcpy(parse, &buf[oldi], parse_pos);
3469                       parse[parse_pos] = NULLCHAR;
3470                       started = STARTED_COMMENT;
3471                       savingComment = TRUE;
3472                     } else if(collective != 3) {
3473                       started = STARTED_CHATTER;
3474                       savingComment = FALSE;
3475                     }
3476                     loggedOn = TRUE;
3477                     continue;
3478                   }
3479                 }
3480
3481                 if (looking_at(buf, &i, "* s-shouts: ") ||
3482                     looking_at(buf, &i, "* c-shouts: ")) {
3483                     if (appData.colorize) {
3484                         if (oldi > next_out) {
3485                             SendToPlayer(&buf[next_out], oldi - next_out);
3486                             next_out = oldi;
3487                         }
3488                         Colorize(ColorSShout, FALSE);
3489                         curColor = ColorSShout;
3490                     }
3491                     loggedOn = TRUE;
3492                     started = STARTED_CHATTER;
3493                     continue;
3494                 }
3495
3496                 if (looking_at(buf, &i, "--->")) {
3497                     loggedOn = TRUE;
3498                     continue;
3499                 }
3500
3501                 if (looking_at(buf, &i, "* shouts: ") ||
3502                     looking_at(buf, &i, "--> ")) {
3503                     if (appData.colorize) {
3504                         if (oldi > next_out) {
3505                             SendToPlayer(&buf[next_out], oldi - next_out);
3506                             next_out = oldi;
3507                         }
3508                         Colorize(ColorShout, FALSE);
3509                         curColor = ColorShout;
3510                     }
3511                     loggedOn = TRUE;
3512                     started = STARTED_CHATTER;
3513                     continue;
3514                 }
3515
3516                 if (looking_at( buf, &i, "Challenge:")) {
3517                     if (appData.colorize) {
3518                         if (oldi > next_out) {
3519                             SendToPlayer(&buf[next_out], oldi - next_out);
3520                             next_out = oldi;
3521                         }
3522                         Colorize(ColorChallenge, FALSE);
3523                         curColor = ColorChallenge;
3524                     }
3525                     loggedOn = TRUE;
3526                     continue;
3527                 }
3528
3529                 if (looking_at(buf, &i, "* offers you") ||
3530                     looking_at(buf, &i, "* offers to be") ||
3531                     looking_at(buf, &i, "* would like to") ||
3532                     looking_at(buf, &i, "* requests to") ||
3533                     looking_at(buf, &i, "Your opponent offers") ||
3534                     looking_at(buf, &i, "Your opponent requests")) {
3535
3536                     if (appData.colorize) {
3537                         if (oldi > next_out) {
3538                             SendToPlayer(&buf[next_out], oldi - next_out);
3539                             next_out = oldi;
3540                         }
3541                         Colorize(ColorRequest, FALSE);
3542                         curColor = ColorRequest;
3543                     }
3544                     continue;
3545                 }
3546
3547                 if (looking_at(buf, &i, "* (*) seeking")) {
3548                     if (appData.colorize) {
3549                         if (oldi > next_out) {
3550                             SendToPlayer(&buf[next_out], oldi - next_out);
3551                             next_out = oldi;
3552                         }
3553                         Colorize(ColorSeek, FALSE);
3554                         curColor = ColorSeek;
3555                     }
3556                     continue;
3557             }
3558
3559           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3560
3561             if (looking_at(buf, &i, "\\   ")) {
3562                 if (prevColor != ColorNormal) {
3563                     if (oldi > next_out) {
3564                         SendToPlayer(&buf[next_out], oldi - next_out);
3565                         next_out = oldi;
3566                     }
3567                     Colorize(prevColor, TRUE);
3568                     curColor = prevColor;
3569                 }
3570                 if (savingComment) {
3571                     parse_pos = i - oldi;
3572                     memcpy(parse, &buf[oldi], parse_pos);
3573                     parse[parse_pos] = NULLCHAR;
3574                     started = STARTED_COMMENT;
3575                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3576                         chattingPartner = savingComment - 3; // kludge to remember the box
3577                 } else {
3578                     started = STARTED_CHATTER;
3579                 }
3580                 continue;
3581             }
3582
3583             if (looking_at(buf, &i, "Black Strength :") ||
3584                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3585                 looking_at(buf, &i, "<10>") ||
3586                 looking_at(buf, &i, "#@#")) {
3587                 /* Wrong board style */
3588                 loggedOn = TRUE;
3589                 SendToICS(ics_prefix);
3590                 SendToICS("set style 12\n");
3591                 SendToICS(ics_prefix);
3592                 SendToICS("refresh\n");
3593                 continue;
3594             }
3595
3596             if (looking_at(buf, &i, "login:")) {
3597               if (!have_sent_ICS_logon) {
3598                 if(ICSInitScript())
3599                   have_sent_ICS_logon = 1;
3600                 else // no init script was found
3601                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3602               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3603                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3604               }
3605                 continue;
3606             }
3607
3608             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3609                 (looking_at(buf, &i, "\n<12> ") ||
3610                  looking_at(buf, &i, "<12> "))) {
3611                 loggedOn = TRUE;
3612                 if (oldi > next_out) {
3613                     SendToPlayer(&buf[next_out], oldi - next_out);
3614                 }
3615                 next_out = i;
3616                 started = STARTED_BOARD;
3617                 parse_pos = 0;
3618                 continue;
3619             }
3620
3621             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3622                 looking_at(buf, &i, "<b1> ")) {
3623                 if (oldi > next_out) {
3624                     SendToPlayer(&buf[next_out], oldi - next_out);
3625                 }
3626                 next_out = i;
3627                 started = STARTED_HOLDINGS;
3628                 parse_pos = 0;
3629                 continue;
3630             }
3631
3632             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3633                 loggedOn = TRUE;
3634                 /* Header for a move list -- first line */
3635
3636                 switch (ics_getting_history) {
3637                   case H_FALSE:
3638                     switch (gameMode) {
3639                       case IcsIdle:
3640                       case BeginningOfGame:
3641                         /* User typed "moves" or "oldmoves" while we
3642                            were idle.  Pretend we asked for these
3643                            moves and soak them up so user can step
3644                            through them and/or save them.
3645                            */
3646                         Reset(FALSE, TRUE);
3647                         gameMode = IcsObserving;
3648                         ModeHighlight();
3649                         ics_gamenum = -1;
3650                         ics_getting_history = H_GOT_UNREQ_HEADER;
3651                         break;
3652                       case EditGame: /*?*/
3653                       case EditPosition: /*?*/
3654                         /* Should above feature work in these modes too? */
3655                         /* For now it doesn't */
3656                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3657                         break;
3658                       default:
3659                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3660                         break;
3661                     }
3662                     break;
3663                   case H_REQUESTED:
3664                     /* Is this the right one? */
3665                     if (gameInfo.white && gameInfo.black &&
3666                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3667                         strcmp(gameInfo.black, star_match[2]) == 0) {
3668                         /* All is well */
3669                         ics_getting_history = H_GOT_REQ_HEADER;
3670                     }
3671                     break;
3672                   case H_GOT_REQ_HEADER:
3673                   case H_GOT_UNREQ_HEADER:
3674                   case H_GOT_UNWANTED_HEADER:
3675                   case H_GETTING_MOVES:
3676                     /* Should not happen */
3677                     DisplayError(_("Error gathering move list: two headers"), 0);
3678                     ics_getting_history = H_FALSE;
3679                     break;
3680                 }
3681
3682                 /* Save player ratings into gameInfo if needed */
3683                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3684                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3685                     (gameInfo.whiteRating == -1 ||
3686                      gameInfo.blackRating == -1)) {
3687
3688                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3689                     gameInfo.blackRating = string_to_rating(star_match[3]);
3690                     if (appData.debugMode)
3691                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3692                               gameInfo.whiteRating, gameInfo.blackRating);
3693                 }
3694                 continue;
3695             }
3696
3697             if (looking_at(buf, &i,
3698               "* * match, initial time: * minute*, increment: * second")) {
3699                 /* Header for a move list -- second line */
3700                 /* Initial board will follow if this is a wild game */
3701                 if (gameInfo.event != NULL) free(gameInfo.event);
3702                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3703                 gameInfo.event = StrSave(str);
3704                 /* [HGM] we switched variant. Translate boards if needed. */
3705                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3706                 continue;
3707             }
3708
3709             if (looking_at(buf, &i, "Move  ")) {
3710                 /* Beginning of a move list */
3711                 switch (ics_getting_history) {
3712                   case H_FALSE:
3713                     /* Normally should not happen */
3714                     /* Maybe user hit reset while we were parsing */
3715                     break;
3716                   case H_REQUESTED:
3717                     /* Happens if we are ignoring a move list that is not
3718                      * the one we just requested.  Common if the user
3719                      * tries to observe two games without turning off
3720                      * getMoveList */
3721                     break;
3722                   case H_GETTING_MOVES:
3723                     /* Should not happen */
3724                     DisplayError(_("Error gathering move list: nested"), 0);
3725                     ics_getting_history = H_FALSE;
3726                     break;
3727                   case H_GOT_REQ_HEADER:
3728                     ics_getting_history = H_GETTING_MOVES;
3729                     started = STARTED_MOVES;
3730                     parse_pos = 0;
3731                     if (oldi > next_out) {
3732                         SendToPlayer(&buf[next_out], oldi - next_out);
3733                     }
3734                     break;
3735                   case H_GOT_UNREQ_HEADER:
3736                     ics_getting_history = H_GETTING_MOVES;
3737                     started = STARTED_MOVES_NOHIDE;
3738                     parse_pos = 0;
3739                     break;
3740                   case H_GOT_UNWANTED_HEADER:
3741                     ics_getting_history = H_FALSE;
3742                     break;
3743                 }
3744                 continue;
3745             }
3746
3747             if (looking_at(buf, &i, "% ") ||
3748                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3749                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3750                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3751                     soughtPending = FALSE;
3752                     seekGraphUp = TRUE;
3753                     DrawSeekGraph();
3754                 }
3755                 if(suppressKibitz) next_out = i;
3756                 savingComment = FALSE;
3757                 suppressKibitz = 0;
3758                 switch (started) {
3759                   case STARTED_MOVES:
3760                   case STARTED_MOVES_NOHIDE:
3761                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3762                     parse[parse_pos + i - oldi] = NULLCHAR;
3763                     ParseGameHistory(parse);
3764 #if ZIPPY
3765                     if (appData.zippyPlay && first.initDone) {
3766                         FeedMovesToProgram(&first, forwardMostMove);
3767                         if (gameMode == IcsPlayingWhite) {
3768                             if (WhiteOnMove(forwardMostMove)) {
3769                                 if (first.sendTime) {
3770                                   if (first.useColors) {
3771                                     SendToProgram("black\n", &first);
3772                                   }
3773                                   SendTimeRemaining(&first, TRUE);
3774                                 }
3775                                 if (first.useColors) {
3776                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3777                                 }
3778                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3779                                 first.maybeThinking = TRUE;
3780                             } else {
3781                                 if (first.usePlayother) {
3782                                   if (first.sendTime) {
3783                                     SendTimeRemaining(&first, TRUE);
3784                                   }
3785                                   SendToProgram("playother\n", &first);
3786                                   firstMove = FALSE;
3787                                 } else {
3788                                   firstMove = TRUE;
3789                                 }
3790                             }
3791                         } else if (gameMode == IcsPlayingBlack) {
3792                             if (!WhiteOnMove(forwardMostMove)) {
3793                                 if (first.sendTime) {
3794                                   if (first.useColors) {
3795                                     SendToProgram("white\n", &first);
3796                                   }
3797                                   SendTimeRemaining(&first, FALSE);
3798                                 }
3799                                 if (first.useColors) {
3800                                   SendToProgram("black\n", &first);
3801                                 }
3802                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3803                                 first.maybeThinking = TRUE;
3804                             } else {
3805                                 if (first.usePlayother) {
3806                                   if (first.sendTime) {
3807                                     SendTimeRemaining(&first, FALSE);
3808                                   }
3809                                   SendToProgram("playother\n", &first);
3810                                   firstMove = FALSE;
3811                                 } else {
3812                                   firstMove = TRUE;
3813                                 }
3814                             }
3815                         }
3816                     }
3817 #endif
3818                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3819                         /* Moves came from oldmoves or moves command
3820                            while we weren't doing anything else.
3821                            */
3822                         currentMove = forwardMostMove;
3823                         ClearHighlights();/*!!could figure this out*/
3824                         flipView = appData.flipView;
3825                         DrawPosition(TRUE, boards[currentMove]);
3826                         DisplayBothClocks();
3827                         snprintf(str, MSG_SIZ, "%s %s %s",
3828                                 gameInfo.white, _("vs."),  gameInfo.black);
3829                         DisplayTitle(str);
3830                         gameMode = IcsIdle;
3831                     } else {
3832                         /* Moves were history of an active game */
3833                         if (gameInfo.resultDetails != NULL) {
3834                             free(gameInfo.resultDetails);
3835                             gameInfo.resultDetails = NULL;
3836                         }
3837                     }
3838                     HistorySet(parseList, backwardMostMove,
3839                                forwardMostMove, currentMove-1);
3840                     DisplayMove(currentMove - 1);
3841                     if (started == STARTED_MOVES) next_out = i;
3842                     started = STARTED_NONE;
3843                     ics_getting_history = H_FALSE;
3844                     break;
3845
3846                   case STARTED_OBSERVE:
3847                     started = STARTED_NONE;
3848                     SendToICS(ics_prefix);
3849                     SendToICS("refresh\n");
3850                     break;
3851
3852                   default:
3853                     break;
3854                 }
3855                 if(bookHit) { // [HGM] book: simulate book reply
3856                     static char bookMove[MSG_SIZ]; // a bit generous?
3857
3858                     programStats.nodes = programStats.depth = programStats.time =
3859                     programStats.score = programStats.got_only_move = 0;
3860                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3861
3862                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3863                     strcat(bookMove, bookHit);
3864                     HandleMachineMove(bookMove, &first);
3865                 }
3866                 continue;
3867             }
3868
3869             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3870                  started == STARTED_HOLDINGS ||
3871                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3872                 /* Accumulate characters in move list or board */
3873                 parse[parse_pos++] = buf[i];
3874             }
3875
3876             /* Start of game messages.  Mostly we detect start of game
3877                when the first board image arrives.  On some versions
3878                of the ICS, though, we need to do a "refresh" after starting
3879                to observe in order to get the current board right away. */
3880             if (looking_at(buf, &i, "Adding game * to observation list")) {
3881                 started = STARTED_OBSERVE;
3882                 continue;
3883             }
3884
3885             /* Handle auto-observe */
3886             if (appData.autoObserve &&
3887                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3888                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3889                 char *player;
3890                 /* Choose the player that was highlighted, if any. */
3891                 if (star_match[0][0] == '\033' ||
3892                     star_match[1][0] != '\033') {
3893                     player = star_match[0];
3894                 } else {
3895                     player = star_match[2];
3896                 }
3897                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3898                         ics_prefix, StripHighlightAndTitle(player));
3899                 SendToICS(str);
3900
3901                 /* Save ratings from notify string */
3902                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3903                 player1Rating = string_to_rating(star_match[1]);
3904                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3905                 player2Rating = string_to_rating(star_match[3]);
3906
3907                 if (appData.debugMode)
3908                   fprintf(debugFP,
3909                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3910                           player1Name, player1Rating,
3911                           player2Name, player2Rating);
3912
3913                 continue;
3914             }
3915
3916             /* Deal with automatic examine mode after a game,
3917                and with IcsObserving -> IcsExamining transition */
3918             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3919                 looking_at(buf, &i, "has made you an examiner of game *")) {
3920
3921                 int gamenum = atoi(star_match[0]);
3922                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3923                     gamenum == ics_gamenum) {
3924                     /* We were already playing or observing this game;
3925                        no need to refetch history */
3926                     gameMode = IcsExamining;
3927                     if (pausing) {
3928                         pauseExamForwardMostMove = forwardMostMove;
3929                     } else if (currentMove < forwardMostMove) {
3930                         ForwardInner(forwardMostMove);
3931                     }
3932                 } else {
3933                     /* I don't think this case really can happen */
3934                     SendToICS(ics_prefix);
3935                     SendToICS("refresh\n");
3936                 }
3937                 continue;
3938             }
3939
3940             /* Error messages */
3941 //          if (ics_user_moved) {
3942             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3943                 if (looking_at(buf, &i, "Illegal move") ||
3944                     looking_at(buf, &i, "Not a legal move") ||
3945                     looking_at(buf, &i, "Your king is in check") ||
3946                     looking_at(buf, &i, "It isn't your turn") ||
3947                     looking_at(buf, &i, "It is not your move")) {
3948                     /* Illegal move */
3949                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3950                         currentMove = forwardMostMove-1;
3951                         DisplayMove(currentMove - 1); /* before DMError */
3952                         DrawPosition(FALSE, boards[currentMove]);
3953                         SwitchClocks(forwardMostMove-1); // [HGM] race
3954                         DisplayBothClocks();
3955                     }
3956                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3957                     ics_user_moved = 0;
3958                     continue;
3959                 }
3960             }
3961
3962             if (looking_at(buf, &i, "still have time") ||
3963                 looking_at(buf, &i, "not out of time") ||
3964                 looking_at(buf, &i, "either player is out of time") ||
3965                 looking_at(buf, &i, "has timeseal; checking")) {
3966                 /* We must have called his flag a little too soon */
3967                 whiteFlag = blackFlag = FALSE;
3968                 continue;
3969             }
3970
3971             if (looking_at(buf, &i, "added * seconds to") ||
3972                 looking_at(buf, &i, "seconds were added to")) {
3973                 /* Update the clocks */
3974                 SendToICS(ics_prefix);
3975                 SendToICS("refresh\n");
3976                 continue;
3977             }
3978
3979             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3980                 ics_clock_paused = TRUE;
3981                 StopClocks();
3982                 continue;
3983             }
3984
3985             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3986                 ics_clock_paused = FALSE;
3987                 StartClocks();
3988                 continue;
3989             }
3990
3991             /* Grab player ratings from the Creating: message.
3992                Note we have to check for the special case when
3993                the ICS inserts things like [white] or [black]. */
3994             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3995                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3996                 /* star_matches:
3997                    0    player 1 name (not necessarily white)
3998                    1    player 1 rating
3999                    2    empty, white, or black (IGNORED)
4000                    3    player 2 name (not necessarily black)
4001                    4    player 2 rating
4002
4003                    The names/ratings are sorted out when the game
4004                    actually starts (below).
4005                 */
4006                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4007                 player1Rating = string_to_rating(star_match[1]);
4008                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4009                 player2Rating = string_to_rating(star_match[4]);
4010
4011                 if (appData.debugMode)
4012                   fprintf(debugFP,
4013                           "Ratings from 'Creating:' %s %d, %s %d\n",
4014                           player1Name, player1Rating,
4015                           player2Name, player2Rating);
4016
4017                 continue;
4018             }
4019
4020             /* Improved generic start/end-of-game messages */
4021             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4022                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4023                 /* If tkind == 0: */
4024                 /* star_match[0] is the game number */
4025                 /*           [1] is the white player's name */
4026                 /*           [2] is the black player's name */
4027                 /* For end-of-game: */
4028                 /*           [3] is the reason for the game end */
4029                 /*           [4] is a PGN end game-token, preceded by " " */
4030                 /* For start-of-game: */
4031                 /*           [3] begins with "Creating" or "Continuing" */
4032                 /*           [4] is " *" or empty (don't care). */
4033                 int gamenum = atoi(star_match[0]);
4034                 char *whitename, *blackname, *why, *endtoken;
4035                 ChessMove endtype = EndOfFile;
4036
4037                 if (tkind == 0) {
4038                   whitename = star_match[1];
4039                   blackname = star_match[2];
4040                   why = star_match[3];
4041                   endtoken = star_match[4];
4042                 } else {
4043                   whitename = star_match[1];
4044                   blackname = star_match[3];
4045                   why = star_match[5];
4046                   endtoken = star_match[6];
4047                 }
4048
4049                 /* Game start messages */
4050                 if (strncmp(why, "Creating ", 9) == 0 ||
4051                     strncmp(why, "Continuing ", 11) == 0) {
4052                     gs_gamenum = gamenum;
4053                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4054                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4055                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4056 #if ZIPPY
4057                     if (appData.zippyPlay) {
4058                         ZippyGameStart(whitename, blackname);
4059                     }
4060 #endif /*ZIPPY*/
4061                     partnerBoardValid = FALSE; // [HGM] bughouse
4062                     continue;
4063                 }
4064
4065                 /* Game end messages */
4066                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4067                     ics_gamenum != gamenum) {
4068                     continue;
4069                 }
4070                 while (endtoken[0] == ' ') endtoken++;
4071                 switch (endtoken[0]) {
4072                   case '*':
4073                   default:
4074                     endtype = GameUnfinished;
4075                     break;
4076                   case '0':
4077                     endtype = BlackWins;
4078                     break;
4079                   case '1':
4080                     if (endtoken[1] == '/')
4081                       endtype = GameIsDrawn;
4082                     else
4083                       endtype = WhiteWins;
4084                     break;
4085                 }
4086                 GameEnds(endtype, why, GE_ICS);
4087 #if ZIPPY
4088                 if (appData.zippyPlay && first.initDone) {
4089                     ZippyGameEnd(endtype, why);
4090                     if (first.pr == NoProc) {
4091                       /* Start the next process early so that we'll
4092                          be ready for the next challenge */
4093                       StartChessProgram(&first);
4094                     }
4095                     /* Send "new" early, in case this command takes
4096                        a long time to finish, so that we'll be ready
4097                        for the next challenge. */
4098                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4099                     Reset(TRUE, TRUE);
4100                 }
4101 #endif /*ZIPPY*/
4102                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4103                 continue;
4104             }
4105
4106             if (looking_at(buf, &i, "Removing game * from observation") ||
4107                 looking_at(buf, &i, "no longer observing game *") ||
4108                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4109                 if (gameMode == IcsObserving &&
4110                     atoi(star_match[0]) == ics_gamenum)
4111                   {
4112                       /* icsEngineAnalyze */
4113                       if (appData.icsEngineAnalyze) {
4114                             ExitAnalyzeMode();
4115                             ModeHighlight();
4116                       }
4117                       StopClocks();
4118                       gameMode = IcsIdle;
4119                       ics_gamenum = -1;
4120                       ics_user_moved = FALSE;
4121                   }
4122                 continue;
4123             }
4124
4125             if (looking_at(buf, &i, "no longer examining game *")) {
4126                 if (gameMode == IcsExamining &&
4127                     atoi(star_match[0]) == ics_gamenum)
4128                   {
4129                       gameMode = IcsIdle;
4130                       ics_gamenum = -1;
4131                       ics_user_moved = FALSE;
4132                   }
4133                 continue;
4134             }
4135
4136             /* Advance leftover_start past any newlines we find,
4137                so only partial lines can get reparsed */
4138             if (looking_at(buf, &i, "\n")) {
4139                 prevColor = curColor;
4140                 if (curColor != ColorNormal) {
4141                     if (oldi > next_out) {
4142                         SendToPlayer(&buf[next_out], oldi - next_out);
4143                         next_out = oldi;
4144                     }
4145                     Colorize(ColorNormal, FALSE);
4146                     curColor = ColorNormal;
4147                 }
4148                 if (started == STARTED_BOARD) {
4149                     started = STARTED_NONE;
4150                     parse[parse_pos] = NULLCHAR;
4151                     ParseBoard12(parse);
4152                     ics_user_moved = 0;
4153
4154                     /* Send premove here */
4155                     if (appData.premove) {
4156                       char str[MSG_SIZ];
4157                       if (currentMove == 0 &&
4158                           gameMode == IcsPlayingWhite &&
4159                           appData.premoveWhite) {
4160                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4161                         if (appData.debugMode)
4162                           fprintf(debugFP, "Sending premove:\n");
4163                         SendToICS(str);
4164                       } else if (currentMove == 1 &&
4165                                  gameMode == IcsPlayingBlack &&
4166                                  appData.premoveBlack) {
4167                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4168                         if (appData.debugMode)
4169                           fprintf(debugFP, "Sending premove:\n");
4170                         SendToICS(str);
4171                       } else if (gotPremove) {
4172                         int oldFMM = forwardMostMove;
4173                         gotPremove = 0;
4174                         ClearPremoveHighlights();
4175                         if (appData.debugMode)
4176                           fprintf(debugFP, "Sending premove:\n");
4177                           UserMoveEvent(premoveFromX, premoveFromY,
4178                                         premoveToX, premoveToY,
4179                                         premovePromoChar);
4180                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4181                           if(moveList[oldFMM-1][1] != '@')
4182                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4183                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4184                           else // (drop)
4185                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4186                         }
4187                       }
4188                     }
4189
4190                     /* Usually suppress following prompt */
4191                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4192                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4193                         if (looking_at(buf, &i, "*% ")) {
4194                             savingComment = FALSE;
4195                             suppressKibitz = 0;
4196                         }
4197                     }
4198                     next_out = i;
4199                 } else if (started == STARTED_HOLDINGS) {
4200                     int gamenum;
4201                     char new_piece[MSG_SIZ];
4202                     started = STARTED_NONE;
4203                     parse[parse_pos] = NULLCHAR;
4204                     if (appData.debugMode)
4205                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4206                                                         parse, currentMove);
4207                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4208                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4209                         if (gameInfo.variant == VariantNormal) {
4210                           /* [HGM] We seem to switch variant during a game!
4211                            * Presumably no holdings were displayed, so we have
4212                            * to move the position two files to the right to
4213                            * create room for them!
4214                            */
4215                           VariantClass newVariant;
4216                           switch(gameInfo.boardWidth) { // base guess on board width
4217                                 case 9:  newVariant = VariantShogi; break;
4218                                 case 10: newVariant = VariantGreat; break;
4219                                 default: newVariant = VariantCrazyhouse; break;
4220                           }
4221                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4222                           /* Get a move list just to see the header, which
4223                              will tell us whether this is really bug or zh */
4224                           if (ics_getting_history == H_FALSE) {
4225                             ics_getting_history = H_REQUESTED;
4226                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4227                             SendToICS(str);
4228                           }
4229                         }
4230                         new_piece[0] = NULLCHAR;
4231                         sscanf(parse, "game %d white [%s black [%s <- %s",
4232                                &gamenum, white_holding, black_holding,
4233                                new_piece);
4234                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4235                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4236                         /* [HGM] copy holdings to board holdings area */
4237                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4238                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4239                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4240 #if ZIPPY
4241                         if (appData.zippyPlay && first.initDone) {
4242                             ZippyHoldings(white_holding, black_holding,
4243                                           new_piece);
4244                         }
4245 #endif /*ZIPPY*/
4246                         if (tinyLayout || smallLayout) {
4247                             char wh[16], bh[16];
4248                             PackHolding(wh, white_holding);
4249                             PackHolding(bh, black_holding);
4250                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4251                                     gameInfo.white, gameInfo.black);
4252                         } else {
4253                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4254                                     gameInfo.white, white_holding, _("vs."),
4255                                     gameInfo.black, black_holding);
4256                         }
4257                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4258                         DrawPosition(FALSE, boards[currentMove]);
4259                         DisplayTitle(str);
4260                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4261                         sscanf(parse, "game %d white [%s black [%s <- %s",
4262                                &gamenum, white_holding, black_holding,
4263                                new_piece);
4264                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4265                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4266                         /* [HGM] copy holdings to partner-board holdings area */
4267                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4268                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4269                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4270                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4271                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4272                       }
4273                     }
4274                     /* Suppress following prompt */
4275                     if (looking_at(buf, &i, "*% ")) {
4276                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4277                         savingComment = FALSE;
4278                         suppressKibitz = 0;
4279                     }
4280                     next_out = i;
4281                 }
4282                 continue;
4283             }
4284
4285             i++;                /* skip unparsed character and loop back */
4286         }
4287
4288         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4289 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4290 //          SendToPlayer(&buf[next_out], i - next_out);
4291             started != STARTED_HOLDINGS && leftover_start > next_out) {
4292             SendToPlayer(&buf[next_out], leftover_start - next_out);
4293             next_out = i;
4294         }
4295
4296         leftover_len = buf_len - leftover_start;
4297         /* if buffer ends with something we couldn't parse,
4298            reparse it after appending the next read */
4299
4300     } else if (count == 0) {
4301         RemoveInputSource(isr);
4302         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4303     } else {
4304         DisplayFatalError(_("Error reading from ICS"), error, 1);
4305     }
4306 }
4307
4308
4309 /* Board style 12 looks like this:
4310
4311    <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
4312
4313  * The "<12> " is stripped before it gets to this routine.  The two
4314  * trailing 0's (flip state and clock ticking) are later addition, and
4315  * some chess servers may not have them, or may have only the first.
4316  * Additional trailing fields may be added in the future.
4317  */
4318
4319 #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"
4320
4321 #define RELATION_OBSERVING_PLAYED    0
4322 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4323 #define RELATION_PLAYING_MYMOVE      1
4324 #define RELATION_PLAYING_NOTMYMOVE  -1
4325 #define RELATION_EXAMINING           2
4326 #define RELATION_ISOLATED_BOARD     -3
4327 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4328
4329 void
4330 ParseBoard12 (char *string)
4331 {
4332 #if ZIPPY
4333     int i, takeback;
4334     char *bookHit = NULL; // [HGM] book
4335 #endif
4336     GameMode newGameMode;
4337     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4338     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4339     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4340     char to_play, board_chars[200];
4341     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4342     char black[32], white[32];
4343     Board board;
4344     int prevMove = currentMove;
4345     int ticking = 2;
4346     ChessMove moveType;
4347     int fromX, fromY, toX, toY;
4348     char promoChar;
4349     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4350     Boolean weird = FALSE, reqFlag = FALSE;
4351
4352     fromX = fromY = toX = toY = -1;
4353
4354     newGame = FALSE;
4355
4356     if (appData.debugMode)
4357       fprintf(debugFP, "Parsing board: %s\n", string);
4358
4359     move_str[0] = NULLCHAR;
4360     elapsed_time[0] = NULLCHAR;
4361     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4362         int  i = 0, j;
4363         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4364             if(string[i] == ' ') { ranks++; files = 0; }
4365             else files++;
4366             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4367             i++;
4368         }
4369         for(j = 0; j <i; j++) board_chars[j] = string[j];
4370         board_chars[i] = '\0';
4371         string += i + 1;
4372     }
4373     n = sscanf(string, PATTERN, &to_play, &double_push,
4374                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4375                &gamenum, white, black, &relation, &basetime, &increment,
4376                &white_stren, &black_stren, &white_time, &black_time,
4377                &moveNum, str, elapsed_time, move_str, &ics_flip,
4378                &ticking);
4379
4380     if (n < 21) {
4381         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4382         DisplayError(str, 0);
4383         return;
4384     }
4385
4386     /* Convert the move number to internal form */
4387     moveNum = (moveNum - 1) * 2;
4388     if (to_play == 'B') moveNum++;
4389     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4390       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4391                         0, 1);
4392       return;
4393     }
4394
4395     switch (relation) {
4396       case RELATION_OBSERVING_PLAYED:
4397       case RELATION_OBSERVING_STATIC:
4398         if (gamenum == -1) {
4399             /* Old ICC buglet */
4400             relation = RELATION_OBSERVING_STATIC;
4401         }
4402         newGameMode = IcsObserving;
4403         break;
4404       case RELATION_PLAYING_MYMOVE:
4405       case RELATION_PLAYING_NOTMYMOVE:
4406         newGameMode =
4407           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4408             IcsPlayingWhite : IcsPlayingBlack;
4409         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4410         break;
4411       case RELATION_EXAMINING:
4412         newGameMode = IcsExamining;
4413         break;
4414       case RELATION_ISOLATED_BOARD:
4415       default:
4416         /* Just display this board.  If user was doing something else,
4417            we will forget about it until the next board comes. */
4418         newGameMode = IcsIdle;
4419         break;
4420       case RELATION_STARTING_POSITION:
4421         newGameMode = gameMode;
4422         break;
4423     }
4424
4425     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4426         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4427          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4428       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4429       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4430       static int lastBgGame = -1;
4431       char *toSqr;
4432       for (k = 0; k < ranks; k++) {
4433         for (j = 0; j < files; j++)
4434           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4435         if(gameInfo.holdingsWidth > 1) {
4436              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4437              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4438         }
4439       }
4440       CopyBoard(partnerBoard, board);
4441       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4442         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4443         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4444       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4445       if(toSqr = strchr(str, '-')) {
4446         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4447         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4448       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4449       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4450       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4451       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4452       if(twoBoards) {
4453           DisplayWhiteClock(white_time*fac, to_play == 'W');
4454           DisplayBlackClock(black_time*fac, to_play != 'W');
4455           activePartner = to_play;
4456           if(gamenum != lastBgGame) {
4457               char buf[MSG_SIZ];
4458               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4459               DisplayTitle(buf);
4460           }
4461           lastBgGame = gamenum;
4462           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4463                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4464       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4465                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4466       if(!twoBoards) DisplayMessage(partnerStatus, "");
4467         partnerBoardValid = TRUE;
4468       return;
4469     }
4470
4471     if(appData.dualBoard && appData.bgObserve) {
4472         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4473             SendToICS(ics_prefix), SendToICS("pobserve\n");
4474         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4475             char buf[MSG_SIZ];
4476             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4477             SendToICS(buf);
4478         }
4479     }
4480
4481     /* Modify behavior for initial board display on move listing
4482        of wild games.
4483        */
4484     switch (ics_getting_history) {
4485       case H_FALSE:
4486       case H_REQUESTED:
4487         break;
4488       case H_GOT_REQ_HEADER:
4489       case H_GOT_UNREQ_HEADER:
4490         /* This is the initial position of the current game */
4491         gamenum = ics_gamenum;
4492         moveNum = 0;            /* old ICS bug workaround */
4493         if (to_play == 'B') {
4494           startedFromSetupPosition = TRUE;
4495           blackPlaysFirst = TRUE;
4496           moveNum = 1;
4497           if (forwardMostMove == 0) forwardMostMove = 1;
4498           if (backwardMostMove == 0) backwardMostMove = 1;
4499           if (currentMove == 0) currentMove = 1;
4500         }
4501         newGameMode = gameMode;
4502         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4503         break;
4504       case H_GOT_UNWANTED_HEADER:
4505         /* This is an initial board that we don't want */
4506         return;
4507       case H_GETTING_MOVES:
4508         /* Should not happen */
4509         DisplayError(_("Error gathering move list: extra board"), 0);
4510         ics_getting_history = H_FALSE;
4511         return;
4512     }
4513
4514    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4515                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4516                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4517      /* [HGM] We seem to have switched variant unexpectedly
4518       * Try to guess new variant from board size
4519       */
4520           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4521           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4522           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4523           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4524           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4525           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4526           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4527           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4528           /* Get a move list just to see the header, which
4529              will tell us whether this is really bug or zh */
4530           if (ics_getting_history == H_FALSE) {
4531             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4532             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4533             SendToICS(str);
4534           }
4535     }
4536
4537     /* Take action if this is the first board of a new game, or of a
4538        different game than is currently being displayed.  */
4539     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4540         relation == RELATION_ISOLATED_BOARD) {
4541
4542         /* Forget the old game and get the history (if any) of the new one */
4543         if (gameMode != BeginningOfGame) {
4544           Reset(TRUE, TRUE);
4545         }
4546         newGame = TRUE;
4547         if (appData.autoRaiseBoard) BoardToTop();
4548         prevMove = -3;
4549         if (gamenum == -1) {
4550             newGameMode = IcsIdle;
4551         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4552                    appData.getMoveList && !reqFlag) {
4553             /* Need to get game history */
4554             ics_getting_history = H_REQUESTED;
4555             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4556             SendToICS(str);
4557         }
4558
4559         /* Initially flip the board to have black on the bottom if playing
4560            black or if the ICS flip flag is set, but let the user change
4561            it with the Flip View button. */
4562         flipView = appData.autoFlipView ?
4563           (newGameMode == IcsPlayingBlack) || ics_flip :
4564           appData.flipView;
4565
4566         /* Done with values from previous mode; copy in new ones */
4567         gameMode = newGameMode;
4568         ModeHighlight();
4569         ics_gamenum = gamenum;
4570         if (gamenum == gs_gamenum) {
4571             int klen = strlen(gs_kind);
4572             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4573             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4574             gameInfo.event = StrSave(str);
4575         } else {
4576             gameInfo.event = StrSave("ICS game");
4577         }
4578         gameInfo.site = StrSave(appData.icsHost);
4579         gameInfo.date = PGNDate();
4580         gameInfo.round = StrSave("-");
4581         gameInfo.white = StrSave(white);
4582         gameInfo.black = StrSave(black);
4583         timeControl = basetime * 60 * 1000;
4584         timeControl_2 = 0;
4585         timeIncrement = increment * 1000;
4586         movesPerSession = 0;
4587         gameInfo.timeControl = TimeControlTagValue();
4588         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4589   if (appData.debugMode) {
4590     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4591     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4592     setbuf(debugFP, NULL);
4593   }
4594
4595         gameInfo.outOfBook = NULL;
4596
4597         /* Do we have the ratings? */
4598         if (strcmp(player1Name, white) == 0 &&
4599             strcmp(player2Name, black) == 0) {
4600             if (appData.debugMode)
4601               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4602                       player1Rating, player2Rating);
4603             gameInfo.whiteRating = player1Rating;
4604             gameInfo.blackRating = player2Rating;
4605         } else if (strcmp(player2Name, white) == 0 &&
4606                    strcmp(player1Name, black) == 0) {
4607             if (appData.debugMode)
4608               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4609                       player2Rating, player1Rating);
4610             gameInfo.whiteRating = player2Rating;
4611             gameInfo.blackRating = player1Rating;
4612         }
4613         player1Name[0] = player2Name[0] = NULLCHAR;
4614
4615         /* Silence shouts if requested */
4616         if (appData.quietPlay &&
4617             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4618             SendToICS(ics_prefix);
4619             SendToICS("set shout 0\n");
4620         }
4621     }
4622
4623     /* Deal with midgame name changes */
4624     if (!newGame) {
4625         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4626             if (gameInfo.white) free(gameInfo.white);
4627             gameInfo.white = StrSave(white);
4628         }
4629         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4630             if (gameInfo.black) free(gameInfo.black);
4631             gameInfo.black = StrSave(black);
4632         }
4633     }
4634
4635     /* Throw away game result if anything actually changes in examine mode */
4636     if (gameMode == IcsExamining && !newGame) {
4637         gameInfo.result = GameUnfinished;
4638         if (gameInfo.resultDetails != NULL) {
4639             free(gameInfo.resultDetails);
4640             gameInfo.resultDetails = NULL;
4641         }
4642     }
4643
4644     /* In pausing && IcsExamining mode, we ignore boards coming
4645        in if they are in a different variation than we are. */
4646     if (pauseExamInvalid) return;
4647     if (pausing && gameMode == IcsExamining) {
4648         if (moveNum <= pauseExamForwardMostMove) {
4649             pauseExamInvalid = TRUE;
4650             forwardMostMove = pauseExamForwardMostMove;
4651             return;
4652         }
4653     }
4654
4655   if (appData.debugMode) {
4656     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4657   }
4658     /* Parse the board */
4659     for (k = 0; k < ranks; k++) {
4660       for (j = 0; j < files; j++)
4661         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4662       if(gameInfo.holdingsWidth > 1) {
4663            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4664            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4665       }
4666     }
4667     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4668       board[5][BOARD_RGHT+1] = WhiteAngel;
4669       board[6][BOARD_RGHT+1] = WhiteMarshall;
4670       board[1][0] = BlackMarshall;
4671       board[2][0] = BlackAngel;
4672       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4673     }
4674     CopyBoard(boards[moveNum], board);
4675     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4676     if (moveNum == 0) {
4677         startedFromSetupPosition =
4678           !CompareBoards(board, initialPosition);
4679         if(startedFromSetupPosition)
4680             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4681     }
4682
4683     /* [HGM] Set castling rights. Take the outermost Rooks,
4684        to make it also work for FRC opening positions. Note that board12
4685        is really defective for later FRC positions, as it has no way to
4686        indicate which Rook can castle if they are on the same side of King.
4687        For the initial position we grant rights to the outermost Rooks,
4688        and remember thos rights, and we then copy them on positions
4689        later in an FRC game. This means WB might not recognize castlings with
4690        Rooks that have moved back to their original position as illegal,
4691        but in ICS mode that is not its job anyway.
4692     */
4693     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4694     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4695
4696         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4697             if(board[0][i] == WhiteRook) j = i;
4698         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4699         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4700             if(board[0][i] == WhiteRook) j = i;
4701         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4702         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4703             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4704         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4705         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4706             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4707         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4708
4709         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4710         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4711         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4712             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4713         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4714             if(board[BOARD_HEIGHT-1][k] == bKing)
4715                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4716         if(gameInfo.variant == VariantTwoKings) {
4717             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4718             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4719             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4720         }
4721     } else { int r;
4722         r = boards[moveNum][CASTLING][0] = initialRights[0];
4723         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4724         r = boards[moveNum][CASTLING][1] = initialRights[1];
4725         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4726         r = boards[moveNum][CASTLING][3] = initialRights[3];
4727         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4728         r = boards[moveNum][CASTLING][4] = initialRights[4];
4729         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4730         /* wildcastle kludge: always assume King has rights */
4731         r = boards[moveNum][CASTLING][2] = initialRights[2];
4732         r = boards[moveNum][CASTLING][5] = initialRights[5];
4733     }
4734     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4735     boards[moveNum][EP_STATUS] = EP_NONE;
4736     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4737     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4738     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4739
4740
4741     if (ics_getting_history == H_GOT_REQ_HEADER ||
4742         ics_getting_history == H_GOT_UNREQ_HEADER) {
4743         /* This was an initial position from a move list, not
4744            the current position */
4745         return;
4746     }
4747
4748     /* Update currentMove and known move number limits */
4749     newMove = newGame || moveNum > forwardMostMove;
4750
4751     if (newGame) {
4752         forwardMostMove = backwardMostMove = currentMove = moveNum;
4753         if (gameMode == IcsExamining && moveNum == 0) {
4754           /* Workaround for ICS limitation: we are not told the wild
4755              type when starting to examine a game.  But if we ask for
4756              the move list, the move list header will tell us */
4757             ics_getting_history = H_REQUESTED;
4758             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4759             SendToICS(str);
4760         }
4761     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4762                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4763 #if ZIPPY
4764         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4765         /* [HGM] applied this also to an engine that is silently watching        */
4766         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4767             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4768             gameInfo.variant == currentlyInitializedVariant) {
4769           takeback = forwardMostMove - moveNum;
4770           for (i = 0; i < takeback; i++) {
4771             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4772             SendToProgram("undo\n", &first);
4773           }
4774         }
4775 #endif
4776
4777         forwardMostMove = moveNum;
4778         if (!pausing || currentMove > forwardMostMove)
4779           currentMove = forwardMostMove;
4780     } else {
4781         /* New part of history that is not contiguous with old part */
4782         if (pausing && gameMode == IcsExamining) {
4783             pauseExamInvalid = TRUE;
4784             forwardMostMove = pauseExamForwardMostMove;
4785             return;
4786         }
4787         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4788 #if ZIPPY
4789             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4790                 // [HGM] when we will receive the move list we now request, it will be
4791                 // fed to the engine from the first move on. So if the engine is not
4792                 // in the initial position now, bring it there.
4793                 InitChessProgram(&first, 0);
4794             }
4795 #endif
4796             ics_getting_history = H_REQUESTED;
4797             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4798             SendToICS(str);
4799         }
4800         forwardMostMove = backwardMostMove = currentMove = moveNum;
4801     }
4802
4803     /* Update the clocks */
4804     if (strchr(elapsed_time, '.')) {
4805       /* Time is in ms */
4806       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4807       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4808     } else {
4809       /* Time is in seconds */
4810       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4811       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4812     }
4813
4814
4815 #if ZIPPY
4816     if (appData.zippyPlay && newGame &&
4817         gameMode != IcsObserving && gameMode != IcsIdle &&
4818         gameMode != IcsExamining)
4819       ZippyFirstBoard(moveNum, basetime, increment);
4820 #endif
4821
4822     /* Put the move on the move list, first converting
4823        to canonical algebraic form. */
4824     if (moveNum > 0) {
4825   if (appData.debugMode) {
4826     int f = forwardMostMove;
4827     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4828             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4829             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4830     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4831     fprintf(debugFP, "moveNum = %d\n", moveNum);
4832     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4833     setbuf(debugFP, NULL);
4834   }
4835         if (moveNum <= backwardMostMove) {
4836             /* We don't know what the board looked like before
4837                this move.  Punt. */
4838           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4839             strcat(parseList[moveNum - 1], " ");
4840             strcat(parseList[moveNum - 1], elapsed_time);
4841             moveList[moveNum - 1][0] = NULLCHAR;
4842         } else if (strcmp(move_str, "none") == 0) {
4843             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4844             /* Again, we don't know what the board looked like;
4845                this is really the start of the game. */
4846             parseList[moveNum - 1][0] = NULLCHAR;
4847             moveList[moveNum - 1][0] = NULLCHAR;
4848             backwardMostMove = moveNum;
4849             startedFromSetupPosition = TRUE;
4850             fromX = fromY = toX = toY = -1;
4851         } else {
4852           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4853           //                 So we parse the long-algebraic move string in stead of the SAN move
4854           int valid; char buf[MSG_SIZ], *prom;
4855
4856           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4857                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4858           // str looks something like "Q/a1-a2"; kill the slash
4859           if(str[1] == '/')
4860             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4861           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4862           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4863                 strcat(buf, prom); // long move lacks promo specification!
4864           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4865                 if(appData.debugMode)
4866                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4867                 safeStrCpy(move_str, buf, MSG_SIZ);
4868           }
4869           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4870                                 &fromX, &fromY, &toX, &toY, &promoChar)
4871                || ParseOneMove(buf, moveNum - 1, &moveType,
4872                                 &fromX, &fromY, &toX, &toY, &promoChar);
4873           // end of long SAN patch
4874           if (valid) {
4875             (void) CoordsToAlgebraic(boards[moveNum - 1],
4876                                      PosFlags(moveNum - 1),
4877                                      fromY, fromX, toY, toX, promoChar,
4878                                      parseList[moveNum-1]);
4879             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4880               case MT_NONE:
4881               case MT_STALEMATE:
4882               default:
4883                 break;
4884               case MT_CHECK:
4885                 if(!IS_SHOGI(gameInfo.variant))
4886                     strcat(parseList[moveNum - 1], "+");
4887                 break;
4888               case MT_CHECKMATE:
4889               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4890                 strcat(parseList[moveNum - 1], "#");
4891                 break;
4892             }
4893             strcat(parseList[moveNum - 1], " ");
4894             strcat(parseList[moveNum - 1], elapsed_time);
4895             /* currentMoveString is set as a side-effect of ParseOneMove */
4896             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4897             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4898             strcat(moveList[moveNum - 1], "\n");
4899
4900             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4901                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4902               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4903                 ChessSquare old, new = boards[moveNum][k][j];
4904                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4905                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4906                   if(old == new) continue;
4907                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4908                   else if(new == WhiteWazir || new == BlackWazir) {
4909                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4910                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4911                       else boards[moveNum][k][j] = old; // preserve type of Gold
4912                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4913                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4914               }
4915           } else {
4916             /* Move from ICS was illegal!?  Punt. */
4917             if (appData.debugMode) {
4918               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4919               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4920             }
4921             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4922             strcat(parseList[moveNum - 1], " ");
4923             strcat(parseList[moveNum - 1], elapsed_time);
4924             moveList[moveNum - 1][0] = NULLCHAR;
4925             fromX = fromY = toX = toY = -1;
4926           }
4927         }
4928   if (appData.debugMode) {
4929     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4930     setbuf(debugFP, NULL);
4931   }
4932
4933 #if ZIPPY
4934         /* Send move to chess program (BEFORE animating it). */
4935         if (appData.zippyPlay && !newGame && newMove &&
4936            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4937
4938             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4939                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4940                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4941                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4942                             move_str);
4943                     DisplayError(str, 0);
4944                 } else {
4945                     if (first.sendTime) {
4946                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4947                     }
4948                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4949                     if (firstMove && !bookHit) {
4950                         firstMove = FALSE;
4951                         if (first.useColors) {
4952                           SendToProgram(gameMode == IcsPlayingWhite ?
4953                                         "white\ngo\n" :
4954                                         "black\ngo\n", &first);
4955                         } else {
4956                           SendToProgram("go\n", &first);
4957                         }
4958                         first.maybeThinking = TRUE;
4959                     }
4960                 }
4961             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4962               if (moveList[moveNum - 1][0] == NULLCHAR) {
4963                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4964                 DisplayError(str, 0);
4965               } else {
4966                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4967                 SendMoveToProgram(moveNum - 1, &first);
4968               }
4969             }
4970         }
4971 #endif
4972     }
4973
4974     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4975         /* If move comes from a remote source, animate it.  If it
4976            isn't remote, it will have already been animated. */
4977         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4978             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4979         }
4980         if (!pausing && appData.highlightLastMove) {
4981             SetHighlights(fromX, fromY, toX, toY);
4982         }
4983     }
4984
4985     /* Start the clocks */
4986     whiteFlag = blackFlag = FALSE;
4987     appData.clockMode = !(basetime == 0 && increment == 0);
4988     if (ticking == 0) {
4989       ics_clock_paused = TRUE;
4990       StopClocks();
4991     } else if (ticking == 1) {
4992       ics_clock_paused = FALSE;
4993     }
4994     if (gameMode == IcsIdle ||
4995         relation == RELATION_OBSERVING_STATIC ||
4996         relation == RELATION_EXAMINING ||
4997         ics_clock_paused)
4998       DisplayBothClocks();
4999     else
5000       StartClocks();
5001
5002     /* Display opponents and material strengths */
5003     if (gameInfo.variant != VariantBughouse &&
5004         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
5005         if (tinyLayout || smallLayout) {
5006             if(gameInfo.variant == VariantNormal)
5007               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5008                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5009                     basetime, increment);
5010             else
5011               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5012                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5013                     basetime, increment, (int) gameInfo.variant);
5014         } else {
5015             if(gameInfo.variant == VariantNormal)
5016               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5017                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5018                     basetime, increment);
5019             else
5020               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5021                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5022                     basetime, increment, VariantName(gameInfo.variant));
5023         }
5024         DisplayTitle(str);
5025   if (appData.debugMode) {
5026     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5027   }
5028     }
5029
5030
5031     /* Display the board */
5032     if (!pausing && !appData.noGUI) {
5033
5034       if (appData.premove)
5035           if (!gotPremove ||
5036              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5037              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5038               ClearPremoveHighlights();
5039
5040       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5041         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5042       DrawPosition(j, boards[currentMove]);
5043
5044       DisplayMove(moveNum - 1);
5045       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5046             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5047               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5048         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5049       }
5050     }
5051
5052     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5053 #if ZIPPY
5054     if(bookHit) { // [HGM] book: simulate book reply
5055         static char bookMove[MSG_SIZ]; // a bit generous?
5056
5057         programStats.nodes = programStats.depth = programStats.time =
5058         programStats.score = programStats.got_only_move = 0;
5059         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5060
5061         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5062         strcat(bookMove, bookHit);
5063         HandleMachineMove(bookMove, &first);
5064     }
5065 #endif
5066 }
5067
5068 void
5069 GetMoveListEvent ()
5070 {
5071     char buf[MSG_SIZ];
5072     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5073         ics_getting_history = H_REQUESTED;
5074         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5075         SendToICS(buf);
5076     }
5077 }
5078
5079 void
5080 SendToBoth (char *msg)
5081 {   // to make it easy to keep two engines in step in dual analysis
5082     SendToProgram(msg, &first);
5083     if(second.analyzing) SendToProgram(msg, &second);
5084 }
5085
5086 void
5087 AnalysisPeriodicEvent (int force)
5088 {
5089     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5090          && !force) || !appData.periodicUpdates)
5091       return;
5092
5093     /* Send . command to Crafty to collect stats */
5094     SendToBoth(".\n");
5095
5096     /* Don't send another until we get a response (this makes
5097        us stop sending to old Crafty's which don't understand
5098        the "." command (sending illegal cmds resets node count & time,
5099        which looks bad)) */
5100     programStats.ok_to_send = 0;
5101 }
5102
5103 void
5104 ics_update_width (int new_width)
5105 {
5106         ics_printf("set width %d\n", new_width);
5107 }
5108
5109 void
5110 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5111 {
5112     char buf[MSG_SIZ];
5113
5114     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5115         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5116             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5117             SendToProgram(buf, cps);
5118             return;
5119         }
5120         // null move in variant where engine does not understand it (for analysis purposes)
5121         SendBoard(cps, moveNum + 1); // send position after move in stead.
5122         return;
5123     }
5124     if (cps->useUsermove) {
5125       SendToProgram("usermove ", cps);
5126     }
5127     if (cps->useSAN) {
5128       char *space;
5129       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5130         int len = space - parseList[moveNum];
5131         memcpy(buf, parseList[moveNum], len);
5132         buf[len++] = '\n';
5133         buf[len] = NULLCHAR;
5134       } else {
5135         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5136       }
5137       SendToProgram(buf, cps);
5138     } else {
5139       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5140         AlphaRank(moveList[moveNum], 4);
5141         SendToProgram(moveList[moveNum], cps);
5142         AlphaRank(moveList[moveNum], 4); // and back
5143       } else
5144       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5145        * the engine. It would be nice to have a better way to identify castle
5146        * moves here. */
5147       if(appData.fischerCastling && cps->useOOCastle) {
5148         int fromX = moveList[moveNum][0] - AAA;
5149         int fromY = moveList[moveNum][1] - ONE;
5150         int toX = moveList[moveNum][2] - AAA;
5151         int toY = moveList[moveNum][3] - ONE;
5152         if((boards[moveNum][fromY][fromX] == WhiteKing
5153             && boards[moveNum][toY][toX] == WhiteRook)
5154            || (boards[moveNum][fromY][fromX] == BlackKing
5155                && boards[moveNum][toY][toX] == BlackRook)) {
5156           if(toX > fromX) SendToProgram("O-O\n", cps);
5157           else SendToProgram("O-O-O\n", cps);
5158         }
5159         else SendToProgram(moveList[moveNum], cps);
5160       } else
5161       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5162         char *m = moveList[moveNum];
5163         static char c[2];
5164         *c = m[7]; // promoChar
5165         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
5166           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5167                                                m[2], m[3] - '0',
5168                                                m[5], m[6] - '0',
5169                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5170         else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
5171           *c = m[9];
5172           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
5173                                                m[7], m[8] - '0',
5174                                                m[7], m[8] - '0',
5175                                                m[5], m[6] - '0',
5176                                                m[5], m[6] - '0',
5177                                                m[2], m[3] - '0', c);
5178         } else
5179           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5180                                                m[5], m[6] - '0',
5181                                                m[5], m[6] - '0',
5182                                                m[2], m[3] - '0', c);
5183           SendToProgram(buf, cps);
5184       } else
5185       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5186         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5187           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5188           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5189                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5190         } else
5191           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5192                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5193         SendToProgram(buf, cps);
5194       }
5195       else SendToProgram(moveList[moveNum], cps);
5196       /* End of additions by Tord */
5197     }
5198
5199     /* [HGM] setting up the opening has brought engine in force mode! */
5200     /*       Send 'go' if we are in a mode where machine should play. */
5201     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5202         (gameMode == TwoMachinesPlay   ||
5203 #if ZIPPY
5204          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5205 #endif
5206          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5207         SendToProgram("go\n", cps);
5208   if (appData.debugMode) {
5209     fprintf(debugFP, "(extra)\n");
5210   }
5211     }
5212     setboardSpoiledMachineBlack = 0;
5213 }
5214
5215 void
5216 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5217 {
5218     char user_move[MSG_SIZ];
5219     char suffix[4];
5220
5221     if(gameInfo.variant == VariantSChess && promoChar) {
5222         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5223         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5224     } else suffix[0] = NULLCHAR;
5225
5226     switch (moveType) {
5227       default:
5228         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5229                 (int)moveType, fromX, fromY, toX, toY);
5230         DisplayError(user_move + strlen("say "), 0);
5231         break;
5232       case WhiteKingSideCastle:
5233       case BlackKingSideCastle:
5234       case WhiteQueenSideCastleWild:
5235       case BlackQueenSideCastleWild:
5236       /* PUSH Fabien */
5237       case WhiteHSideCastleFR:
5238       case BlackHSideCastleFR:
5239       /* POP Fabien */
5240         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5241         break;
5242       case WhiteQueenSideCastle:
5243       case BlackQueenSideCastle:
5244       case WhiteKingSideCastleWild:
5245       case BlackKingSideCastleWild:
5246       /* PUSH Fabien */
5247       case WhiteASideCastleFR:
5248       case BlackASideCastleFR:
5249       /* POP Fabien */
5250         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5251         break;
5252       case WhiteNonPromotion:
5253       case BlackNonPromotion:
5254         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5255         break;
5256       case WhitePromotion:
5257       case BlackPromotion:
5258         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5259            gameInfo.variant == VariantMakruk)
5260           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5261                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5262                 PieceToChar(WhiteFerz));
5263         else if(gameInfo.variant == VariantGreat)
5264           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5265                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5266                 PieceToChar(WhiteMan));
5267         else
5268           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5269                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5270                 promoChar);
5271         break;
5272       case WhiteDrop:
5273       case BlackDrop:
5274       drop:
5275         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5276                  ToUpper(PieceToChar((ChessSquare) fromX)),
5277                  AAA + toX, ONE + toY);
5278         break;
5279       case IllegalMove:  /* could be a variant we don't quite understand */
5280         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5281       case NormalMove:
5282       case WhiteCapturesEnPassant:
5283       case BlackCapturesEnPassant:
5284         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5285                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5286         break;
5287     }
5288     SendToICS(user_move);
5289     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5290         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5291 }
5292
5293 void
5294 UploadGameEvent ()
5295 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5296     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5297     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5298     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5299       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5300       return;
5301     }
5302     if(gameMode != IcsExamining) { // is this ever not the case?
5303         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5304
5305         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5306           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5307         } else { // on FICS we must first go to general examine mode
5308           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5309         }
5310         if(gameInfo.variant != VariantNormal) {
5311             // try figure out wild number, as xboard names are not always valid on ICS
5312             for(i=1; i<=36; i++) {
5313               snprintf(buf, MSG_SIZ, "wild/%d", i);
5314                 if(StringToVariant(buf) == gameInfo.variant) break;
5315             }
5316             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5317             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5318             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5319         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5320         SendToICS(ics_prefix);
5321         SendToICS(buf);
5322         if(startedFromSetupPosition || backwardMostMove != 0) {
5323           fen = PositionToFEN(backwardMostMove, NULL, 1);
5324           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5325             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5326             SendToICS(buf);
5327           } else { // FICS: everything has to set by separate bsetup commands
5328             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5329             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5330             SendToICS(buf);
5331             if(!WhiteOnMove(backwardMostMove)) {
5332                 SendToICS("bsetup tomove black\n");
5333             }
5334             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5335             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5336             SendToICS(buf);
5337             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5338             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5339             SendToICS(buf);
5340             i = boards[backwardMostMove][EP_STATUS];
5341             if(i >= 0) { // set e.p.
5342               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5343                 SendToICS(buf);
5344             }
5345             bsetup++;
5346           }
5347         }
5348       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5349             SendToICS("bsetup done\n"); // switch to normal examining.
5350     }
5351     for(i = backwardMostMove; i<last; i++) {
5352         char buf[20];
5353         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5354         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5355             int len = strlen(moveList[i]);
5356             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5357             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5358         }
5359         SendToICS(buf);
5360     }
5361     SendToICS(ics_prefix);
5362     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5363 }
5364
5365 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5366 int legNr = 1;
5367
5368 void
5369 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5370 {
5371     if (rf == DROP_RANK) {
5372       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5373       sprintf(move, "%c@%c%c\n",
5374                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5375     } else {
5376         if (promoChar == 'x' || promoChar == NULLCHAR) {
5377           sprintf(move, "%c%c%c%c\n",
5378                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5379           if(killX >= 0 && killY >= 0) {
5380             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5381             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
5382           }
5383         } else {
5384             sprintf(move, "%c%c%c%c%c\n",
5385                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5386           if(killX >= 0 && killY >= 0) {
5387             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5388             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
5389           }
5390         }
5391     }
5392 }
5393
5394 void
5395 ProcessICSInitScript (FILE *f)
5396 {
5397     char buf[MSG_SIZ];
5398
5399     while (fgets(buf, MSG_SIZ, f)) {
5400         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5401     }
5402
5403     fclose(f);
5404 }
5405
5406
5407 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5408 int dragging;
5409 static ClickType lastClickType;
5410
5411 int
5412 PieceInString (char *s, ChessSquare piece)
5413 {
5414   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5415   while((p = strchr(s, ID))) {
5416     if(!suffix || p[1] == suffix) return TRUE;
5417     s = p;
5418   }
5419   return FALSE;
5420 }
5421
5422 int
5423 Partner (ChessSquare *p)
5424 { // change piece into promotion partner if one shogi-promotes to the other
5425   ChessSquare partner = promoPartner[*p];
5426   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5427   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5428   *p = partner;
5429   return 1;
5430 }
5431
5432 void
5433 Sweep (int step)
5434 {
5435     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5436     static int toggleFlag;
5437     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5438     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5439     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5440     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5441     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5442     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5443     do {
5444         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5445         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5446         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5447         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5448         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5449         if(!step) step = -1;
5450     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5451             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5452             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5453             promoSweep == pawn ||
5454             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5455             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5456     if(toX >= 0) {
5457         int victim = boards[currentMove][toY][toX];
5458         boards[currentMove][toY][toX] = promoSweep;
5459         DrawPosition(FALSE, boards[currentMove]);
5460         boards[currentMove][toY][toX] = victim;
5461     } else
5462     ChangeDragPiece(promoSweep);
5463 }
5464
5465 int
5466 PromoScroll (int x, int y)
5467 {
5468   int step = 0;
5469
5470   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5471   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5472   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5473   if(!step) return FALSE;
5474   lastX = x; lastY = y;
5475   if((promoSweep < BlackPawn) == flipView) step = -step;
5476   if(step > 0) selectFlag = 1;
5477   if(!selectFlag) Sweep(step);
5478   return FALSE;
5479 }
5480
5481 void
5482 NextPiece (int step)
5483 {
5484     ChessSquare piece = boards[currentMove][toY][toX];
5485     do {
5486         pieceSweep -= step;
5487         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5488         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5489         if(!step) step = -1;
5490     } while(PieceToChar(pieceSweep) == '.');
5491     boards[currentMove][toY][toX] = pieceSweep;
5492     DrawPosition(FALSE, boards[currentMove]);
5493     boards[currentMove][toY][toX] = piece;
5494 }
5495 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5496 void
5497 AlphaRank (char *move, int n)
5498 {
5499 //    char *p = move, c; int x, y;
5500
5501     if (appData.debugMode) {
5502         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5503     }
5504
5505     if(move[1]=='*' &&
5506        move[2]>='0' && move[2]<='9' &&
5507        move[3]>='a' && move[3]<='x'    ) {
5508         move[1] = '@';
5509         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5510         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5511     } else
5512     if(move[0]>='0' && move[0]<='9' &&
5513        move[1]>='a' && move[1]<='x' &&
5514        move[2]>='0' && move[2]<='9' &&
5515        move[3]>='a' && move[3]<='x'    ) {
5516         /* input move, Shogi -> normal */
5517         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5518         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5519         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5520         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5521     } else
5522     if(move[1]=='@' &&
5523        move[3]>='0' && move[3]<='9' &&
5524        move[2]>='a' && move[2]<='x'    ) {
5525         move[1] = '*';
5526         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5527         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5528     } else
5529     if(
5530        move[0]>='a' && move[0]<='x' &&
5531        move[3]>='0' && move[3]<='9' &&
5532        move[2]>='a' && move[2]<='x'    ) {
5533          /* output move, normal -> Shogi */
5534         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5535         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5536         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5537         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5538         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5539     }
5540     if (appData.debugMode) {
5541         fprintf(debugFP, "   out = '%s'\n", move);
5542     }
5543 }
5544
5545 char yy_textstr[8000];
5546
5547 /* Parser for moves from gnuchess, ICS, or user typein box */
5548 Boolean
5549 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5550 {
5551     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5552
5553     switch (*moveType) {
5554       case WhitePromotion:
5555       case BlackPromotion:
5556       case WhiteNonPromotion:
5557       case BlackNonPromotion:
5558       case NormalMove:
5559       case FirstLeg:
5560       case WhiteCapturesEnPassant:
5561       case BlackCapturesEnPassant:
5562       case WhiteKingSideCastle:
5563       case WhiteQueenSideCastle:
5564       case BlackKingSideCastle:
5565       case BlackQueenSideCastle:
5566       case WhiteKingSideCastleWild:
5567       case WhiteQueenSideCastleWild:
5568       case BlackKingSideCastleWild:
5569       case BlackQueenSideCastleWild:
5570       /* Code added by Tord: */
5571       case WhiteHSideCastleFR:
5572       case WhiteASideCastleFR:
5573       case BlackHSideCastleFR:
5574       case BlackASideCastleFR:
5575       /* End of code added by Tord */
5576       case IllegalMove:         /* bug or odd chess variant */
5577         if(currentMoveString[1] == '@') { // illegal drop
5578           *fromX = WhiteOnMove(moveNum) ?
5579             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5580             (int) CharToPiece(ToLower(currentMoveString[0]));
5581           goto drop;
5582         }
5583         *fromX = currentMoveString[0] - AAA;
5584         *fromY = currentMoveString[1] - ONE;
5585         *toX = currentMoveString[2] - AAA;
5586         *toY = currentMoveString[3] - ONE;
5587         *promoChar = currentMoveString[4];
5588         if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
5589         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5590             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5591     if (appData.debugMode) {
5592         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5593     }
5594             *fromX = *fromY = *toX = *toY = 0;
5595             return FALSE;
5596         }
5597         if (appData.testLegality) {
5598           return (*moveType != IllegalMove);
5599         } else {
5600           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5601                          // [HGM] lion: if this is a double move we are less critical
5602                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5603         }
5604
5605       case WhiteDrop:
5606       case BlackDrop:
5607         *fromX = *moveType == WhiteDrop ?
5608           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5609           (int) CharToPiece(ToLower(currentMoveString[0]));
5610       drop:
5611         *fromY = DROP_RANK;
5612         *toX = currentMoveString[2] - AAA;
5613         *toY = currentMoveString[3] - ONE;
5614         *promoChar = NULLCHAR;
5615         return TRUE;
5616
5617       case AmbiguousMove:
5618       case ImpossibleMove:
5619       case EndOfFile:
5620       case ElapsedTime:
5621       case Comment:
5622       case PGNTag:
5623       case NAG:
5624       case WhiteWins:
5625       case BlackWins:
5626       case GameIsDrawn:
5627       default:
5628     if (appData.debugMode) {
5629         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5630     }
5631         /* bug? */
5632         *fromX = *fromY = *toX = *toY = 0;
5633         *promoChar = NULLCHAR;
5634         return FALSE;
5635     }
5636 }
5637
5638 Boolean pushed = FALSE;
5639 char *lastParseAttempt;
5640
5641 void
5642 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5643 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5644   int fromX, fromY, toX, toY; char promoChar;
5645   ChessMove moveType;
5646   Boolean valid;
5647   int nr = 0;
5648
5649   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5650   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5651     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5652     pushed = TRUE;
5653   }
5654   endPV = forwardMostMove;
5655   do {
5656     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5657     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5658     lastParseAttempt = pv;
5659     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5660     if(!valid && nr == 0 &&
5661        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5662         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5663         // Hande case where played move is different from leading PV move
5664         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5665         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5666         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5667         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5668           endPV += 2; // if position different, keep this
5669           moveList[endPV-1][0] = fromX + AAA;
5670           moveList[endPV-1][1] = fromY + ONE;
5671           moveList[endPV-1][2] = toX + AAA;
5672           moveList[endPV-1][3] = toY + ONE;
5673           parseList[endPV-1][0] = NULLCHAR;
5674           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5675         }
5676       }
5677     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5678     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5679     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5680     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5681         valid++; // allow comments in PV
5682         continue;
5683     }
5684     nr++;
5685     if(endPV+1 > framePtr) break; // no space, truncate
5686     if(!valid) break;
5687     endPV++;
5688     CopyBoard(boards[endPV], boards[endPV-1]);
5689     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5690     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5691     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5692     CoordsToAlgebraic(boards[endPV - 1],
5693                              PosFlags(endPV - 1),
5694                              fromY, fromX, toY, toX, promoChar,
5695                              parseList[endPV - 1]);
5696   } while(valid);
5697   if(atEnd == 2) return; // used hidden, for PV conversion
5698   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5699   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5700   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5701                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5702   DrawPosition(TRUE, boards[currentMove]);
5703 }
5704
5705 int
5706 MultiPV (ChessProgramState *cps, int kind)
5707 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5708         int i;
5709         for(i=0; i<cps->nrOptions; i++) {
5710             char *s = cps->option[i].name;
5711             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5712             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5713                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5714         }
5715         return -1;
5716 }
5717
5718 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5719 static int multi, pv_margin;
5720 static ChessProgramState *activeCps;
5721
5722 Boolean
5723 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5724 {
5725         int startPV, lineStart, origIndex = index;
5726         char *p, buf2[MSG_SIZ];
5727         ChessProgramState *cps = (pane ? &second : &first);
5728
5729         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5730         lastX = x; lastY = y;
5731         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5732         lineStart = startPV = index;
5733         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5734         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5735         index = startPV;
5736         do{ while(buf[index] && buf[index] != '\n') index++;
5737         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5738         buf[index] = 0;
5739         if(lineStart == 0 && gameMode == AnalyzeMode) {
5740             int n = 0;
5741             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5742             if(n == 0) { // click not on "fewer" or "more"
5743                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5744                     pv_margin = cps->option[multi].value;
5745                     activeCps = cps; // non-null signals margin adjustment
5746                 }
5747             } else if((multi = MultiPV(cps, 1)) >= 0) {
5748                 n += cps->option[multi].value; if(n < 1) n = 1;
5749                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5750                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5751                 cps->option[multi].value = n;
5752                 *start = *end = 0;
5753                 return FALSE;
5754             }
5755         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5756                 ExcludeClick(origIndex - lineStart);
5757                 return FALSE;
5758         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5759                 Collapse(origIndex - lineStart);
5760                 return FALSE;
5761         }
5762         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5763         *start = startPV; *end = index-1;
5764         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5765         return TRUE;
5766 }
5767
5768 char *
5769 PvToSAN (char *pv)
5770 {
5771         static char buf[10*MSG_SIZ];
5772         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5773         *buf = NULLCHAR;
5774         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5775         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5776         for(i = forwardMostMove; i<endPV; i++){
5777             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5778             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5779             k += strlen(buf+k);
5780         }
5781         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5782         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5783         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5784         endPV = savedEnd;
5785         return buf;
5786 }
5787
5788 Boolean
5789 LoadPV (int x, int y)
5790 { // called on right mouse click to load PV
5791   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5792   lastX = x; lastY = y;
5793   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5794   extendGame = FALSE;
5795   return TRUE;
5796 }
5797
5798 void
5799 UnLoadPV ()
5800 {
5801   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5802   if(activeCps) {
5803     if(pv_margin != activeCps->option[multi].value) {
5804       char buf[MSG_SIZ];
5805       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5806       SendToProgram(buf, activeCps);
5807       activeCps->option[multi].value = pv_margin;
5808     }
5809     activeCps = NULL;
5810     return;
5811   }
5812   if(endPV < 0) return;
5813   if(appData.autoCopyPV) CopyFENToClipboard();
5814   endPV = -1;
5815   if(extendGame && currentMove > forwardMostMove) {
5816         Boolean saveAnimate = appData.animate;
5817         if(pushed) {
5818             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5819                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5820             } else storedGames--; // abandon shelved tail of original game
5821         }
5822         pushed = FALSE;
5823         forwardMostMove = currentMove;
5824         currentMove = oldFMM;
5825         appData.animate = FALSE;
5826         ToNrEvent(forwardMostMove);
5827         appData.animate = saveAnimate;
5828   }
5829   currentMove = forwardMostMove;
5830   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5831   ClearPremoveHighlights();
5832   DrawPosition(TRUE, boards[currentMove]);
5833 }
5834
5835 void
5836 MovePV (int x, int y, int h)
5837 { // step through PV based on mouse coordinates (called on mouse move)
5838   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5839
5840   if(activeCps) { // adjusting engine's multi-pv margin
5841     if(x > lastX) pv_margin++; else
5842     if(x < lastX) pv_margin -= (pv_margin > 0);
5843     if(x != lastX) {
5844       char buf[MSG_SIZ];
5845       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5846       DisplayMessage(buf, "");
5847     }
5848     lastX = x;
5849     return;
5850   }
5851   // we must somehow check if right button is still down (might be released off board!)
5852   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5853   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5854   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5855   if(!step) return;
5856   lastX = x; lastY = y;
5857
5858   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5859   if(endPV < 0) return;
5860   if(y < margin) step = 1; else
5861   if(y > h - margin) step = -1;
5862   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5863   currentMove += step;
5864   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5865   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5866                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5867   DrawPosition(FALSE, boards[currentMove]);
5868 }
5869
5870
5871 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5872 // All positions will have equal probability, but the current method will not provide a unique
5873 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5874 #define DARK 1
5875 #define LITE 2
5876 #define ANY 3
5877
5878 int squaresLeft[4];
5879 int piecesLeft[(int)BlackPawn];
5880 int seed, nrOfShuffles;
5881
5882 void
5883 GetPositionNumber ()
5884 {       // sets global variable seed
5885         int i;
5886
5887         seed = appData.defaultFrcPosition;
5888         if(seed < 0) { // randomize based on time for negative FRC position numbers
5889                 for(i=0; i<50; i++) seed += random();
5890                 seed = random() ^ random() >> 8 ^ random() << 8;
5891                 if(seed<0) seed = -seed;
5892         }
5893 }
5894
5895 int
5896 put (Board board, int pieceType, int rank, int n, int shade)
5897 // put the piece on the (n-1)-th empty squares of the given shade
5898 {
5899         int i;
5900
5901         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5902                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5903                         board[rank][i] = (ChessSquare) pieceType;
5904                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5905                         squaresLeft[ANY]--;
5906                         piecesLeft[pieceType]--;
5907                         return i;
5908                 }
5909         }
5910         return -1;
5911 }
5912
5913
5914 void
5915 AddOnePiece (Board board, int pieceType, int rank, int shade)
5916 // calculate where the next piece goes, (any empty square), and put it there
5917 {
5918         int i;
5919
5920         i = seed % squaresLeft[shade];
5921         nrOfShuffles *= squaresLeft[shade];
5922         seed /= squaresLeft[shade];
5923         put(board, pieceType, rank, i, shade);
5924 }
5925
5926 void
5927 AddTwoPieces (Board board, int pieceType, int rank)
5928 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5929 {
5930         int i, n=squaresLeft[ANY], j=n-1, k;
5931
5932         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5933         i = seed % k;  // pick one
5934         nrOfShuffles *= k;
5935         seed /= k;
5936         while(i >= j) i -= j--;
5937         j = n - 1 - j; i += j;
5938         put(board, pieceType, rank, j, ANY);
5939         put(board, pieceType, rank, i, ANY);
5940 }
5941
5942 void
5943 SetUpShuffle (Board board, int number)
5944 {
5945         int i, p, first=1;
5946
5947         GetPositionNumber(); nrOfShuffles = 1;
5948
5949         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5950         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5951         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5952
5953         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5954
5955         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5956             p = (int) board[0][i];
5957             if(p < (int) BlackPawn) piecesLeft[p] ++;
5958             board[0][i] = EmptySquare;
5959         }
5960
5961         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5962             // shuffles restricted to allow normal castling put KRR first
5963             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5964                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5965             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5966                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5967             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5968                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5969             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5970                 put(board, WhiteRook, 0, 0, ANY);
5971             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5972         }
5973
5974         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5975             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5976             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5977                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5978                 while(piecesLeft[p] >= 2) {
5979                     AddOnePiece(board, p, 0, LITE);
5980                     AddOnePiece(board, p, 0, DARK);
5981                 }
5982                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5983             }
5984
5985         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5986             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5987             // but we leave King and Rooks for last, to possibly obey FRC restriction
5988             if(p == (int)WhiteRook) continue;
5989             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5990             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5991         }
5992
5993         // now everything is placed, except perhaps King (Unicorn) and Rooks
5994
5995         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5996             // Last King gets castling rights
5997             while(piecesLeft[(int)WhiteUnicorn]) {
5998                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5999                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6000             }
6001
6002             while(piecesLeft[(int)WhiteKing]) {
6003                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
6004                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
6005             }
6006
6007
6008         } else {
6009             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
6010             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
6011         }
6012
6013         // Only Rooks can be left; simply place them all
6014         while(piecesLeft[(int)WhiteRook]) {
6015                 i = put(board, WhiteRook, 0, 0, ANY);
6016                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6017                         if(first) {
6018                                 first=0;
6019                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6020                         }
6021                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6022                 }
6023         }
6024         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6025             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6026         }
6027
6028         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6029 }
6030
6031 int
6032 ptclen (const char *s, char *escapes)
6033 {
6034     int n = 0;
6035     if(!*escapes) return strlen(s);
6036     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6037     return n;
6038 }
6039
6040 int
6041 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6042 /* [HGM] moved here from winboard.c because of its general usefulness */
6043 /*       Basically a safe strcpy that uses the last character as King */
6044 {
6045     int result = FALSE; int NrPieces;
6046     unsigned char partner[EmptySquare];
6047
6048     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6049                     && NrPieces >= 12 && !(NrPieces&1)) {
6050         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6051
6052         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6053         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6054             char *p, c=0;
6055             if(map[j] == '/') offs = WhitePBishop - i, j++;
6056             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6057             table[i+offs] = map[j++];
6058             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6059             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6060             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6061         }
6062         table[(int) WhiteKing]  = map[j++];
6063         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6064             char *p, c=0;
6065             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6066             i = WHITE_TO_BLACK ii;
6067             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6068             table[i+offs] = map[j++];
6069             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6070             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6071             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6072         }
6073         table[(int) BlackKing]  = map[j++];
6074
6075
6076         if(*escapes) { // set up promotion pairing
6077             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6078             // pieceToChar entirely filled, so we can look up specified partners
6079             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6080                 int c = table[i];
6081                 if(c == '^' || c == '-') { // has specified partner
6082                     int p;
6083                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6084                     if(c == '^') table[i] = '+';
6085                     if(p < EmptySquare) {
6086                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6087                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6088                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6089                     }
6090                 } else if(c == '*') {
6091                     table[i] = partner[i];
6092                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6093                 }
6094             }
6095         }
6096
6097         result = TRUE;
6098     }
6099
6100     return result;
6101 }
6102
6103 int
6104 SetCharTable (unsigned char *table, const char * map)
6105 {
6106     return SetCharTableEsc(table, map, "");
6107 }
6108
6109 void
6110 Prelude (Board board)
6111 {       // [HGM] superchess: random selection of exo-pieces
6112         int i, j, k; ChessSquare p;
6113         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6114
6115         GetPositionNumber(); // use FRC position number
6116
6117         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6118             SetCharTable(pieceToChar, appData.pieceToCharTable);
6119             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6120                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6121         }
6122
6123         j = seed%4;                 seed /= 4;
6124         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6125         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6126         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6127         j = seed%3 + (seed%3 >= j); seed /= 3;
6128         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6129         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6130         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6131         j = seed%3;                 seed /= 3;
6132         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6133         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6134         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6135         j = seed%2 + (seed%2 >= j); seed /= 2;
6136         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6137         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6138         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6139         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6140         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6141         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6142         put(board, exoPieces[0],    0, 0, ANY);
6143         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6144 }
6145
6146 void
6147 InitPosition (int redraw)
6148 {
6149     ChessSquare (* pieces)[BOARD_FILES];
6150     int i, j, pawnRow=1, pieceRows=1, overrule,
6151     oldx = gameInfo.boardWidth,
6152     oldy = gameInfo.boardHeight,
6153     oldh = gameInfo.holdingsWidth;
6154     static int oldv;
6155
6156     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6157
6158     /* [AS] Initialize pv info list [HGM] and game status */
6159     {
6160         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6161             pvInfoList[i].depth = 0;
6162             boards[i][EP_STATUS] = EP_NONE;
6163             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6164         }
6165
6166         initialRulePlies = 0; /* 50-move counter start */
6167
6168         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6169         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6170     }
6171
6172
6173     /* [HGM] logic here is completely changed. In stead of full positions */
6174     /* the initialized data only consist of the two backranks. The switch */
6175     /* selects which one we will use, which is than copied to the Board   */
6176     /* initialPosition, which for the rest is initialized by Pawns and    */
6177     /* empty squares. This initial position is then copied to boards[0],  */
6178     /* possibly after shuffling, so that it remains available.            */
6179
6180     gameInfo.holdingsWidth = 0; /* default board sizes */
6181     gameInfo.boardWidth    = 8;
6182     gameInfo.boardHeight   = 8;
6183     gameInfo.holdingsSize  = 0;
6184     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6185     for(i=0; i<BOARD_FILES-6; i++)
6186       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6187     initialPosition[EP_STATUS] = EP_NONE;
6188     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6189     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6190     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6191          SetCharTable(pieceNickName, appData.pieceNickNames);
6192     else SetCharTable(pieceNickName, "............");
6193     pieces = FIDEArray;
6194
6195     switch (gameInfo.variant) {
6196     case VariantFischeRandom:
6197       shuffleOpenings = TRUE;
6198       appData.fischerCastling = TRUE;
6199     default:
6200       break;
6201     case VariantShatranj:
6202       pieces = ShatranjArray;
6203       nrCastlingRights = 0;
6204       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6205       break;
6206     case VariantMakruk:
6207       pieces = makrukArray;
6208       nrCastlingRights = 0;
6209       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6210       break;
6211     case VariantASEAN:
6212       pieces = aseanArray;
6213       nrCastlingRights = 0;
6214       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6215       break;
6216     case VariantTwoKings:
6217       pieces = twoKingsArray;
6218       break;
6219     case VariantGrand:
6220       pieces = GrandArray;
6221       nrCastlingRights = 0;
6222       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6223       gameInfo.boardWidth = 10;
6224       gameInfo.boardHeight = 10;
6225       gameInfo.holdingsSize = 7;
6226       break;
6227     case VariantCapaRandom:
6228       shuffleOpenings = TRUE;
6229       appData.fischerCastling = TRUE;
6230     case VariantCapablanca:
6231       pieces = CapablancaArray;
6232       gameInfo.boardWidth = 10;
6233       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6234       break;
6235     case VariantGothic:
6236       pieces = GothicArray;
6237       gameInfo.boardWidth = 10;
6238       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6239       break;
6240     case VariantSChess:
6241       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6242       gameInfo.holdingsSize = 7;
6243       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6244       break;
6245     case VariantJanus:
6246       pieces = JanusArray;
6247       gameInfo.boardWidth = 10;
6248       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6249       nrCastlingRights = 6;
6250         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6251         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6252         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6253         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6254         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6255         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6256       break;
6257     case VariantFalcon:
6258       pieces = FalconArray;
6259       gameInfo.boardWidth = 10;
6260       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6261       break;
6262     case VariantXiangqi:
6263       pieces = XiangqiArray;
6264       gameInfo.boardWidth  = 9;
6265       gameInfo.boardHeight = 10;
6266       nrCastlingRights = 0;
6267       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6268       break;
6269     case VariantShogi:
6270       pieces = ShogiArray;
6271       gameInfo.boardWidth  = 9;
6272       gameInfo.boardHeight = 9;
6273       gameInfo.holdingsSize = 7;
6274       nrCastlingRights = 0;
6275       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6276       break;
6277     case VariantChu:
6278       pieces = ChuArray; pieceRows = 3;
6279       gameInfo.boardWidth  = 12;
6280       gameInfo.boardHeight = 12;
6281       nrCastlingRights = 0;
6282       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6283                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6284       break;
6285     case VariantCourier:
6286       pieces = CourierArray;
6287       gameInfo.boardWidth  = 12;
6288       nrCastlingRights = 0;
6289       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6290       break;
6291     case VariantKnightmate:
6292       pieces = KnightmateArray;
6293       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6294       break;
6295     case VariantSpartan:
6296       pieces = SpartanArray;
6297       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6298       break;
6299     case VariantLion:
6300       pieces = lionArray;
6301       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6302       break;
6303     case VariantChuChess:
6304       pieces = ChuChessArray;
6305       gameInfo.boardWidth = 10;
6306       gameInfo.boardHeight = 10;
6307       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6308       break;
6309     case VariantFairy:
6310       pieces = fairyArray;
6311       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6312       break;
6313     case VariantGreat:
6314       pieces = GreatArray;
6315       gameInfo.boardWidth = 10;
6316       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6317       gameInfo.holdingsSize = 8;
6318       break;
6319     case VariantSuper:
6320       pieces = FIDEArray;
6321       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6322       gameInfo.holdingsSize = 8;
6323       startedFromSetupPosition = TRUE;
6324       break;
6325     case VariantCrazyhouse:
6326     case VariantBughouse:
6327       pieces = FIDEArray;
6328       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6329       gameInfo.holdingsSize = 5;
6330       break;
6331     case VariantWildCastle:
6332       pieces = FIDEArray;
6333       /* !!?shuffle with kings guaranteed to be on d or e file */
6334       shuffleOpenings = 1;
6335       break;
6336     case VariantNoCastle:
6337       pieces = FIDEArray;
6338       nrCastlingRights = 0;
6339       /* !!?unconstrained back-rank shuffle */
6340       shuffleOpenings = 1;
6341       break;
6342     }
6343
6344     overrule = 0;
6345     if(appData.NrFiles >= 0) {
6346         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6347         gameInfo.boardWidth = appData.NrFiles;
6348     }
6349     if(appData.NrRanks >= 0) {
6350         gameInfo.boardHeight = appData.NrRanks;
6351     }
6352     if(appData.holdingsSize >= 0) {
6353         i = appData.holdingsSize;
6354         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6355         gameInfo.holdingsSize = i;
6356     }
6357     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6358     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6359         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6360
6361     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6362     if(pawnRow < 1) pawnRow = 1;
6363     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6364        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6365     if(gameInfo.variant == VariantChu) pawnRow = 3;
6366
6367     /* User pieceToChar list overrules defaults */
6368     if(appData.pieceToCharTable != NULL)
6369         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6370
6371     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6372
6373         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6374             s = (ChessSquare) 0; /* account holding counts in guard band */
6375         for( i=0; i<BOARD_HEIGHT; i++ )
6376             initialPosition[i][j] = s;
6377
6378         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6379         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6380         initialPosition[pawnRow][j] = WhitePawn;
6381         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6382         if(gameInfo.variant == VariantXiangqi) {
6383             if(j&1) {
6384                 initialPosition[pawnRow][j] =
6385                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6386                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6387                    initialPosition[2][j] = WhiteCannon;
6388                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6389                 }
6390             }
6391         }
6392         if(gameInfo.variant == VariantChu) {
6393              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6394                initialPosition[pawnRow+1][j] = WhiteCobra,
6395                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6396              for(i=1; i<pieceRows; i++) {
6397                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6398                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6399              }
6400         }
6401         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6402             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6403                initialPosition[0][j] = WhiteRook;
6404                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6405             }
6406         }
6407         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6408     }
6409     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6410     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6411
6412             j=BOARD_LEFT+1;
6413             initialPosition[1][j] = WhiteBishop;
6414             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6415             j=BOARD_RGHT-2;
6416             initialPosition[1][j] = WhiteRook;
6417             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6418     }
6419
6420     if( nrCastlingRights == -1) {
6421         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6422         /*       This sets default castling rights from none to normal corners   */
6423         /* Variants with other castling rights must set them themselves above    */
6424         nrCastlingRights = 6;
6425
6426         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6427         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6428         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6429         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6430         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6431         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6432      }
6433
6434      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6435      if(gameInfo.variant == VariantGreat) { // promotion commoners
6436         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6437         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6438         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6439         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6440      }
6441      if( gameInfo.variant == VariantSChess ) {
6442       initialPosition[1][0] = BlackMarshall;
6443       initialPosition[2][0] = BlackAngel;
6444       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6445       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6446       initialPosition[1][1] = initialPosition[2][1] =
6447       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6448      }
6449   if (appData.debugMode) {
6450     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6451   }
6452     if(shuffleOpenings) {
6453         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6454         startedFromSetupPosition = TRUE;
6455     }
6456     if(startedFromPositionFile) {
6457       /* [HGM] loadPos: use PositionFile for every new game */
6458       CopyBoard(initialPosition, filePosition);
6459       for(i=0; i<nrCastlingRights; i++)
6460           initialRights[i] = filePosition[CASTLING][i];
6461       startedFromSetupPosition = TRUE;
6462     }
6463     if(*appData.men) LoadPieceDesc(appData.men);
6464
6465     CopyBoard(boards[0], initialPosition);
6466
6467     if(oldx != gameInfo.boardWidth ||
6468        oldy != gameInfo.boardHeight ||
6469        oldv != gameInfo.variant ||
6470        oldh != gameInfo.holdingsWidth
6471                                          )
6472             InitDrawingSizes(-2 ,0);
6473
6474     oldv = gameInfo.variant;
6475     if (redraw)
6476       DrawPosition(TRUE, boards[currentMove]);
6477 }
6478
6479 void
6480 SendBoard (ChessProgramState *cps, int moveNum)
6481 {
6482     char message[MSG_SIZ];
6483
6484     if (cps->useSetboard) {
6485       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6486       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6487       SendToProgram(message, cps);
6488       free(fen);
6489
6490     } else {
6491       ChessSquare *bp;
6492       int i, j, left=0, right=BOARD_WIDTH;
6493       /* Kludge to set black to move, avoiding the troublesome and now
6494        * deprecated "black" command.
6495        */
6496       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6497         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6498
6499       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6500
6501       SendToProgram("edit\n", cps);
6502       SendToProgram("#\n", cps);
6503       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6504         bp = &boards[moveNum][i][left];
6505         for (j = left; j < right; j++, bp++) {
6506           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6507           if ((int) *bp < (int) BlackPawn) {
6508             if(j == BOARD_RGHT+1)
6509                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6510             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6511             if(message[0] == '+' || message[0] == '~') {
6512               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6513                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6514                         AAA + j, ONE + i - '0');
6515             }
6516             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6517                 message[1] = BOARD_RGHT   - 1 - j + '1';
6518                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6519             }
6520             SendToProgram(message, cps);
6521           }
6522         }
6523       }
6524
6525       SendToProgram("c\n", cps);
6526       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6527         bp = &boards[moveNum][i][left];
6528         for (j = left; j < right; j++, bp++) {
6529           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6530           if (((int) *bp != (int) EmptySquare)
6531               && ((int) *bp >= (int) BlackPawn)) {
6532             if(j == BOARD_LEFT-2)
6533                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6534             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6535                     AAA + j, ONE + i - '0');
6536             if(message[0] == '+' || message[0] == '~') {
6537               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6538                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6539                         AAA + j, ONE + i - '0');
6540             }
6541             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6542                 message[1] = BOARD_RGHT   - 1 - j + '1';
6543                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6544             }
6545             SendToProgram(message, cps);
6546           }
6547         }
6548       }
6549
6550       SendToProgram(".\n", cps);
6551     }
6552     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6553 }
6554
6555 char exclusionHeader[MSG_SIZ];
6556 int exCnt, excludePtr;
6557 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6558 static Exclusion excluTab[200];
6559 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6560
6561 static void
6562 WriteMap (int s)
6563 {
6564     int j;
6565     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6566     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6567 }
6568
6569 static void
6570 ClearMap ()
6571 {
6572     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6573     excludePtr = 24; exCnt = 0;
6574     WriteMap(0);
6575 }
6576
6577 static void
6578 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6579 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6580     char buf[2*MOVE_LEN], *p;
6581     Exclusion *e = excluTab;
6582     int i;
6583     for(i=0; i<exCnt; i++)
6584         if(e[i].ff == fromX && e[i].fr == fromY &&
6585            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6586     if(i == exCnt) { // was not in exclude list; add it
6587         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6588         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6589             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6590             return; // abort
6591         }
6592         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6593         excludePtr++; e[i].mark = excludePtr++;
6594         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6595         exCnt++;
6596     }
6597     exclusionHeader[e[i].mark] = state;
6598 }
6599
6600 static int
6601 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6602 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6603     char buf[MSG_SIZ];
6604     int j, k;
6605     ChessMove moveType;
6606     if((signed char)promoChar == -1) { // kludge to indicate best move
6607         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6608             return 1; // if unparsable, abort
6609     }
6610     // update exclusion map (resolving toggle by consulting existing state)
6611     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6612     j = k%8; k >>= 3;
6613     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6614     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6615          excludeMap[k] |=   1<<j;
6616     else excludeMap[k] &= ~(1<<j);
6617     // update header
6618     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6619     // inform engine
6620     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6621     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6622     SendToBoth(buf);
6623     return (state == '+');
6624 }
6625
6626 static void
6627 ExcludeClick (int index)
6628 {
6629     int i, j;
6630     Exclusion *e = excluTab;
6631     if(index < 25) { // none, best or tail clicked
6632         if(index < 13) { // none: include all
6633             WriteMap(0); // clear map
6634             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6635             SendToBoth("include all\n"); // and inform engine
6636         } else if(index > 18) { // tail
6637             if(exclusionHeader[19] == '-') { // tail was excluded
6638                 SendToBoth("include all\n");
6639                 WriteMap(0); // clear map completely
6640                 // now re-exclude selected moves
6641                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6642                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6643             } else { // tail was included or in mixed state
6644                 SendToBoth("exclude all\n");
6645                 WriteMap(0xFF); // fill map completely
6646                 // now re-include selected moves
6647                 j = 0; // count them
6648                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6649                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6650                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6651             }
6652         } else { // best
6653             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6654         }
6655     } else {
6656         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6657             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6658             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6659             break;
6660         }
6661     }
6662 }
6663
6664 ChessSquare
6665 DefaultPromoChoice (int white)
6666 {
6667     ChessSquare result;
6668     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6669        gameInfo.variant == VariantMakruk)
6670         result = WhiteFerz; // no choice
6671     else if(gameInfo.variant == VariantASEAN)
6672         result = WhiteRook; // no choice
6673     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6674         result= WhiteKing; // in Suicide Q is the last thing we want
6675     else if(gameInfo.variant == VariantSpartan)
6676         result = white ? WhiteQueen : WhiteAngel;
6677     else result = WhiteQueen;
6678     if(!white) result = WHITE_TO_BLACK result;
6679     return result;
6680 }
6681
6682 static int autoQueen; // [HGM] oneclick
6683
6684 int
6685 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6686 {
6687     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6688     /* [HGM] add Shogi promotions */
6689     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6690     ChessSquare piece, partner;
6691     ChessMove moveType;
6692     Boolean premove;
6693
6694     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6695     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6696
6697     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6698       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6699         return FALSE;
6700
6701     piece = boards[currentMove][fromY][fromX];
6702     if(gameInfo.variant == VariantChu) {
6703         promotionZoneSize = BOARD_HEIGHT/3;
6704         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6705     } else if(gameInfo.variant == VariantShogi) {
6706         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6707         highestPromotingPiece = (int)WhiteAlfil;
6708     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6709         promotionZoneSize = 3;
6710     }
6711
6712     // Treat Lance as Pawn when it is not representing Amazon or Lance
6713     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6714         if(piece == WhiteLance) piece = WhitePawn; else
6715         if(piece == BlackLance) piece = BlackPawn;
6716     }
6717
6718     // next weed out all moves that do not touch the promotion zone at all
6719     if((int)piece >= BlackPawn) {
6720         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6721              return FALSE;
6722         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6723         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6724     } else {
6725         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6726            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6727         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6728              return FALSE;
6729     }
6730
6731     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6732
6733     // weed out mandatory Shogi promotions
6734     if(gameInfo.variant == VariantShogi) {
6735         if(piece >= BlackPawn) {
6736             if(toY == 0 && piece == BlackPawn ||
6737                toY == 0 && piece == BlackQueen ||
6738                toY <= 1 && piece == BlackKnight) {
6739                 *promoChoice = '+';
6740                 return FALSE;
6741             }
6742         } else {
6743             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6744                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6745                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6746                 *promoChoice = '+';
6747                 return FALSE;
6748             }
6749         }
6750     }
6751
6752     // weed out obviously illegal Pawn moves
6753     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6754         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6755         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6756         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6757         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6758         // note we are not allowed to test for valid (non-)capture, due to premove
6759     }
6760
6761     // we either have a choice what to promote to, or (in Shogi) whether to promote
6762     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6763        gameInfo.variant == VariantMakruk) {
6764         ChessSquare p=BlackFerz;  // no choice
6765         while(p < EmptySquare) {  //but make sure we use piece that exists
6766             *promoChoice = PieceToChar(p++);
6767             if(*promoChoice != '.') break;
6768         }
6769         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6770     }
6771     // no sense asking what we must promote to if it is going to explode...
6772     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6773         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6774         return FALSE;
6775     }
6776     // give caller the default choice even if we will not make it
6777     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6778     partner = piece; // pieces can promote if the pieceToCharTable says so
6779     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6780     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6781     if(        sweepSelect && gameInfo.variant != VariantGreat
6782                            && gameInfo.variant != VariantGrand
6783                            && gameInfo.variant != VariantSuper) return FALSE;
6784     if(autoQueen) return FALSE; // predetermined
6785
6786     // suppress promotion popup on illegal moves that are not premoves
6787     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6788               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6789     if(appData.testLegality && !premove) {
6790         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6791                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6792         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6793         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6794             return FALSE;
6795     }
6796
6797     return TRUE;
6798 }
6799
6800 int
6801 InPalace (int row, int column)
6802 {   /* [HGM] for Xiangqi */
6803     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6804          column < (BOARD_WIDTH + 4)/2 &&
6805          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6806     return FALSE;
6807 }
6808
6809 int
6810 PieceForSquare (int x, int y)
6811 {
6812   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6813      return -1;
6814   else
6815      return boards[currentMove][y][x];
6816 }
6817
6818 int
6819 OKToStartUserMove (int x, int y)
6820 {
6821     ChessSquare from_piece;
6822     int white_piece;
6823
6824     if (matchMode) return FALSE;
6825     if (gameMode == EditPosition) return TRUE;
6826
6827     if (x >= 0 && y >= 0)
6828       from_piece = boards[currentMove][y][x];
6829     else
6830       from_piece = EmptySquare;
6831
6832     if (from_piece == EmptySquare) return FALSE;
6833
6834     white_piece = (int)from_piece >= (int)WhitePawn &&
6835       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6836
6837     switch (gameMode) {
6838       case AnalyzeFile:
6839       case TwoMachinesPlay:
6840       case EndOfGame:
6841         return FALSE;
6842
6843       case IcsObserving:
6844       case IcsIdle:
6845         return FALSE;
6846
6847       case MachinePlaysWhite:
6848       case IcsPlayingBlack:
6849         if (appData.zippyPlay) return FALSE;
6850         if (white_piece) {
6851             DisplayMoveError(_("You are playing Black"));
6852             return FALSE;
6853         }
6854         break;
6855
6856       case MachinePlaysBlack:
6857       case IcsPlayingWhite:
6858         if (appData.zippyPlay) return FALSE;
6859         if (!white_piece) {
6860             DisplayMoveError(_("You are playing White"));
6861             return FALSE;
6862         }
6863         break;
6864
6865       case PlayFromGameFile:
6866             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6867       case EditGame:
6868       case AnalyzeMode:
6869         if (!white_piece && WhiteOnMove(currentMove)) {
6870             DisplayMoveError(_("It is White's turn"));
6871             return FALSE;
6872         }
6873         if (white_piece && !WhiteOnMove(currentMove)) {
6874             DisplayMoveError(_("It is Black's turn"));
6875             return FALSE;
6876         }
6877         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6878             /* Editing correspondence game history */
6879             /* Could disallow this or prompt for confirmation */
6880             cmailOldMove = -1;
6881         }
6882         break;
6883
6884       case BeginningOfGame:
6885         if (appData.icsActive) return FALSE;
6886         if (!appData.noChessProgram) {
6887             if (!white_piece) {
6888                 DisplayMoveError(_("You are playing White"));
6889                 return FALSE;
6890             }
6891         }
6892         break;
6893
6894       case Training:
6895         if (!white_piece && WhiteOnMove(currentMove)) {
6896             DisplayMoveError(_("It is White's turn"));
6897             return FALSE;
6898         }
6899         if (white_piece && !WhiteOnMove(currentMove)) {
6900             DisplayMoveError(_("It is Black's turn"));
6901             return FALSE;
6902         }
6903         break;
6904
6905       default:
6906       case IcsExamining:
6907         break;
6908     }
6909     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6910         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6911         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6912         && gameMode != AnalyzeFile && gameMode != Training) {
6913         DisplayMoveError(_("Displayed position is not current"));
6914         return FALSE;
6915     }
6916     return TRUE;
6917 }
6918
6919 Boolean
6920 OnlyMove (int *x, int *y, Boolean captures)
6921 {
6922     DisambiguateClosure cl;
6923     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6924     switch(gameMode) {
6925       case MachinePlaysBlack:
6926       case IcsPlayingWhite:
6927       case BeginningOfGame:
6928         if(!WhiteOnMove(currentMove)) return FALSE;
6929         break;
6930       case MachinePlaysWhite:
6931       case IcsPlayingBlack:
6932         if(WhiteOnMove(currentMove)) return FALSE;
6933         break;
6934       case EditGame:
6935         break;
6936       default:
6937         return FALSE;
6938     }
6939     cl.pieceIn = EmptySquare;
6940     cl.rfIn = *y;
6941     cl.ffIn = *x;
6942     cl.rtIn = -1;
6943     cl.ftIn = -1;
6944     cl.promoCharIn = NULLCHAR;
6945     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6946     if( cl.kind == NormalMove ||
6947         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6948         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6949         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6950       fromX = cl.ff;
6951       fromY = cl.rf;
6952       *x = cl.ft;
6953       *y = cl.rt;
6954       return TRUE;
6955     }
6956     if(cl.kind != ImpossibleMove) return FALSE;
6957     cl.pieceIn = EmptySquare;
6958     cl.rfIn = -1;
6959     cl.ffIn = -1;
6960     cl.rtIn = *y;
6961     cl.ftIn = *x;
6962     cl.promoCharIn = NULLCHAR;
6963     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6964     if( cl.kind == NormalMove ||
6965         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6966         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6967         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6968       fromX = cl.ff;
6969       fromY = cl.rf;
6970       *x = cl.ft;
6971       *y = cl.rt;
6972       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6973       return TRUE;
6974     }
6975     return FALSE;
6976 }
6977
6978 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6979 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6980 int lastLoadGameUseList = FALSE;
6981 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6982 ChessMove lastLoadGameStart = EndOfFile;
6983 int doubleClick;
6984 Boolean addToBookFlag;
6985
6986 void
6987 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6988 {
6989     ChessMove moveType;
6990     ChessSquare pup;
6991     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6992
6993     /* Check if the user is playing in turn.  This is complicated because we
6994        let the user "pick up" a piece before it is his turn.  So the piece he
6995        tried to pick up may have been captured by the time he puts it down!
6996        Therefore we use the color the user is supposed to be playing in this
6997        test, not the color of the piece that is currently on the starting
6998        square---except in EditGame mode, where the user is playing both
6999        sides; fortunately there the capture race can't happen.  (It can
7000        now happen in IcsExamining mode, but that's just too bad.  The user
7001        will get a somewhat confusing message in that case.)
7002        */
7003
7004     switch (gameMode) {
7005       case AnalyzeFile:
7006       case TwoMachinesPlay:
7007       case EndOfGame:
7008       case IcsObserving:
7009       case IcsIdle:
7010         /* We switched into a game mode where moves are not accepted,
7011            perhaps while the mouse button was down. */
7012         return;
7013
7014       case MachinePlaysWhite:
7015         /* User is moving for Black */
7016         if (WhiteOnMove(currentMove)) {
7017             DisplayMoveError(_("It is White's turn"));
7018             return;
7019         }
7020         break;
7021
7022       case MachinePlaysBlack:
7023         /* User is moving for White */
7024         if (!WhiteOnMove(currentMove)) {
7025             DisplayMoveError(_("It is Black's turn"));
7026             return;
7027         }
7028         break;
7029
7030       case PlayFromGameFile:
7031             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7032       case EditGame:
7033       case IcsExamining:
7034       case BeginningOfGame:
7035       case AnalyzeMode:
7036       case Training:
7037         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7038         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7039             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7040             /* User is moving for Black */
7041             if (WhiteOnMove(currentMove)) {
7042                 DisplayMoveError(_("It is White's turn"));
7043                 return;
7044             }
7045         } else {
7046             /* User is moving for White */
7047             if (!WhiteOnMove(currentMove)) {
7048                 DisplayMoveError(_("It is Black's turn"));
7049                 return;
7050             }
7051         }
7052         break;
7053
7054       case IcsPlayingBlack:
7055         /* User is moving for Black */
7056         if (WhiteOnMove(currentMove)) {
7057             if (!appData.premove) {
7058                 DisplayMoveError(_("It is White's turn"));
7059             } else if (toX >= 0 && toY >= 0) {
7060                 premoveToX = toX;
7061                 premoveToY = toY;
7062                 premoveFromX = fromX;
7063                 premoveFromY = fromY;
7064                 premovePromoChar = promoChar;
7065                 gotPremove = 1;
7066                 if (appData.debugMode)
7067                     fprintf(debugFP, "Got premove: fromX %d,"
7068                             "fromY %d, toX %d, toY %d\n",
7069                             fromX, fromY, toX, toY);
7070             }
7071             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7072             return;
7073         }
7074         break;
7075
7076       case IcsPlayingWhite:
7077         /* User is moving for White */
7078         if (!WhiteOnMove(currentMove)) {
7079             if (!appData.premove) {
7080                 DisplayMoveError(_("It is Black's turn"));
7081             } else if (toX >= 0 && toY >= 0) {
7082                 premoveToX = toX;
7083                 premoveToY = toY;
7084                 premoveFromX = fromX;
7085                 premoveFromY = fromY;
7086                 premovePromoChar = promoChar;
7087                 gotPremove = 1;
7088                 if (appData.debugMode)
7089                     fprintf(debugFP, "Got premove: fromX %d,"
7090                             "fromY %d, toX %d, toY %d\n",
7091                             fromX, fromY, toX, toY);
7092             }
7093             DrawPosition(TRUE, boards[currentMove]);
7094             return;
7095         }
7096         break;
7097
7098       default:
7099         break;
7100
7101       case EditPosition:
7102         /* EditPosition, empty square, or different color piece;
7103            click-click move is possible */
7104         if (toX == -2 || toY == -2) {
7105             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7106             DrawPosition(FALSE, boards[currentMove]);
7107             return;
7108         } else if (toX >= 0 && toY >= 0) {
7109             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7110                 ChessSquare p = boards[0][rf][ff];
7111                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7112                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7113             }
7114             boards[0][toY][toX] = boards[0][fromY][fromX];
7115             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7116                 if(boards[0][fromY][0] != EmptySquare) {
7117                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7118                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7119                 }
7120             } else
7121             if(fromX == BOARD_RGHT+1) {
7122                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7123                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7124                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7125                 }
7126             } else
7127             boards[0][fromY][fromX] = gatingPiece;
7128             ClearHighlights();
7129             DrawPosition(FALSE, boards[currentMove]);
7130             return;
7131         }
7132         return;
7133     }
7134
7135     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7136     pup = boards[currentMove][toY][toX];
7137
7138     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7139     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7140          if( pup != EmptySquare ) return;
7141          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7142            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7143                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7144            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7145            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7146            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7147            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7148          fromY = DROP_RANK;
7149     }
7150
7151     /* [HGM] always test for legality, to get promotion info */
7152     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7153                                          fromY, fromX, toY, toX, promoChar);
7154
7155     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7156
7157     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7158
7159     /* [HGM] but possibly ignore an IllegalMove result */
7160     if (appData.testLegality) {
7161         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7162             DisplayMoveError(_("Illegal move"));
7163             return;
7164         }
7165     }
7166
7167     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7168         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7169              ClearPremoveHighlights(); // was included
7170         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7171         return;
7172     }
7173
7174     if(addToBookFlag) { // adding moves to book
7175         char buf[MSG_SIZ], move[MSG_SIZ];
7176         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7177         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7178                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7179         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7180         AddBookMove(buf);
7181         addToBookFlag = FALSE;
7182         ClearHighlights();
7183         return;
7184     }
7185
7186     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7187 }
7188
7189 /* Common tail of UserMoveEvent and DropMenuEvent */
7190 int
7191 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7192 {
7193     char *bookHit = 0;
7194
7195     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7196         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7197         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7198         if(WhiteOnMove(currentMove)) {
7199             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7200         } else {
7201             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7202         }
7203     }
7204
7205     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7206        move type in caller when we know the move is a legal promotion */
7207     if(moveType == NormalMove && promoChar)
7208         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7209
7210     /* [HGM] <popupFix> The following if has been moved here from
7211        UserMoveEvent(). Because it seemed to belong here (why not allow
7212        piece drops in training games?), and because it can only be
7213        performed after it is known to what we promote. */
7214     if (gameMode == Training) {
7215       /* compare the move played on the board to the next move in the
7216        * game. If they match, display the move and the opponent's response.
7217        * If they don't match, display an error message.
7218        */
7219       int saveAnimate;
7220       Board testBoard;
7221       CopyBoard(testBoard, boards[currentMove]);
7222       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7223
7224       if (CompareBoards(testBoard, boards[currentMove+1])) {
7225         ForwardInner(currentMove+1);
7226
7227         /* Autoplay the opponent's response.
7228          * if appData.animate was TRUE when Training mode was entered,
7229          * the response will be animated.
7230          */
7231         saveAnimate = appData.animate;
7232         appData.animate = animateTraining;
7233         ForwardInner(currentMove+1);
7234         appData.animate = saveAnimate;
7235
7236         /* check for the end of the game */
7237         if (currentMove >= forwardMostMove) {
7238           gameMode = PlayFromGameFile;
7239           ModeHighlight();
7240           SetTrainingModeOff();
7241           DisplayInformation(_("End of game"));
7242         }
7243       } else {
7244         DisplayError(_("Incorrect move"), 0);
7245       }
7246       return 1;
7247     }
7248
7249   /* Ok, now we know that the move is good, so we can kill
7250      the previous line in Analysis Mode */
7251   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7252                                 && currentMove < forwardMostMove) {
7253     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7254     else forwardMostMove = currentMove;
7255   }
7256
7257   ClearMap();
7258
7259   /* If we need the chess program but it's dead, restart it */
7260   ResurrectChessProgram();
7261
7262   /* A user move restarts a paused game*/
7263   if (pausing)
7264     PauseEvent();
7265
7266   thinkOutput[0] = NULLCHAR;
7267
7268   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7269
7270   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7271     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7272     return 1;
7273   }
7274
7275   if (gameMode == BeginningOfGame) {
7276     if (appData.noChessProgram) {
7277       gameMode = EditGame;
7278       SetGameInfo();
7279     } else {
7280       char buf[MSG_SIZ];
7281       gameMode = MachinePlaysBlack;
7282       StartClocks();
7283       SetGameInfo();
7284       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7285       DisplayTitle(buf);
7286       if (first.sendName) {
7287         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7288         SendToProgram(buf, &first);
7289       }
7290       StartClocks();
7291     }
7292     ModeHighlight();
7293   }
7294
7295   /* Relay move to ICS or chess engine */
7296   if (appData.icsActive) {
7297     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7298         gameMode == IcsExamining) {
7299       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7300         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7301         SendToICS("draw ");
7302         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7303       }
7304       // also send plain move, in case ICS does not understand atomic claims
7305       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7306       ics_user_moved = 1;
7307     }
7308   } else {
7309     if (first.sendTime && (gameMode == BeginningOfGame ||
7310                            gameMode == MachinePlaysWhite ||
7311                            gameMode == MachinePlaysBlack)) {
7312       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7313     }
7314     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7315          // [HGM] book: if program might be playing, let it use book
7316         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7317         first.maybeThinking = TRUE;
7318     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7319         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7320         SendBoard(&first, currentMove+1);
7321         if(second.analyzing) {
7322             if(!second.useSetboard) SendToProgram("undo\n", &second);
7323             SendBoard(&second, currentMove+1);
7324         }
7325     } else {
7326         SendMoveToProgram(forwardMostMove-1, &first);
7327         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7328     }
7329     if (currentMove == cmailOldMove + 1) {
7330       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7331     }
7332   }
7333
7334   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7335
7336   switch (gameMode) {
7337   case EditGame:
7338     if(appData.testLegality)
7339     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7340     case MT_NONE:
7341     case MT_CHECK:
7342       break;
7343     case MT_CHECKMATE:
7344     case MT_STAINMATE:
7345       if (WhiteOnMove(currentMove)) {
7346         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7347       } else {
7348         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7349       }
7350       break;
7351     case MT_STALEMATE:
7352       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7353       break;
7354     }
7355     break;
7356
7357   case MachinePlaysBlack:
7358   case MachinePlaysWhite:
7359     /* disable certain menu options while machine is thinking */
7360     SetMachineThinkingEnables();
7361     break;
7362
7363   default:
7364     break;
7365   }
7366
7367   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7368   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7369
7370   if(bookHit) { // [HGM] book: simulate book reply
7371         static char bookMove[MSG_SIZ]; // a bit generous?
7372
7373         programStats.nodes = programStats.depth = programStats.time =
7374         programStats.score = programStats.got_only_move = 0;
7375         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7376
7377         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7378         strcat(bookMove, bookHit);
7379         HandleMachineMove(bookMove, &first);
7380   }
7381   return 1;
7382 }
7383
7384 void
7385 MarkByFEN(char *fen)
7386 {
7387         int r, f;
7388         if(!appData.markers || !appData.highlightDragging) return;
7389         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7390         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7391         while(*fen) {
7392             int s = 0;
7393             marker[r][f] = 0;
7394             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7395             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7396             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7397             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7398             if(*fen == 'T') marker[r][f++] = 0; else
7399             if(*fen == 'Y') marker[r][f++] = 1; else
7400             if(*fen == 'G') marker[r][f++] = 3; else
7401             if(*fen == 'B') marker[r][f++] = 4; else
7402             if(*fen == 'C') marker[r][f++] = 5; else
7403             if(*fen == 'M') marker[r][f++] = 6; else
7404             if(*fen == 'W') marker[r][f++] = 7; else
7405             if(*fen == 'D') marker[r][f++] = 8; else
7406             if(*fen == 'R') marker[r][f++] = 2; else {
7407                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7408               f += s; fen -= s>0;
7409             }
7410             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7411             if(r < 0) break;
7412             fen++;
7413         }
7414         DrawPosition(TRUE, NULL);
7415 }
7416
7417 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7418
7419 void
7420 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7421 {
7422     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7423     Markers *m = (Markers *) closure;
7424     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
7425                                       kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
7426         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7427                          || kind == WhiteCapturesEnPassant
7428                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
7429     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7430 }
7431
7432 static int hoverSavedValid;
7433
7434 void
7435 MarkTargetSquares (int clear)
7436 {
7437   int x, y, sum=0;
7438   if(clear) { // no reason to ever suppress clearing
7439     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7440     hoverSavedValid = 0;
7441     if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
7442   } else {
7443     int capt = 0;
7444     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7445        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7446     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7447     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7448       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7449       if(capt)
7450       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7451     }
7452   }
7453   DrawPosition(FALSE, NULL);
7454 }
7455
7456 int
7457 Explode (Board board, int fromX, int fromY, int toX, int toY)
7458 {
7459     if(gameInfo.variant == VariantAtomic &&
7460        (board[toY][toX] != EmptySquare ||                     // capture?
7461         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7462                          board[fromY][fromX] == BlackPawn   )
7463       )) {
7464         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7465         return TRUE;
7466     }
7467     return FALSE;
7468 }
7469
7470 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7471
7472 int
7473 CanPromote (ChessSquare piece, int y)
7474 {
7475         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7476         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7477         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7478         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7479            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7480           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7481            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7482         return (piece == BlackPawn && y <= zone ||
7483                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7484                 piece == BlackLance && y <= zone ||
7485                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7486 }
7487
7488 void
7489 HoverEvent (int xPix, int yPix, int x, int y)
7490 {
7491         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7492         int r, f;
7493         if(!first.highlight) return;
7494         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7495         if(x == oldX && y == oldY) return; // only do something if we enter new square
7496         oldFromX = fromX; oldFromY = fromY;
7497         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7498           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7499             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7500           hoverSavedValid = 1;
7501         } else if(oldX != x || oldY != y) {
7502           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7503           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7504           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7505             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7506           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7507             char buf[MSG_SIZ];
7508             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7509             SendToProgram(buf, &first);
7510           }
7511           oldX = x; oldY = y;
7512 //        SetHighlights(fromX, fromY, x, y);
7513         }
7514 }
7515
7516 void ReportClick(char *action, int x, int y)
7517 {
7518         char buf[MSG_SIZ]; // Inform engine of what user does
7519         int r, f;
7520         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7521           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7522             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7523         if(!first.highlight || gameMode == EditPosition) return;
7524         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7525         SendToProgram(buf, &first);
7526 }
7527
7528 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7529
7530 void
7531 LeftClick (ClickType clickType, int xPix, int yPix)
7532 {
7533     int x, y;
7534     Boolean saveAnimate;
7535     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
7536     char promoChoice = NULLCHAR;
7537     ChessSquare piece;
7538     static TimeMark lastClickTime, prevClickTime;
7539
7540     if(flashing) return;
7541
7542     x = EventToSquare(xPix, BOARD_WIDTH);
7543     y = EventToSquare(yPix, BOARD_HEIGHT);
7544     if (!flipView && y >= 0) {
7545         y = BOARD_HEIGHT - 1 - y;
7546     }
7547     if (flipView && x >= 0) {
7548         x = BOARD_WIDTH - 1 - x;
7549     }
7550
7551     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7552         static int dummy;
7553         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7554         right = TRUE;
7555         return;
7556     }
7557
7558     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7559
7560     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7561
7562     if (clickType == Press) ErrorPopDown();
7563     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7564
7565     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7566         defaultPromoChoice = promoSweep;
7567         promoSweep = EmptySquare;   // terminate sweep
7568         promoDefaultAltered = TRUE;
7569         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7570     }
7571
7572     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7573         if(clickType == Release) return; // ignore upclick of click-click destination
7574         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7575         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7576         if(gameInfo.holdingsWidth &&
7577                 (WhiteOnMove(currentMove)
7578                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7579                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7580             // click in right holdings, for determining promotion piece
7581             ChessSquare p = boards[currentMove][y][x];
7582             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7583             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7584             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7585                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7586                 fromX = fromY = -1;
7587                 return;
7588             }
7589         }
7590         DrawPosition(FALSE, boards[currentMove]);
7591         return;
7592     }
7593
7594     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7595     if(clickType == Press
7596             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7597               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7598               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7599         return;
7600
7601     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7602         // could be static click on premove from-square: abort premove
7603         gotPremove = 0;
7604         ClearPremoveHighlights();
7605     }
7606
7607     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7608         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7609
7610     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7611         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7612                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7613         defaultPromoChoice = DefaultPromoChoice(side);
7614     }
7615
7616     autoQueen = appData.alwaysPromoteToQueen;
7617
7618     if (fromX == -1) {
7619       int originalY = y;
7620       gatingPiece = EmptySquare;
7621       if (clickType != Press) {
7622         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7623             DragPieceEnd(xPix, yPix); dragging = 0;
7624             DrawPosition(FALSE, NULL);
7625         }
7626         return;
7627       }
7628       doubleClick = FALSE;
7629       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7630         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7631       }
7632       fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
7633       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7634          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7635          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7636             /* First square */
7637             if (OKToStartUserMove(fromX, fromY)) {
7638                 second = 0;
7639                 ReportClick("lift", x, y);
7640                 MarkTargetSquares(0);
7641                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7642                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7643                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7644                     promoSweep = defaultPromoChoice;
7645                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7646                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7647                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7648                 }
7649                 if (appData.highlightDragging) {
7650                     SetHighlights(fromX, fromY, -1, -1);
7651                 } else {
7652                     ClearHighlights();
7653                 }
7654             } else fromX = fromY = -1;
7655             return;
7656         }
7657     }
7658
7659     /* fromX != -1 */
7660     if (clickType == Press && gameMode != EditPosition) {
7661         ChessSquare fromP;
7662         ChessSquare toP;
7663         int frc;
7664
7665         // ignore off-board to clicks
7666         if(y < 0 || x < 0) return;
7667
7668         /* Check if clicking again on the same color piece */
7669         fromP = boards[currentMove][fromY][fromX];
7670         toP = boards[currentMove][y][x];
7671         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7672         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7673             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7674            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7675              WhitePawn <= toP && toP <= WhiteKing &&
7676              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7677              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7678             (BlackPawn <= fromP && fromP <= BlackKing &&
7679              BlackPawn <= toP && toP <= BlackKing &&
7680              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7681              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7682             /* Clicked again on same color piece -- changed his mind */
7683             second = (x == fromX && y == fromY);
7684             killX = killY = kill2X = kill2Y = -1;
7685             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7686                 second = FALSE; // first double-click rather than scond click
7687                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7688             }
7689             promoDefaultAltered = FALSE;
7690            if(!second) MarkTargetSquares(1);
7691            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7692             if (appData.highlightDragging) {
7693                 SetHighlights(x, y, -1, -1);
7694             } else {
7695                 ClearHighlights();
7696             }
7697             if (OKToStartUserMove(x, y)) {
7698                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7699                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7700                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7701                  gatingPiece = boards[currentMove][fromY][fromX];
7702                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7703                 fromX = x;
7704                 fromY = y; dragging = 1;
7705                 if(!second) ReportClick("lift", x, y);
7706                 MarkTargetSquares(0);
7707                 DragPieceBegin(xPix, yPix, FALSE);
7708                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7709                     promoSweep = defaultPromoChoice;
7710                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7711                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7712                 }
7713             }
7714            }
7715            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7716            second = FALSE;
7717         }
7718         // ignore clicks on holdings
7719         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7720     }
7721
7722     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7723         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7724         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7725         return;
7726     }
7727
7728     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7729         DragPieceEnd(xPix, yPix); dragging = 0;
7730         if(clearFlag) {
7731             // a deferred attempt to click-click move an empty square on top of a piece
7732             boards[currentMove][y][x] = EmptySquare;
7733             ClearHighlights();
7734             DrawPosition(FALSE, boards[currentMove]);
7735             fromX = fromY = -1; clearFlag = 0;
7736             return;
7737         }
7738         if (appData.animateDragging) {
7739             /* Undo animation damage if any */
7740             DrawPosition(FALSE, NULL);
7741         }
7742         if (second) {
7743             /* Second up/down in same square; just abort move */
7744             second = 0;
7745             fromX = fromY = -1;
7746             gatingPiece = EmptySquare;
7747             ClearHighlights();
7748             gotPremove = 0;
7749             ClearPremoveHighlights();
7750             MarkTargetSquares(-1);
7751             DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
7752         } else {
7753             /* First upclick in same square; start click-click mode */
7754             SetHighlights(x, y, -1, -1);
7755         }
7756         return;
7757     }
7758
7759     clearFlag = 0;
7760
7761     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7762        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7763         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7764         DisplayMessage(_("only marked squares are legal"),"");
7765         DrawPosition(TRUE, NULL);
7766         return; // ignore to-click
7767     }
7768
7769     /* we now have a different from- and (possibly off-board) to-square */
7770     /* Completed move */
7771     if(!sweepSelecting) {
7772         toX = x;
7773         toY = y;
7774     }
7775
7776     piece = boards[currentMove][fromY][fromX];
7777
7778     saveAnimate = appData.animate;
7779     if (clickType == Press) {
7780         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7781         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7782             // must be Edit Position mode with empty-square selected
7783             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7784             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7785             return;
7786         }
7787         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7788             return;
7789         }
7790         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7791             killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
7792         } else
7793         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7794         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7795           if(appData.sweepSelect) {
7796             promoSweep = defaultPromoChoice;
7797             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7798             selectFlag = 0; lastX = xPix; lastY = yPix;
7799             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7800             saveFlash = appData.flashCount; appData.flashCount = 0;
7801             Sweep(0); // Pawn that is going to promote: preview promotion piece
7802             sweepSelecting = 1;
7803             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7804             MarkTargetSquares(1);
7805           }
7806           return; // promo popup appears on up-click
7807         }
7808         /* Finish clickclick move */
7809         if (appData.animate || appData.highlightLastMove) {
7810             SetHighlights(fromX, fromY, toX, toY);
7811         } else {
7812             ClearHighlights();
7813         }
7814         MarkTargetSquares(1);
7815     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7816         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7817         *promoRestrict = 0; appData.flashCount = saveFlash;
7818         if (appData.animate || appData.highlightLastMove) {
7819             SetHighlights(fromX, fromY, toX, toY);
7820         } else {
7821             ClearHighlights();
7822         }
7823         MarkTargetSquares(1);
7824     } else {
7825 #if 0
7826 // [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
7827         /* Finish drag move */
7828         if (appData.highlightLastMove) {
7829             SetHighlights(fromX, fromY, toX, toY);
7830         } else {
7831             ClearHighlights();
7832         }
7833 #endif
7834         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7835           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7836         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7837         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7838           dragging *= 2;            // flag button-less dragging if we are dragging
7839           MarkTargetSquares(1);
7840           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7841           else {
7842             kill2X = killX; kill2Y = killY;
7843             killX = x; killY = y;     // remember this square as intermediate
7844             ReportClick("put", x, y); // and inform engine
7845             ReportClick("lift", x, y);
7846             MarkTargetSquares(0);
7847             return;
7848           }
7849         }
7850         DragPieceEnd(xPix, yPix); dragging = 0;
7851         /* Don't animate move and drag both */
7852         appData.animate = FALSE;
7853         MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
7854     }
7855
7856     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7857     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7858         ChessSquare piece = boards[currentMove][fromY][fromX];
7859         if(gameMode == EditPosition && piece != EmptySquare &&
7860            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7861             int n;
7862
7863             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7864                 n = PieceToNumber(piece - (int)BlackPawn);
7865                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7866                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7867                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7868             } else
7869             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7870                 n = PieceToNumber(piece);
7871                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7872                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7873                 boards[currentMove][n][BOARD_WIDTH-2]++;
7874             }
7875             boards[currentMove][fromY][fromX] = EmptySquare;
7876         }
7877         ClearHighlights();
7878         fromX = fromY = -1;
7879         MarkTargetSquares(1);
7880         DrawPosition(TRUE, boards[currentMove]);
7881         return;
7882     }
7883
7884     // off-board moves should not be highlighted
7885     if(x < 0 || y < 0) ClearHighlights();
7886     else ReportClick("put", x, y);
7887
7888     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7889
7890     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7891
7892     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7893         SetHighlights(fromX, fromY, toX, toY);
7894         MarkTargetSquares(1);
7895         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7896             // [HGM] super: promotion to captured piece selected from holdings
7897             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7898             promotionChoice = TRUE;
7899             // kludge follows to temporarily execute move on display, without promoting yet
7900             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7901             boards[currentMove][toY][toX] = p;
7902             DrawPosition(FALSE, boards[currentMove]);
7903             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7904             boards[currentMove][toY][toX] = q;
7905             DisplayMessage("Click in holdings to choose piece", "");
7906             return;
7907         }
7908         PromotionPopUp(promoChoice);
7909     } else {
7910         int oldMove = currentMove;
7911         flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
7912         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7913         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7914         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7915         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7916            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7917             DrawPosition(TRUE, boards[currentMove]);
7918         fromX = fromY = -1;
7919         flashing = 0;
7920     }
7921     appData.animate = saveAnimate;
7922     if (appData.animate || appData.animateDragging) {
7923         /* Undo animation damage if needed */
7924 //      DrawPosition(FALSE, NULL);
7925     }
7926 }
7927
7928 int
7929 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7930 {   // front-end-free part taken out of PieceMenuPopup
7931     int whichMenu; int xSqr, ySqr;
7932
7933     if(seekGraphUp) { // [HGM] seekgraph
7934         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7935         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7936         return -2;
7937     }
7938
7939     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7940          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7941         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7942         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7943         if(action == Press)   {
7944             originalFlip = flipView;
7945             flipView = !flipView; // temporarily flip board to see game from partners perspective
7946             DrawPosition(TRUE, partnerBoard);
7947             DisplayMessage(partnerStatus, "");
7948             partnerUp = TRUE;
7949         } else if(action == Release) {
7950             flipView = originalFlip;
7951             DrawPosition(TRUE, boards[currentMove]);
7952             partnerUp = FALSE;
7953         }
7954         return -2;
7955     }
7956
7957     xSqr = EventToSquare(x, BOARD_WIDTH);
7958     ySqr = EventToSquare(y, BOARD_HEIGHT);
7959     if (action == Release) {
7960         if(pieceSweep != EmptySquare) {
7961             EditPositionMenuEvent(pieceSweep, toX, toY);
7962             pieceSweep = EmptySquare;
7963         } else UnLoadPV(); // [HGM] pv
7964     }
7965     if (action != Press) return -2; // return code to be ignored
7966     switch (gameMode) {
7967       case IcsExamining:
7968         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7969       case EditPosition:
7970         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7971         if (xSqr < 0 || ySqr < 0) return -1;
7972         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7973         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7974         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7975         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7976         NextPiece(0);
7977         return 2; // grab
7978       case IcsObserving:
7979         if(!appData.icsEngineAnalyze) return -1;
7980       case IcsPlayingWhite:
7981       case IcsPlayingBlack:
7982         if(!appData.zippyPlay) goto noZip;
7983       case AnalyzeMode:
7984       case AnalyzeFile:
7985       case MachinePlaysWhite:
7986       case MachinePlaysBlack:
7987       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7988         if (!appData.dropMenu) {
7989           LoadPV(x, y);
7990           return 2; // flag front-end to grab mouse events
7991         }
7992         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7993            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7994       case EditGame:
7995       noZip:
7996         if (xSqr < 0 || ySqr < 0) return -1;
7997         if (!appData.dropMenu || appData.testLegality &&
7998             gameInfo.variant != VariantBughouse &&
7999             gameInfo.variant != VariantCrazyhouse) return -1;
8000         whichMenu = 1; // drop menu
8001         break;
8002       default:
8003         return -1;
8004     }
8005
8006     if (((*fromX = xSqr) < 0) ||
8007         ((*fromY = ySqr) < 0)) {
8008         *fromX = *fromY = -1;
8009         return -1;
8010     }
8011     if (flipView)
8012       *fromX = BOARD_WIDTH - 1 - *fromX;
8013     else
8014       *fromY = BOARD_HEIGHT - 1 - *fromY;
8015
8016     return whichMenu;
8017 }
8018
8019 void
8020 Wheel (int dir, int x, int y)
8021 {
8022     if(gameMode == EditPosition) {
8023         int xSqr = EventToSquare(x, BOARD_WIDTH);
8024         int ySqr = EventToSquare(y, BOARD_HEIGHT);
8025         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8026         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8027         do {
8028             boards[currentMove][ySqr][xSqr] += dir;
8029             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8030             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8031         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8032         DrawPosition(FALSE, boards[currentMove]);
8033     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8034 }
8035
8036 void
8037 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8038 {
8039 //    char * hint = lastHint;
8040     FrontEndProgramStats stats;
8041
8042     stats.which = cps == &first ? 0 : 1;
8043     stats.depth = cpstats->depth;
8044     stats.nodes = cpstats->nodes;
8045     stats.score = cpstats->score;
8046     stats.time = cpstats->time;
8047     stats.pv = cpstats->movelist;
8048     stats.hint = lastHint;
8049     stats.an_move_index = 0;
8050     stats.an_move_count = 0;
8051
8052     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8053         stats.hint = cpstats->move_name;
8054         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8055         stats.an_move_count = cpstats->nr_moves;
8056     }
8057
8058     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
8059
8060     if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
8061         && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
8062
8063     SetProgramStats( &stats );
8064 }
8065
8066 void
8067 ClearEngineOutputPane (int which)
8068 {
8069     static FrontEndProgramStats dummyStats;
8070     dummyStats.which = which;
8071     dummyStats.pv = "#";
8072     SetProgramStats( &dummyStats );
8073 }
8074
8075 #define MAXPLAYERS 500
8076
8077 char *
8078 TourneyStandings (int display)
8079 {
8080     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8081     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8082     char result, *p, *names[MAXPLAYERS];
8083
8084     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8085         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8086     names[0] = p = strdup(appData.participants);
8087     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8088
8089     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8090
8091     while(result = appData.results[nr]) {
8092         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8093         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8094         wScore = bScore = 0;
8095         switch(result) {
8096           case '+': wScore = 2; break;
8097           case '-': bScore = 2; break;
8098           case '=': wScore = bScore = 1; break;
8099           case ' ':
8100           case '*': return strdup("busy"); // tourney not finished
8101         }
8102         score[w] += wScore;
8103         score[b] += bScore;
8104         games[w]++;
8105         games[b]++;
8106         nr++;
8107     }
8108     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8109     for(w=0; w<nPlayers; w++) {
8110         bScore = -1;
8111         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8112         ranking[w] = b; points[w] = bScore; score[b] = -2;
8113     }
8114     p = malloc(nPlayers*34+1);
8115     for(w=0; w<nPlayers && w<display; w++)
8116         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8117     free(names[0]);
8118     return p;
8119 }
8120
8121 void
8122 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8123 {       // count all piece types
8124         int p, f, r;
8125         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8126         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8127         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8128                 p = board[r][f];
8129                 pCnt[p]++;
8130                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8131                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8132                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8133                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8134                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8135                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8136         }
8137 }
8138
8139 int
8140 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8141 {
8142         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8143         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8144
8145         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8146         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8147         if(myPawns == 2 && nMine == 3) // KPP
8148             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8149         if(myPawns == 1 && nMine == 2) // KP
8150             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8151         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8152             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8153         if(myPawns) return FALSE;
8154         if(pCnt[WhiteRook+side])
8155             return pCnt[BlackRook-side] ||
8156                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8157                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8158                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8159         if(pCnt[WhiteCannon+side]) {
8160             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8161             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8162         }
8163         if(pCnt[WhiteKnight+side])
8164             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8165         return FALSE;
8166 }
8167
8168 int
8169 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8170 {
8171         VariantClass v = gameInfo.variant;
8172
8173         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8174         if(v == VariantShatranj) return TRUE; // always winnable through baring
8175         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8176         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8177
8178         if(v == VariantXiangqi) {
8179                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8180
8181                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8182                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8183                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8184                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8185                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8186                 if(stale) // we have at least one last-rank P plus perhaps C
8187                     return majors // KPKX
8188                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8189                 else // KCA*E*
8190                     return pCnt[WhiteFerz+side] // KCAK
8191                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8192                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8193                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8194
8195         } else if(v == VariantKnightmate) {
8196                 if(nMine == 1) return FALSE;
8197                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8198         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8199                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8200
8201                 if(nMine == 1) return FALSE; // bare King
8202                 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
8203                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8204                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8205                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8206                 if(pCnt[WhiteKnight+side])
8207                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8208                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8209                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8210                 if(nBishops)
8211                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8212                 if(pCnt[WhiteAlfil+side])
8213                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8214                 if(pCnt[WhiteWazir+side])
8215                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8216         }
8217
8218         return TRUE;
8219 }
8220
8221 int
8222 CompareWithRights (Board b1, Board b2)
8223 {
8224     int rights = 0;
8225     if(!CompareBoards(b1, b2)) return FALSE;
8226     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8227     /* compare castling rights */
8228     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8229            rights++; /* King lost rights, while rook still had them */
8230     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8231         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8232            rights++; /* but at least one rook lost them */
8233     }
8234     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8235            rights++;
8236     if( b1[CASTLING][5] != NoRights ) {
8237         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8238            rights++;
8239     }
8240     return rights == 0;
8241 }
8242
8243 int
8244 Adjudicate (ChessProgramState *cps)
8245 {       // [HGM] some adjudications useful with buggy engines
8246         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8247         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8248         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8249         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8250         int k, drop, count = 0; static int bare = 1;
8251         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8252         Boolean canAdjudicate = !appData.icsActive;
8253
8254         // most tests only when we understand the game, i.e. legality-checking on
8255             if( appData.testLegality )
8256             {   /* [HGM] Some more adjudications for obstinate engines */
8257                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8258                 static int moveCount = 6;
8259                 ChessMove result;
8260                 char *reason = NULL;
8261
8262                 /* Count what is on board. */
8263                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8264
8265                 /* Some material-based adjudications that have to be made before stalemate test */
8266                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8267                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8268                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8269                      if(canAdjudicate && appData.checkMates) {
8270                          if(engineOpponent)
8271                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8272                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8273                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8274                          return 1;
8275                      }
8276                 }
8277
8278                 /* Bare King in Shatranj (loses) or Losers (wins) */
8279                 if( nrW == 1 || nrB == 1) {
8280                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8281                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8282                      if(canAdjudicate && appData.checkMates) {
8283                          if(engineOpponent)
8284                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8285                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8286                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8287                          return 1;
8288                      }
8289                   } else
8290                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8291                   {    /* bare King */
8292                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8293                         if(canAdjudicate && appData.checkMates) {
8294                             /* but only adjudicate if adjudication enabled */
8295                             if(engineOpponent)
8296                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8297                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8298                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8299                             return 1;
8300                         }
8301                   }
8302                 } else bare = 1;
8303
8304
8305             // don't wait for engine to announce game end if we can judge ourselves
8306             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8307               case MT_CHECK:
8308                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8309                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8310                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8311                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8312                             checkCnt++;
8313                         if(checkCnt >= 2) {
8314                             reason = "Xboard adjudication: 3rd check";
8315                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8316                             break;
8317                         }
8318                     }
8319                 }
8320               case MT_NONE:
8321               default:
8322                 break;
8323               case MT_STEALMATE:
8324               case MT_STALEMATE:
8325               case MT_STAINMATE:
8326                 reason = "Xboard adjudication: Stalemate";
8327                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8328                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8329                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8330                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8331                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8332                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8333                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8334                                                                         EP_CHECKMATE : EP_WINS);
8335                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8336                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8337                 }
8338                 break;
8339               case MT_CHECKMATE:
8340                 reason = "Xboard adjudication: Checkmate";
8341                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8342                 if(gameInfo.variant == VariantShogi) {
8343                     if(forwardMostMove > backwardMostMove
8344                        && moveList[forwardMostMove-1][1] == '@'
8345                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8346                         reason = "XBoard adjudication: pawn-drop mate";
8347                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8348                     }
8349                 }
8350                 break;
8351             }
8352
8353                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8354                     case EP_STALEMATE:
8355                         result = GameIsDrawn; break;
8356                     case EP_CHECKMATE:
8357                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8358                     case EP_WINS:
8359                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8360                     default:
8361                         result = EndOfFile;
8362                 }
8363                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8364                     if(engineOpponent)
8365                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8366                     GameEnds( result, reason, GE_XBOARD );
8367                     return 1;
8368                 }
8369
8370                 /* Next absolutely insufficient mating material. */
8371                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8372                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8373                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8374
8375                      /* always flag draws, for judging claims */
8376                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8377
8378                      if(canAdjudicate && appData.materialDraws) {
8379                          /* but only adjudicate them if adjudication enabled */
8380                          if(engineOpponent) {
8381                            SendToProgram("force\n", engineOpponent); // suppress reply
8382                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8383                          }
8384                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8385                          return 1;
8386                      }
8387                 }
8388
8389                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8390                 if(gameInfo.variant == VariantXiangqi ?
8391                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8392                  : nrW + nrB == 4 &&
8393                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8394                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8395                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8396                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8397                    ) ) {
8398                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8399                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8400                           if(engineOpponent) {
8401                             SendToProgram("force\n", engineOpponent); // suppress reply
8402                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8403                           }
8404                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8405                           return 1;
8406                      }
8407                 } else moveCount = 6;
8408             }
8409
8410         // Repetition draws and 50-move rule can be applied independently of legality testing
8411
8412                 /* Check for rep-draws */
8413                 count = 0;
8414                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8415                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8416                 for(k = forwardMostMove-2;
8417                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8418                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8419                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8420                     k-=2)
8421                 {   int rights=0;
8422                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8423                         /* compare castling rights */
8424                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8425                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8426                                 rights++; /* King lost rights, while rook still had them */
8427                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8428                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8429                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8430                                    rights++; /* but at least one rook lost them */
8431                         }
8432                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8433                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8434                                 rights++;
8435                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8436                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8437                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8438                                    rights++;
8439                         }
8440                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8441                             && appData.drawRepeats > 1) {
8442                              /* adjudicate after user-specified nr of repeats */
8443                              int result = GameIsDrawn;
8444                              char *details = "XBoard adjudication: repetition draw";
8445                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8446                                 // [HGM] xiangqi: check for forbidden perpetuals
8447                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8448                                 for(m=forwardMostMove; m>k; m-=2) {
8449                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8450                                         ourPerpetual = 0; // the current mover did not always check
8451                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8452                                         hisPerpetual = 0; // the opponent did not always check
8453                                 }
8454                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8455                                                                         ourPerpetual, hisPerpetual);
8456                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8457                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8458                                     details = "Xboard adjudication: perpetual checking";
8459                                 } else
8460                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8461                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8462                                 } else
8463                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8464                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8465                                         result = BlackWins;
8466                                         details = "Xboard adjudication: repetition";
8467                                     }
8468                                 } else // it must be XQ
8469                                 // Now check for perpetual chases
8470                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8471                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8472                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8473                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8474                                         static char resdet[MSG_SIZ];
8475                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8476                                         details = resdet;
8477                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8478                                     } else
8479                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8480                                         break; // Abort repetition-checking loop.
8481                                 }
8482                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8483                              }
8484                              if(engineOpponent) {
8485                                SendToProgram("force\n", engineOpponent); // suppress reply
8486                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8487                              }
8488                              GameEnds( result, details, GE_XBOARD );
8489                              return 1;
8490                         }
8491                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8492                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8493                     }
8494                 }
8495
8496                 /* Now we test for 50-move draws. Determine ply count */
8497                 count = forwardMostMove;
8498                 /* look for last irreversble move */
8499                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8500                     count--;
8501                 /* if we hit starting position, add initial plies */
8502                 if( count == backwardMostMove )
8503                     count -= initialRulePlies;
8504                 count = forwardMostMove - count;
8505                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8506                         // adjust reversible move counter for checks in Xiangqi
8507                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8508                         if(i < backwardMostMove) i = backwardMostMove;
8509                         while(i <= forwardMostMove) {
8510                                 lastCheck = inCheck; // check evasion does not count
8511                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8512                                 if(inCheck || lastCheck) count--; // check does not count
8513                                 i++;
8514                         }
8515                 }
8516                 if( count >= 100)
8517                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8518                          /* this is used to judge if draw claims are legal */
8519                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8520                          if(engineOpponent) {
8521                            SendToProgram("force\n", engineOpponent); // suppress reply
8522                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8523                          }
8524                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8525                          return 1;
8526                 }
8527
8528                 /* if draw offer is pending, treat it as a draw claim
8529                  * when draw condition present, to allow engines a way to
8530                  * claim draws before making their move to avoid a race
8531                  * condition occurring after their move
8532                  */
8533                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8534                          char *p = NULL;
8535                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8536                              p = "Draw claim: 50-move rule";
8537                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8538                              p = "Draw claim: 3-fold repetition";
8539                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8540                              p = "Draw claim: insufficient mating material";
8541                          if( p != NULL && canAdjudicate) {
8542                              if(engineOpponent) {
8543                                SendToProgram("force\n", engineOpponent); // suppress reply
8544                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8545                              }
8546                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8547                              return 1;
8548                          }
8549                 }
8550
8551                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8552                     if(engineOpponent) {
8553                       SendToProgram("force\n", engineOpponent); // suppress reply
8554                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8555                     }
8556                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8557                     return 1;
8558                 }
8559         return 0;
8560 }
8561
8562 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8563 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8564 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8565
8566 static int
8567 BitbaseProbe ()
8568 {
8569     int pieces[10], squares[10], cnt=0, r, f, res;
8570     static int loaded;
8571     static PPROBE_EGBB probeBB;
8572     if(!appData.testLegality) return 10;
8573     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8574     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8575     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8576     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8577         ChessSquare piece = boards[forwardMostMove][r][f];
8578         int black = (piece >= BlackPawn);
8579         int type = piece - black*BlackPawn;
8580         if(piece == EmptySquare) continue;
8581         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8582         if(type == WhiteKing) type = WhiteQueen + 1;
8583         type = egbbCode[type];
8584         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8585         pieces[cnt] = type + black*6;
8586         if(++cnt > 5) return 11;
8587     }
8588     pieces[cnt] = squares[cnt] = 0;
8589     // probe EGBB
8590     if(loaded == 2) return 13; // loading failed before
8591     if(loaded == 0) {
8592         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8593         HMODULE lib;
8594         PLOAD_EGBB loadBB;
8595         loaded = 2; // prepare for failure
8596         if(!path) return 13; // no egbb installed
8597         strncpy(buf, path + 8, MSG_SIZ);
8598         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8599         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8600         lib = LoadLibrary(buf);
8601         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8602         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8603         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8604         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8605         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8606         loaded = 1; // success!
8607     }
8608     res = probeBB(forwardMostMove & 1, pieces, squares);
8609     return res > 0 ? 1 : res < 0 ? -1 : 0;
8610 }
8611
8612 char *
8613 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8614 {   // [HGM] book: this routine intercepts moves to simulate book replies
8615     char *bookHit = NULL;
8616
8617     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8618         char buf[MSG_SIZ];
8619         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8620         SendToProgram(buf, cps);
8621     }
8622     //first determine if the incoming move brings opponent into his book
8623     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8624         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8625     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8626     if(bookHit != NULL && !cps->bookSuspend) {
8627         // make sure opponent is not going to reply after receiving move to book position
8628         SendToProgram("force\n", cps);
8629         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8630     }
8631     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8632     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8633     // now arrange restart after book miss
8634     if(bookHit) {
8635         // after a book hit we never send 'go', and the code after the call to this routine
8636         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8637         char buf[MSG_SIZ], *move = bookHit;
8638         if(cps->useSAN) {
8639             int fromX, fromY, toX, toY;
8640             char promoChar;
8641             ChessMove moveType;
8642             move = buf + 30;
8643             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8644                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8645                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8646                                     PosFlags(forwardMostMove),
8647                                     fromY, fromX, toY, toX, promoChar, move);
8648             } else {
8649                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8650                 bookHit = NULL;
8651             }
8652         }
8653         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8654         SendToProgram(buf, cps);
8655         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8656     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8657         SendToProgram("go\n", cps);
8658         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8659     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8660         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8661             SendToProgram("go\n", cps);
8662         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8663     }
8664     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8665 }
8666
8667 int
8668 LoadError (char *errmess, ChessProgramState *cps)
8669 {   // unloads engine and switches back to -ncp mode if it was first
8670     if(cps->initDone) return FALSE;
8671     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8672     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8673     cps->pr = NoProc;
8674     if(cps == &first) {
8675         appData.noChessProgram = TRUE;
8676         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8677         gameMode = BeginningOfGame; ModeHighlight();
8678         SetNCPMode();
8679     }
8680     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8681     DisplayMessage("", ""); // erase waiting message
8682     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8683     return TRUE;
8684 }
8685
8686 char *savedMessage;
8687 ChessProgramState *savedState;
8688 void
8689 DeferredBookMove (void)
8690 {
8691         if(savedState->lastPing != savedState->lastPong)
8692                     ScheduleDelayedEvent(DeferredBookMove, 10);
8693         else
8694         HandleMachineMove(savedMessage, savedState);
8695 }
8696
8697 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8698 static ChessProgramState *stalledEngine;
8699 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8700
8701 void
8702 HandleMachineMove (char *message, ChessProgramState *cps)
8703 {
8704     static char firstLeg[20], legs;
8705     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8706     char realname[MSG_SIZ];
8707     int fromX, fromY, toX, toY;
8708     ChessMove moveType;
8709     char promoChar, roar;
8710     char *p, *pv=buf1;
8711     int oldError;
8712     char *bookHit;
8713
8714     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8715         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8716         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8717             DisplayError(_("Invalid pairing from pairing engine"), 0);
8718             return;
8719         }
8720         pairingReceived = 1;
8721         NextMatchGame();
8722         return; // Skim the pairing messages here.
8723     }
8724
8725     oldError = cps->userError; cps->userError = 0;
8726
8727 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8728     /*
8729      * Kludge to ignore BEL characters
8730      */
8731     while (*message == '\007') message++;
8732
8733     /*
8734      * [HGM] engine debug message: ignore lines starting with '#' character
8735      */
8736     if(cps->debug && *message == '#') return;
8737
8738     /*
8739      * Look for book output
8740      */
8741     if (cps == &first && bookRequested) {
8742         if (message[0] == '\t' || message[0] == ' ') {
8743             /* Part of the book output is here; append it */
8744             strcat(bookOutput, message);
8745             strcat(bookOutput, "  \n");
8746             return;
8747         } else if (bookOutput[0] != NULLCHAR) {
8748             /* All of book output has arrived; display it */
8749             char *p = bookOutput;
8750             while (*p != NULLCHAR) {
8751                 if (*p == '\t') *p = ' ';
8752                 p++;
8753             }
8754             DisplayInformation(bookOutput);
8755             bookRequested = FALSE;
8756             /* Fall through to parse the current output */
8757         }
8758     }
8759
8760     /*
8761      * Look for machine move.
8762      */
8763     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8764         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8765     {
8766         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8767             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8768             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8769             stalledEngine = cps;
8770             if(appData.ponderNextMove) { // bring opponent out of ponder
8771                 if(gameMode == TwoMachinesPlay) {
8772                     if(cps->other->pause)
8773                         PauseEngine(cps->other);
8774                     else
8775                         SendToProgram("easy\n", cps->other);
8776                 }
8777             }
8778             StopClocks();
8779             return;
8780         }
8781
8782       if(cps->usePing) {
8783
8784         /* This method is only useful on engines that support ping */
8785         if(abortEngineThink) {
8786             if (appData.debugMode) {
8787                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8788             }
8789             SendToProgram("undo\n", cps);
8790             return;
8791         }
8792
8793         if (cps->lastPing != cps->lastPong) {
8794             /* Extra move from before last new; ignore */
8795             if (appData.debugMode) {
8796                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8797             }
8798           return;
8799         }
8800
8801       } else {
8802
8803         int machineWhite = FALSE;
8804
8805         switch (gameMode) {
8806           case BeginningOfGame:
8807             /* Extra move from before last reset; ignore */
8808             if (appData.debugMode) {
8809                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8810             }
8811             return;
8812
8813           case EndOfGame:
8814           case IcsIdle:
8815           default:
8816             /* Extra move after we tried to stop.  The mode test is
8817                not a reliable way of detecting this problem, but it's
8818                the best we can do on engines that don't support ping.
8819             */
8820             if (appData.debugMode) {
8821                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8822                         cps->which, gameMode);
8823             }
8824             SendToProgram("undo\n", cps);
8825             return;
8826
8827           case MachinePlaysWhite:
8828           case IcsPlayingWhite:
8829             machineWhite = TRUE;
8830             break;
8831
8832           case MachinePlaysBlack:
8833           case IcsPlayingBlack:
8834             machineWhite = FALSE;
8835             break;
8836
8837           case TwoMachinesPlay:
8838             machineWhite = (cps->twoMachinesColor[0] == 'w');
8839             break;
8840         }
8841         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8842             if (appData.debugMode) {
8843                 fprintf(debugFP,
8844                         "Ignoring move out of turn by %s, gameMode %d"
8845                         ", forwardMost %d\n",
8846                         cps->which, gameMode, forwardMostMove);
8847             }
8848             return;
8849         }
8850       }
8851
8852         if(cps->alphaRank) AlphaRank(machineMove, 4);
8853
8854         // [HGM] lion: (some very limited) support for Alien protocol
8855         killX = killY = kill2X = kill2Y = -1;
8856         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8857             if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
8858             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8859             return;
8860         }
8861         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8862             char *q = strchr(p+1, ',');            // second comma?
8863             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8864             if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
8865             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8866         }
8867         if(firstLeg[0]) { // there was a previous leg;
8868             // only support case where same piece makes two step
8869             char buf[20], *p = machineMove+1, *q = buf+1, f;
8870             safeStrCpy(buf, machineMove, 20);
8871             while(isdigit(*q)) q++; // find start of to-square
8872             safeStrCpy(machineMove, firstLeg, 20);
8873             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8874             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
8875             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)
8876             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8877             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8878             firstLeg[0] = NULLCHAR; legs = 0;
8879         }
8880
8881         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8882                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8883             /* Machine move could not be parsed; ignore it. */
8884           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8885                     machineMove, _(cps->which));
8886             DisplayMoveError(buf1);
8887             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8888                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8889             if (gameMode == TwoMachinesPlay) {
8890               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8891                        buf1, GE_XBOARD);
8892             }
8893             return;
8894         }
8895
8896         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8897         /* So we have to redo legality test with true e.p. status here,  */
8898         /* to make sure an illegal e.p. capture does not slip through,   */
8899         /* to cause a forfeit on a justified illegal-move complaint      */
8900         /* of the opponent.                                              */
8901         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8902            ChessMove moveType;
8903            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8904                              fromY, fromX, toY, toX, promoChar);
8905             if(moveType == IllegalMove) {
8906               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8907                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8908                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8909                            buf1, GE_XBOARD);
8910                 return;
8911            } else if(!appData.fischerCastling)
8912            /* [HGM] Kludge to handle engines that send FRC-style castling
8913               when they shouldn't (like TSCP-Gothic) */
8914            switch(moveType) {
8915              case WhiteASideCastleFR:
8916              case BlackASideCastleFR:
8917                toX+=2;
8918                currentMoveString[2]++;
8919                break;
8920              case WhiteHSideCastleFR:
8921              case BlackHSideCastleFR:
8922                toX--;
8923                currentMoveString[2]--;
8924                break;
8925              default: ; // nothing to do, but suppresses warning of pedantic compilers
8926            }
8927         }
8928         hintRequested = FALSE;
8929         lastHint[0] = NULLCHAR;
8930         bookRequested = FALSE;
8931         /* Program may be pondering now */
8932         cps->maybeThinking = TRUE;
8933         if (cps->sendTime == 2) cps->sendTime = 1;
8934         if (cps->offeredDraw) cps->offeredDraw--;
8935
8936         /* [AS] Save move info*/
8937         pvInfoList[ forwardMostMove ].score = programStats.score;
8938         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8939         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8940
8941         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8942
8943         /* Test suites abort the 'game' after one move */
8944         if(*appData.finger) {
8945            static FILE *f;
8946            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8947            if(!f) f = fopen(appData.finger, "w");
8948            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8949            else { DisplayFatalError("Bad output file", errno, 0); return; }
8950            free(fen);
8951            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8952         }
8953         if(appData.epd) {
8954            if(solvingTime >= 0) {
8955               snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
8956               totalTime += solvingTime; first.matchWins++; solvingTime = -1;
8957            } else {
8958               snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
8959               if(solvingTime == -2) second.matchWins++;
8960            }
8961            OutputKibitz(2, buf1);
8962            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8963         }
8964
8965         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8966         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8967             int count = 0;
8968
8969             while( count < adjudicateLossPlies ) {
8970                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8971
8972                 if( count & 1 ) {
8973                     score = -score; /* Flip score for winning side */
8974                 }
8975
8976                 if( score > appData.adjudicateLossThreshold ) {
8977                     break;
8978                 }
8979
8980                 count++;
8981             }
8982
8983             if( count >= adjudicateLossPlies ) {
8984                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8985
8986                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8987                     "Xboard adjudication",
8988                     GE_XBOARD );
8989
8990                 return;
8991             }
8992         }
8993
8994         if(Adjudicate(cps)) {
8995             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8996             return; // [HGM] adjudicate: for all automatic game ends
8997         }
8998
8999 #if ZIPPY
9000         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
9001             first.initDone) {
9002           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
9003                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
9004                 SendToICS("draw ");
9005                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9006           }
9007           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
9008           ics_user_moved = 1;
9009           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
9010                 char buf[3*MSG_SIZ];
9011
9012                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
9013                         programStats.score / 100.,
9014                         programStats.depth,
9015                         programStats.time / 100.,
9016                         (unsigned int)programStats.nodes,
9017                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
9018                         programStats.movelist);
9019                 SendToICS(buf);
9020           }
9021         }
9022 #endif
9023
9024         /* [AS] Clear stats for next move */
9025         ClearProgramStats();
9026         thinkOutput[0] = NULLCHAR;
9027         hiddenThinkOutputState = 0;
9028
9029         bookHit = NULL;
9030         if (gameMode == TwoMachinesPlay) {
9031             /* [HGM] relaying draw offers moved to after reception of move */
9032             /* and interpreting offer as claim if it brings draw condition */
9033             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9034                 SendToProgram("draw\n", cps->other);
9035             }
9036             if (cps->other->sendTime) {
9037                 SendTimeRemaining(cps->other,
9038                                   cps->other->twoMachinesColor[0] == 'w');
9039             }
9040             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9041             if (firstMove && !bookHit) {
9042                 firstMove = FALSE;
9043                 if (cps->other->useColors) {
9044                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9045                 }
9046                 SendToProgram("go\n", cps->other);
9047             }
9048             cps->other->maybeThinking = TRUE;
9049         }
9050
9051         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9052
9053         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9054
9055         if (!pausing && appData.ringBellAfterMoves) {
9056             if(!roar) RingBell();
9057         }
9058
9059         /*
9060          * Reenable menu items that were disabled while
9061          * machine was thinking
9062          */
9063         if (gameMode != TwoMachinesPlay)
9064             SetUserThinkingEnables();
9065
9066         // [HGM] book: after book hit opponent has received move and is now in force mode
9067         // force the book reply into it, and then fake that it outputted this move by jumping
9068         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9069         if(bookHit) {
9070                 static char bookMove[MSG_SIZ]; // a bit generous?
9071
9072                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9073                 strcat(bookMove, bookHit);
9074                 message = bookMove;
9075                 cps = cps->other;
9076                 programStats.nodes = programStats.depth = programStats.time =
9077                 programStats.score = programStats.got_only_move = 0;
9078                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9079
9080                 if(cps->lastPing != cps->lastPong) {
9081                     savedMessage = message; // args for deferred call
9082                     savedState = cps;
9083                     ScheduleDelayedEvent(DeferredBookMove, 10);
9084                     return;
9085                 }
9086                 goto FakeBookMove;
9087         }
9088
9089         return;
9090     }
9091
9092     /* Set special modes for chess engines.  Later something general
9093      *  could be added here; for now there is just one kludge feature,
9094      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9095      *  when "xboard" is given as an interactive command.
9096      */
9097     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9098         cps->useSigint = FALSE;
9099         cps->useSigterm = FALSE;
9100     }
9101     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9102       ParseFeatures(message+8, cps);
9103       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9104     }
9105
9106     if (!strncmp(message, "setup ", 6) && 
9107         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9108           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9109                                         ) { // [HGM] allow first engine to define opening position
9110       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9111       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9112       *buf = NULLCHAR;
9113       if(sscanf(message, "setup (%s", buf) == 1) {
9114         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9115         ASSIGN(appData.pieceToCharTable, buf);
9116       }
9117       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9118       if(dummy >= 3) {
9119         while(message[s] && message[s++] != ' ');
9120         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9121            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9122             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9123             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9124           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9125           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9126           startedFromSetupPosition = FALSE;
9127         }
9128       }
9129       if(startedFromSetupPosition) return;
9130       ParseFEN(boards[0], &dummy, message+s, FALSE);
9131       DrawPosition(TRUE, boards[0]);
9132       CopyBoard(initialPosition, boards[0]);
9133       startedFromSetupPosition = TRUE;
9134       return;
9135     }
9136     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9137       ChessSquare piece = WhitePawn;
9138       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9139       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9140       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9141       piece += CharToPiece(ID & 255) - WhitePawn;
9142       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9143       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9144       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9145       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9146       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9147       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9148                                                && gameInfo.variant != VariantGreat
9149                                                && gameInfo.variant != VariantFairy    ) return;
9150       if(piece < EmptySquare) {
9151         pieceDefs = TRUE;
9152         ASSIGN(pieceDesc[piece], buf1);
9153         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9154       }
9155       return;
9156     }
9157     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9158       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9159       Sweep(0);
9160       return;
9161     }
9162     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9163      * want this, I was asked to put it in, and obliged.
9164      */
9165     if (!strncmp(message, "setboard ", 9)) {
9166         Board initial_position;
9167
9168         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9169
9170         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9171             DisplayError(_("Bad FEN received from engine"), 0);
9172             return ;
9173         } else {
9174            Reset(TRUE, FALSE);
9175            CopyBoard(boards[0], initial_position);
9176            initialRulePlies = FENrulePlies;
9177            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9178            else gameMode = MachinePlaysBlack;
9179            DrawPosition(FALSE, boards[currentMove]);
9180         }
9181         return;
9182     }
9183
9184     /*
9185      * Look for communication commands
9186      */
9187     if (!strncmp(message, "telluser ", 9)) {
9188         if(message[9] == '\\' && message[10] == '\\')
9189             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9190         PlayTellSound();
9191         DisplayNote(message + 9);
9192         return;
9193     }
9194     if (!strncmp(message, "tellusererror ", 14)) {
9195         cps->userError = 1;
9196         if(message[14] == '\\' && message[15] == '\\')
9197             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9198         PlayTellSound();
9199         DisplayError(message + 14, 0);
9200         return;
9201     }
9202     if (!strncmp(message, "tellopponent ", 13)) {
9203       if (appData.icsActive) {
9204         if (loggedOn) {
9205           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9206           SendToICS(buf1);
9207         }
9208       } else {
9209         DisplayNote(message + 13);
9210       }
9211       return;
9212     }
9213     if (!strncmp(message, "tellothers ", 11)) {
9214       if (appData.icsActive) {
9215         if (loggedOn) {
9216           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9217           SendToICS(buf1);
9218         }
9219       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9220       return;
9221     }
9222     if (!strncmp(message, "tellall ", 8)) {
9223       if (appData.icsActive) {
9224         if (loggedOn) {
9225           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9226           SendToICS(buf1);
9227         }
9228       } else {
9229         DisplayNote(message + 8);
9230       }
9231       return;
9232     }
9233     if (strncmp(message, "warning", 7) == 0) {
9234         /* Undocumented feature, use tellusererror in new code */
9235         DisplayError(message, 0);
9236         return;
9237     }
9238     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9239         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9240         strcat(realname, " query");
9241         AskQuestion(realname, buf2, buf1, cps->pr);
9242         return;
9243     }
9244     /* Commands from the engine directly to ICS.  We don't allow these to be
9245      *  sent until we are logged on. Crafty kibitzes have been known to
9246      *  interfere with the login process.
9247      */
9248     if (loggedOn) {
9249         if (!strncmp(message, "tellics ", 8)) {
9250             SendToICS(message + 8);
9251             SendToICS("\n");
9252             return;
9253         }
9254         if (!strncmp(message, "tellicsnoalias ", 15)) {
9255             SendToICS(ics_prefix);
9256             SendToICS(message + 15);
9257             SendToICS("\n");
9258             return;
9259         }
9260         /* The following are for backward compatibility only */
9261         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9262             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9263             SendToICS(ics_prefix);
9264             SendToICS(message);
9265             SendToICS("\n");
9266             return;
9267         }
9268     }
9269     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9270         if(initPing == cps->lastPong) {
9271             if(gameInfo.variant == VariantUnknown) {
9272                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9273                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9274                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9275             }
9276             initPing = -1;
9277         }
9278         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9279             abortEngineThink = FALSE;
9280             DisplayMessage("", "");
9281             ThawUI();
9282         }
9283         return;
9284     }
9285     if(!strncmp(message, "highlight ", 10)) {
9286         if(appData.testLegality && !*engineVariant && appData.markers) return;
9287         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9288         return;
9289     }
9290     if(!strncmp(message, "click ", 6)) {
9291         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9292         if(appData.testLegality || !appData.oneClick) return;
9293         sscanf(message+6, "%c%d%c", &f, &y, &c);
9294         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9295         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9296         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9297         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9298         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9299         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9300             LeftClick(Release, lastLeftX, lastLeftY);
9301         controlKey  = (c == ',');
9302         LeftClick(Press, x, y);
9303         LeftClick(Release, x, y);
9304         first.highlight = f;
9305         return;
9306     }
9307     /*
9308      * If the move is illegal, cancel it and redraw the board.
9309      * Also deal with other error cases.  Matching is rather loose
9310      * here to accommodate engines written before the spec.
9311      */
9312     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9313         strncmp(message, "Error", 5) == 0) {
9314         if (StrStr(message, "name") ||
9315             StrStr(message, "rating") || StrStr(message, "?") ||
9316             StrStr(message, "result") || StrStr(message, "board") ||
9317             StrStr(message, "bk") || StrStr(message, "computer") ||
9318             StrStr(message, "variant") || StrStr(message, "hint") ||
9319             StrStr(message, "random") || StrStr(message, "depth") ||
9320             StrStr(message, "accepted")) {
9321             return;
9322         }
9323         if (StrStr(message, "protover")) {
9324           /* Program is responding to input, so it's apparently done
9325              initializing, and this error message indicates it is
9326              protocol version 1.  So we don't need to wait any longer
9327              for it to initialize and send feature commands. */
9328           FeatureDone(cps, 1);
9329           cps->protocolVersion = 1;
9330           return;
9331         }
9332         cps->maybeThinking = FALSE;
9333
9334         if (StrStr(message, "draw")) {
9335             /* Program doesn't have "draw" command */
9336             cps->sendDrawOffers = 0;
9337             return;
9338         }
9339         if (cps->sendTime != 1 &&
9340             (StrStr(message, "time") || StrStr(message, "otim"))) {
9341           /* Program apparently doesn't have "time" or "otim" command */
9342           cps->sendTime = 0;
9343           return;
9344         }
9345         if (StrStr(message, "analyze")) {
9346             cps->analysisSupport = FALSE;
9347             cps->analyzing = FALSE;
9348 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9349             EditGameEvent(); // [HGM] try to preserve loaded game
9350             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9351             DisplayError(buf2, 0);
9352             return;
9353         }
9354         if (StrStr(message, "(no matching move)st")) {
9355           /* Special kludge for GNU Chess 4 only */
9356           cps->stKludge = TRUE;
9357           SendTimeControl(cps, movesPerSession, timeControl,
9358                           timeIncrement, appData.searchDepth,
9359                           searchTime);
9360           return;
9361         }
9362         if (StrStr(message, "(no matching move)sd")) {
9363           /* Special kludge for GNU Chess 4 only */
9364           cps->sdKludge = TRUE;
9365           SendTimeControl(cps, movesPerSession, timeControl,
9366                           timeIncrement, appData.searchDepth,
9367                           searchTime);
9368           return;
9369         }
9370         if (!StrStr(message, "llegal")) {
9371             return;
9372         }
9373         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9374             gameMode == IcsIdle) return;
9375         if (forwardMostMove <= backwardMostMove) return;
9376         if (pausing) PauseEvent();
9377       if(appData.forceIllegal) {
9378             // [HGM] illegal: machine refused move; force position after move into it
9379           SendToProgram("force\n", cps);
9380           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9381                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9382                 // when black is to move, while there might be nothing on a2 or black
9383                 // might already have the move. So send the board as if white has the move.
9384                 // But first we must change the stm of the engine, as it refused the last move
9385                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9386                 if(WhiteOnMove(forwardMostMove)) {
9387                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9388                     SendBoard(cps, forwardMostMove); // kludgeless board
9389                 } else {
9390                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9391                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9392                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9393                 }
9394           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9395             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9396                  gameMode == TwoMachinesPlay)
9397               SendToProgram("go\n", cps);
9398             return;
9399       } else
9400         if (gameMode == PlayFromGameFile) {
9401             /* Stop reading this game file */
9402             gameMode = EditGame;
9403             ModeHighlight();
9404         }
9405         /* [HGM] illegal-move claim should forfeit game when Xboard */
9406         /* only passes fully legal moves                            */
9407         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9408             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9409                                 "False illegal-move claim", GE_XBOARD );
9410             return; // do not take back move we tested as valid
9411         }
9412         currentMove = forwardMostMove-1;
9413         DisplayMove(currentMove-1); /* before DisplayMoveError */
9414         SwitchClocks(forwardMostMove-1); // [HGM] race
9415         DisplayBothClocks();
9416         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9417                 parseList[currentMove], _(cps->which));
9418         DisplayMoveError(buf1);
9419         DrawPosition(FALSE, boards[currentMove]);
9420
9421         SetUserThinkingEnables();
9422         return;
9423     }
9424     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9425         /* Program has a broken "time" command that
9426            outputs a string not ending in newline.
9427            Don't use it. */
9428         cps->sendTime = 0;
9429     }
9430     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9431         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9432             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9433     }
9434
9435     /*
9436      * If chess program startup fails, exit with an error message.
9437      * Attempts to recover here are futile. [HGM] Well, we try anyway
9438      */
9439     if ((StrStr(message, "unknown host") != NULL)
9440         || (StrStr(message, "No remote directory") != NULL)
9441         || (StrStr(message, "not found") != NULL)
9442         || (StrStr(message, "No such file") != NULL)
9443         || (StrStr(message, "can't alloc") != NULL)
9444         || (StrStr(message, "Permission denied") != NULL)) {
9445
9446         cps->maybeThinking = FALSE;
9447         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9448                 _(cps->which), cps->program, cps->host, message);
9449         RemoveInputSource(cps->isr);
9450         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9451             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9452             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9453         }
9454         return;
9455     }
9456
9457     /*
9458      * Look for hint output
9459      */
9460     if (sscanf(message, "Hint: %s", buf1) == 1) {
9461         if (cps == &first && hintRequested) {
9462             hintRequested = FALSE;
9463             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9464                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9465                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9466                                     PosFlags(forwardMostMove),
9467                                     fromY, fromX, toY, toX, promoChar, buf1);
9468                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9469                 DisplayInformation(buf2);
9470             } else {
9471                 /* Hint move could not be parsed!? */
9472               snprintf(buf2, sizeof(buf2),
9473                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9474                         buf1, _(cps->which));
9475                 DisplayError(buf2, 0);
9476             }
9477         } else {
9478           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9479         }
9480         return;
9481     }
9482
9483     /*
9484      * Ignore other messages if game is not in progress
9485      */
9486     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9487         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9488
9489     /*
9490      * look for win, lose, draw, or draw offer
9491      */
9492     if (strncmp(message, "1-0", 3) == 0) {
9493         char *p, *q, *r = "";
9494         p = strchr(message, '{');
9495         if (p) {
9496             q = strchr(p, '}');
9497             if (q) {
9498                 *q = NULLCHAR;
9499                 r = p + 1;
9500             }
9501         }
9502         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9503         return;
9504     } else if (strncmp(message, "0-1", 3) == 0) {
9505         char *p, *q, *r = "";
9506         p = strchr(message, '{');
9507         if (p) {
9508             q = strchr(p, '}');
9509             if (q) {
9510                 *q = NULLCHAR;
9511                 r = p + 1;
9512             }
9513         }
9514         /* Kludge for Arasan 4.1 bug */
9515         if (strcmp(r, "Black resigns") == 0) {
9516             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9517             return;
9518         }
9519         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9520         return;
9521     } else if (strncmp(message, "1/2", 3) == 0) {
9522         char *p, *q, *r = "";
9523         p = strchr(message, '{');
9524         if (p) {
9525             q = strchr(p, '}');
9526             if (q) {
9527                 *q = NULLCHAR;
9528                 r = p + 1;
9529             }
9530         }
9531
9532         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9533         return;
9534
9535     } else if (strncmp(message, "White resign", 12) == 0) {
9536         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9537         return;
9538     } else if (strncmp(message, "Black resign", 12) == 0) {
9539         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9540         return;
9541     } else if (strncmp(message, "White matches", 13) == 0 ||
9542                strncmp(message, "Black matches", 13) == 0   ) {
9543         /* [HGM] ignore GNUShogi noises */
9544         return;
9545     } else if (strncmp(message, "White", 5) == 0 &&
9546                message[5] != '(' &&
9547                StrStr(message, "Black") == NULL) {
9548         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9549         return;
9550     } else if (strncmp(message, "Black", 5) == 0 &&
9551                message[5] != '(') {
9552         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9553         return;
9554     } else if (strcmp(message, "resign") == 0 ||
9555                strcmp(message, "computer resigns") == 0) {
9556         switch (gameMode) {
9557           case MachinePlaysBlack:
9558           case IcsPlayingBlack:
9559             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9560             break;
9561           case MachinePlaysWhite:
9562           case IcsPlayingWhite:
9563             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9564             break;
9565           case TwoMachinesPlay:
9566             if (cps->twoMachinesColor[0] == 'w')
9567               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9568             else
9569               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9570             break;
9571           default:
9572             /* can't happen */
9573             break;
9574         }
9575         return;
9576     } else if (strncmp(message, "opponent mates", 14) == 0) {
9577         switch (gameMode) {
9578           case MachinePlaysBlack:
9579           case IcsPlayingBlack:
9580             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9581             break;
9582           case MachinePlaysWhite:
9583           case IcsPlayingWhite:
9584             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9585             break;
9586           case TwoMachinesPlay:
9587             if (cps->twoMachinesColor[0] == 'w')
9588               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9589             else
9590               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9591             break;
9592           default:
9593             /* can't happen */
9594             break;
9595         }
9596         return;
9597     } else if (strncmp(message, "computer mates", 14) == 0) {
9598         switch (gameMode) {
9599           case MachinePlaysBlack:
9600           case IcsPlayingBlack:
9601             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9602             break;
9603           case MachinePlaysWhite:
9604           case IcsPlayingWhite:
9605             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9606             break;
9607           case TwoMachinesPlay:
9608             if (cps->twoMachinesColor[0] == 'w')
9609               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9610             else
9611               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9612             break;
9613           default:
9614             /* can't happen */
9615             break;
9616         }
9617         return;
9618     } else if (strncmp(message, "checkmate", 9) == 0) {
9619         if (WhiteOnMove(forwardMostMove)) {
9620             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9621         } else {
9622             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9623         }
9624         return;
9625     } else if (strstr(message, "Draw") != NULL ||
9626                strstr(message, "game is a draw") != NULL) {
9627         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9628         return;
9629     } else if (strstr(message, "offer") != NULL &&
9630                strstr(message, "draw") != NULL) {
9631 #if ZIPPY
9632         if (appData.zippyPlay && first.initDone) {
9633             /* Relay offer to ICS */
9634             SendToICS(ics_prefix);
9635             SendToICS("draw\n");
9636         }
9637 #endif
9638         cps->offeredDraw = 2; /* valid until this engine moves twice */
9639         if (gameMode == TwoMachinesPlay) {
9640             if (cps->other->offeredDraw) {
9641                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9642             /* [HGM] in two-machine mode we delay relaying draw offer      */
9643             /* until after we also have move, to see if it is really claim */
9644             }
9645         } else if (gameMode == MachinePlaysWhite ||
9646                    gameMode == MachinePlaysBlack) {
9647           if (userOfferedDraw) {
9648             DisplayInformation(_("Machine accepts your draw offer"));
9649             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9650           } else {
9651             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9652           }
9653         }
9654     }
9655
9656
9657     /*
9658      * Look for thinking output
9659      */
9660     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9661           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9662                                 ) {
9663         int plylev, mvleft, mvtot, curscore, time;
9664         char mvname[MOVE_LEN];
9665         u64 nodes; // [DM]
9666         char plyext;
9667         int ignore = FALSE;
9668         int prefixHint = FALSE;
9669         mvname[0] = NULLCHAR;
9670
9671         switch (gameMode) {
9672           case MachinePlaysBlack:
9673           case IcsPlayingBlack:
9674             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9675             break;
9676           case MachinePlaysWhite:
9677           case IcsPlayingWhite:
9678             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9679             break;
9680           case AnalyzeMode:
9681           case AnalyzeFile:
9682             break;
9683           case IcsObserving: /* [DM] icsEngineAnalyze */
9684             if (!appData.icsEngineAnalyze) ignore = TRUE;
9685             break;
9686           case TwoMachinesPlay:
9687             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9688                 ignore = TRUE;
9689             }
9690             break;
9691           default:
9692             ignore = TRUE;
9693             break;
9694         }
9695
9696         if (!ignore) {
9697             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9698             int solved = 0;
9699             buf1[0] = NULLCHAR;
9700             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9701                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9702                 char score_buf[MSG_SIZ];
9703
9704                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9705                     nodes += u64Const(0x100000000);
9706
9707                 if (plyext != ' ' && plyext != '\t') {
9708                     time *= 100;
9709                 }
9710
9711                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9712                 if( cps->scoreIsAbsolute &&
9713                     ( gameMode == MachinePlaysBlack ||
9714                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9715                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9716                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9717                      !WhiteOnMove(currentMove)
9718                     ) )
9719                 {
9720                     curscore = -curscore;
9721                 }
9722
9723                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9724
9725                 if(*bestMove) { // rememer time best EPD move was first found
9726                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9727                     ChessMove mt; char *p = bestMove;
9728                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9729                     solved = 0;
9730                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9731                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9732                             solvingTime = (solvingTime < 0 ? time : solvingTime);
9733                             solved = 1;
9734                             break;
9735                         }
9736                         while(*p && *p != ' ') p++;
9737                         while(*p == ' ') p++;
9738                     }
9739                     if(!solved) solvingTime = -1;
9740                 }
9741                 if(*avoidMove && !solved) {
9742                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9743                     ChessMove mt; char *p = avoidMove, solved = 1;
9744                     int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9745                     while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
9746                         if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
9747                             solved = 0; solvingTime = -2;
9748                             break;
9749                         }
9750                         while(*p && *p != ' ') p++;
9751                         while(*p == ' ') p++;
9752                     }
9753                     if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
9754                 }
9755
9756                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9757                         char buf[MSG_SIZ];
9758                         FILE *f;
9759                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9760                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9761                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9762                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9763                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9764                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9765                                 fclose(f);
9766                         }
9767                         else
9768                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9769                           DisplayError(_("failed writing PV"), 0);
9770                 }
9771
9772                 tempStats.depth = plylev;
9773                 tempStats.nodes = nodes;
9774                 tempStats.time = time;
9775                 tempStats.score = curscore;
9776                 tempStats.got_only_move = 0;
9777
9778                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9779                         int ticklen;
9780
9781                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9782                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9783                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9784                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9785                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9786                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9787                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9788                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9789                 }
9790
9791                 /* Buffer overflow protection */
9792                 if (pv[0] != NULLCHAR) {
9793                     if (strlen(pv) >= sizeof(tempStats.movelist)
9794                         && appData.debugMode) {
9795                         fprintf(debugFP,
9796                                 "PV is too long; using the first %u bytes.\n",
9797                                 (unsigned) sizeof(tempStats.movelist) - 1);
9798                     }
9799
9800                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9801                 } else {
9802                     sprintf(tempStats.movelist, " no PV\n");
9803                 }
9804
9805                 if (tempStats.seen_stat) {
9806                     tempStats.ok_to_send = 1;
9807                 }
9808
9809                 if (strchr(tempStats.movelist, '(') != NULL) {
9810                     tempStats.line_is_book = 1;
9811                     tempStats.nr_moves = 0;
9812                     tempStats.moves_left = 0;
9813                 } else {
9814                     tempStats.line_is_book = 0;
9815                 }
9816
9817                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9818                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9819
9820                 SendProgramStatsToFrontend( cps, &tempStats );
9821
9822                 /*
9823                     [AS] Protect the thinkOutput buffer from overflow... this
9824                     is only useful if buf1 hasn't overflowed first!
9825                 */
9826                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9827                 if(curscore >= MATE_SCORE) 
9828                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9829                 else if(curscore <= -MATE_SCORE) 
9830                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9831                 else
9832                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9833                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9834                          plylev,
9835                          (gameMode == TwoMachinesPlay ?
9836                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9837                          score_buf,
9838                          prefixHint ? lastHint : "",
9839                          prefixHint ? " " : "" );
9840
9841                 if( buf1[0] != NULLCHAR ) {
9842                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9843
9844                     if( strlen(pv) > max_len ) {
9845                         if( appData.debugMode) {
9846                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9847                         }
9848                         pv[max_len+1] = '\0';
9849                     }
9850
9851                     strcat( thinkOutput, pv);
9852                 }
9853
9854                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9855                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9856                     DisplayMove(currentMove - 1);
9857                 }
9858                 return;
9859
9860             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9861                 /* crafty (9.25+) says "(only move) <move>"
9862                  * if there is only 1 legal move
9863                  */
9864                 sscanf(p, "(only move) %s", buf1);
9865                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9866                 sprintf(programStats.movelist, "%s (only move)", buf1);
9867                 programStats.depth = 1;
9868                 programStats.nr_moves = 1;
9869                 programStats.moves_left = 1;
9870                 programStats.nodes = 1;
9871                 programStats.time = 1;
9872                 programStats.got_only_move = 1;
9873
9874                 /* Not really, but we also use this member to
9875                    mean "line isn't going to change" (Crafty
9876                    isn't searching, so stats won't change) */
9877                 programStats.line_is_book = 1;
9878
9879                 SendProgramStatsToFrontend( cps, &programStats );
9880
9881                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9882                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9883                     DisplayMove(currentMove - 1);
9884                 }
9885                 return;
9886             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9887                               &time, &nodes, &plylev, &mvleft,
9888                               &mvtot, mvname) >= 5) {
9889                 /* The stat01: line is from Crafty (9.29+) in response
9890                    to the "." command */
9891                 programStats.seen_stat = 1;
9892                 cps->maybeThinking = TRUE;
9893
9894                 if (programStats.got_only_move || !appData.periodicUpdates)
9895                   return;
9896
9897                 programStats.depth = plylev;
9898                 programStats.time = time;
9899                 programStats.nodes = nodes;
9900                 programStats.moves_left = mvleft;
9901                 programStats.nr_moves = mvtot;
9902                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9903                 programStats.ok_to_send = 1;
9904                 programStats.movelist[0] = '\0';
9905
9906                 SendProgramStatsToFrontend( cps, &programStats );
9907
9908                 return;
9909
9910             } else if (strncmp(message,"++",2) == 0) {
9911                 /* Crafty 9.29+ outputs this */
9912                 programStats.got_fail = 2;
9913                 return;
9914
9915             } else if (strncmp(message,"--",2) == 0) {
9916                 /* Crafty 9.29+ outputs this */
9917                 programStats.got_fail = 1;
9918                 return;
9919
9920             } else if (thinkOutput[0] != NULLCHAR &&
9921                        strncmp(message, "    ", 4) == 0) {
9922                 unsigned message_len;
9923
9924                 p = message;
9925                 while (*p && *p == ' ') p++;
9926
9927                 message_len = strlen( p );
9928
9929                 /* [AS] Avoid buffer overflow */
9930                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9931                     strcat(thinkOutput, " ");
9932                     strcat(thinkOutput, p);
9933                 }
9934
9935                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9936                     strcat(programStats.movelist, " ");
9937                     strcat(programStats.movelist, p);
9938                 }
9939
9940                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9941                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9942                     DisplayMove(currentMove - 1);
9943                 }
9944                 return;
9945             }
9946         }
9947         else {
9948             buf1[0] = NULLCHAR;
9949
9950             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9951                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9952             {
9953                 ChessProgramStats cpstats;
9954
9955                 if (plyext != ' ' && plyext != '\t') {
9956                     time *= 100;
9957                 }
9958
9959                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9960                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9961                     curscore = -curscore;
9962                 }
9963
9964                 cpstats.depth = plylev;
9965                 cpstats.nodes = nodes;
9966                 cpstats.time = time;
9967                 cpstats.score = curscore;
9968                 cpstats.got_only_move = 0;
9969                 cpstats.movelist[0] = '\0';
9970
9971                 if (buf1[0] != NULLCHAR) {
9972                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9973                 }
9974
9975                 cpstats.ok_to_send = 0;
9976                 cpstats.line_is_book = 0;
9977                 cpstats.nr_moves = 0;
9978                 cpstats.moves_left = 0;
9979
9980                 SendProgramStatsToFrontend( cps, &cpstats );
9981             }
9982         }
9983     }
9984 }
9985
9986
9987 /* Parse a game score from the character string "game", and
9988    record it as the history of the current game.  The game
9989    score is NOT assumed to start from the standard position.
9990    The display is not updated in any way.
9991    */
9992 void
9993 ParseGameHistory (char *game)
9994 {
9995     ChessMove moveType;
9996     int fromX, fromY, toX, toY, boardIndex;
9997     char promoChar;
9998     char *p, *q;
9999     char buf[MSG_SIZ];
10000
10001     if (appData.debugMode)
10002       fprintf(debugFP, "Parsing game history: %s\n", game);
10003
10004     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
10005     gameInfo.site = StrSave(appData.icsHost);
10006     gameInfo.date = PGNDate();
10007     gameInfo.round = StrSave("-");
10008
10009     /* Parse out names of players */
10010     while (*game == ' ') game++;
10011     p = buf;
10012     while (*game != ' ') *p++ = *game++;
10013     *p = NULLCHAR;
10014     gameInfo.white = StrSave(buf);
10015     while (*game == ' ') game++;
10016     p = buf;
10017     while (*game != ' ' && *game != '\n') *p++ = *game++;
10018     *p = NULLCHAR;
10019     gameInfo.black = StrSave(buf);
10020
10021     /* Parse moves */
10022     boardIndex = blackPlaysFirst ? 1 : 0;
10023     yynewstr(game);
10024     for (;;) {
10025         yyboardindex = boardIndex;
10026         moveType = (ChessMove) Myylex();
10027         switch (moveType) {
10028           case IllegalMove:             /* maybe suicide chess, etc. */
10029   if (appData.debugMode) {
10030     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
10031     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10032     setbuf(debugFP, NULL);
10033   }
10034           case WhitePromotion:
10035           case BlackPromotion:
10036           case WhiteNonPromotion:
10037           case BlackNonPromotion:
10038           case NormalMove:
10039           case FirstLeg:
10040           case WhiteCapturesEnPassant:
10041           case BlackCapturesEnPassant:
10042           case WhiteKingSideCastle:
10043           case WhiteQueenSideCastle:
10044           case BlackKingSideCastle:
10045           case BlackQueenSideCastle:
10046           case WhiteKingSideCastleWild:
10047           case WhiteQueenSideCastleWild:
10048           case BlackKingSideCastleWild:
10049           case BlackQueenSideCastleWild:
10050           /* PUSH Fabien */
10051           case WhiteHSideCastleFR:
10052           case WhiteASideCastleFR:
10053           case BlackHSideCastleFR:
10054           case BlackASideCastleFR:
10055           /* POP Fabien */
10056             fromX = currentMoveString[0] - AAA;
10057             fromY = currentMoveString[1] - ONE;
10058             toX = currentMoveString[2] - AAA;
10059             toY = currentMoveString[3] - ONE;
10060             promoChar = currentMoveString[4];
10061             break;
10062           case WhiteDrop:
10063           case BlackDrop:
10064             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10065             fromX = moveType == WhiteDrop ?
10066               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10067             (int) CharToPiece(ToLower(currentMoveString[0]));
10068             fromY = DROP_RANK;
10069             toX = currentMoveString[2] - AAA;
10070             toY = currentMoveString[3] - ONE;
10071             promoChar = NULLCHAR;
10072             break;
10073           case AmbiguousMove:
10074             /* bug? */
10075             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10076   if (appData.debugMode) {
10077     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10078     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10079     setbuf(debugFP, NULL);
10080   }
10081             DisplayError(buf, 0);
10082             return;
10083           case ImpossibleMove:
10084             /* bug? */
10085             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10086   if (appData.debugMode) {
10087     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10088     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10089     setbuf(debugFP, NULL);
10090   }
10091             DisplayError(buf, 0);
10092             return;
10093           case EndOfFile:
10094             if (boardIndex < backwardMostMove) {
10095                 /* Oops, gap.  How did that happen? */
10096                 DisplayError(_("Gap in move list"), 0);
10097                 return;
10098             }
10099             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10100             if (boardIndex > forwardMostMove) {
10101                 forwardMostMove = boardIndex;
10102             }
10103             return;
10104           case ElapsedTime:
10105             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10106                 strcat(parseList[boardIndex-1], " ");
10107                 strcat(parseList[boardIndex-1], yy_text);
10108             }
10109             continue;
10110           case Comment:
10111           case PGNTag:
10112           case NAG:
10113           default:
10114             /* ignore */
10115             continue;
10116           case WhiteWins:
10117           case BlackWins:
10118           case GameIsDrawn:
10119           case GameUnfinished:
10120             if (gameMode == IcsExamining) {
10121                 if (boardIndex < backwardMostMove) {
10122                     /* Oops, gap.  How did that happen? */
10123                     return;
10124                 }
10125                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10126                 return;
10127             }
10128             gameInfo.result = moveType;
10129             p = strchr(yy_text, '{');
10130             if (p == NULL) p = strchr(yy_text, '(');
10131             if (p == NULL) {
10132                 p = yy_text;
10133                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10134             } else {
10135                 q = strchr(p, *p == '{' ? '}' : ')');
10136                 if (q != NULL) *q = NULLCHAR;
10137                 p++;
10138             }
10139             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10140             gameInfo.resultDetails = StrSave(p);
10141             continue;
10142         }
10143         if (boardIndex >= forwardMostMove &&
10144             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10145             backwardMostMove = blackPlaysFirst ? 1 : 0;
10146             return;
10147         }
10148         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10149                                  fromY, fromX, toY, toX, promoChar,
10150                                  parseList[boardIndex]);
10151         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10152         /* currentMoveString is set as a side-effect of yylex */
10153         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10154         strcat(moveList[boardIndex], "\n");
10155         boardIndex++;
10156         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10157         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10158           case MT_NONE:
10159           case MT_STALEMATE:
10160           default:
10161             break;
10162           case MT_CHECK:
10163             if(!IS_SHOGI(gameInfo.variant))
10164                 strcat(parseList[boardIndex - 1], "+");
10165             break;
10166           case MT_CHECKMATE:
10167           case MT_STAINMATE:
10168             strcat(parseList[boardIndex - 1], "#");
10169             break;
10170         }
10171     }
10172 }
10173
10174
10175 /* Apply a move to the given board  */
10176 void
10177 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10178 {
10179   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10180   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10181
10182     /* [HGM] compute & store e.p. status and castling rights for new position */
10183     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10184
10185       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10186       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10187       board[EP_STATUS] = EP_NONE;
10188       board[EP_FILE] = board[EP_RANK] = 100;
10189
10190   if (fromY == DROP_RANK) {
10191         /* must be first */
10192         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10193             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10194             return;
10195         }
10196         piece = board[toY][toX] = (ChessSquare) fromX;
10197   } else {
10198 //      ChessSquare victim;
10199       int i;
10200
10201       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10202 //           victim = board[killY][killX],
10203            killed = board[killY][killX],
10204            board[killY][killX] = EmptySquare,
10205            board[EP_STATUS] = EP_CAPTURE;
10206            if( kill2X >= 0 && kill2Y >= 0)
10207              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10208       }
10209
10210       if( board[toY][toX] != EmptySquare ) {
10211            board[EP_STATUS] = EP_CAPTURE;
10212            if( (fromX != toX || fromY != toY) && // not igui!
10213                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10214                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10215                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10216            }
10217       }
10218
10219       pawn = board[fromY][fromX];
10220       if( pawn == WhiteLance || pawn == BlackLance ) {
10221            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10222                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10223                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10224            }
10225       }
10226       if( pawn == WhitePawn ) {
10227            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10228                board[EP_STATUS] = EP_PAWN_MOVE;
10229            if( toY-fromY>=2) {
10230                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10231                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10232                         gameInfo.variant != VariantBerolina || toX < fromX)
10233                       board[EP_STATUS] = toX | berolina;
10234                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10235                         gameInfo.variant != VariantBerolina || toX > fromX)
10236                       board[EP_STATUS] = toX;
10237            }
10238       } else
10239       if( pawn == BlackPawn ) {
10240            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10241                board[EP_STATUS] = EP_PAWN_MOVE;
10242            if( toY-fromY<= -2) {
10243                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10244                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10245                         gameInfo.variant != VariantBerolina || toX < fromX)
10246                       board[EP_STATUS] = toX | berolina;
10247                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10248                         gameInfo.variant != VariantBerolina || toX > fromX)
10249                       board[EP_STATUS] = toX;
10250            }
10251        }
10252
10253        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10254        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10255        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10256        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10257
10258        for(i=0; i<nrCastlingRights; i++) {
10259            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10260               board[CASTLING][i] == toX   && castlingRank[i] == toY
10261              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10262        }
10263
10264        if(gameInfo.variant == VariantSChess) { // update virginity
10265            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10266            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10267            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10268            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10269        }
10270
10271      if (fromX == toX && fromY == toY && killX < 0) return;
10272
10273      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10274      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10275      if(gameInfo.variant == VariantKnightmate)
10276          king += (int) WhiteUnicorn - (int) WhiteKing;
10277
10278     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10279        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10280         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10281         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10282         board[EP_STATUS] = EP_NONE; // capture was fake!
10283     } else
10284     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10285         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10286         board[toY][toX] = piece;
10287         board[EP_STATUS] = EP_NONE; // capture was fake!
10288     } else
10289     /* Code added by Tord: */
10290     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10291     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10292         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10293       board[EP_STATUS] = EP_NONE; // capture was fake!
10294       board[fromY][fromX] = EmptySquare;
10295       board[toY][toX] = EmptySquare;
10296       if((toX > fromX) != (piece == WhiteRook)) {
10297         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10298       } else {
10299         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10300       }
10301     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10302                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10303       board[EP_STATUS] = EP_NONE;
10304       board[fromY][fromX] = EmptySquare;
10305       board[toY][toX] = EmptySquare;
10306       if((toX > fromX) != (piece == BlackRook)) {
10307         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10308       } else {
10309         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10310       }
10311     /* End of code added by Tord */
10312
10313     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10314         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10315         board[toY][toX] = piece;
10316     } else if (board[fromY][fromX] == king
10317         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10318         && toY == fromY && toX > fromX+1) {
10319         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10320         board[fromY][toX-1] = board[fromY][rookX];
10321         board[fromY][rookX] = EmptySquare;
10322         board[fromY][fromX] = EmptySquare;
10323         board[toY][toX] = king;
10324     } else if (board[fromY][fromX] == king
10325         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10326                && toY == fromY && toX < fromX-1) {
10327         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10328         board[fromY][toX+1] = board[fromY][rookX];
10329         board[fromY][rookX] = EmptySquare;
10330         board[fromY][fromX] = EmptySquare;
10331         board[toY][toX] = king;
10332     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10333                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10334                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10335                ) {
10336         /* white pawn promotion */
10337         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10338         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10339             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10340         board[fromY][fromX] = EmptySquare;
10341     } else if ((fromY >= BOARD_HEIGHT>>1)
10342                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10343                && (toX != fromX)
10344                && gameInfo.variant != VariantXiangqi
10345                && gameInfo.variant != VariantBerolina
10346                && (pawn == WhitePawn)
10347                && (board[toY][toX] == EmptySquare)) {
10348         board[fromY][fromX] = EmptySquare;
10349         board[toY][toX] = piece;
10350         if(toY == epRank - 128 + 1)
10351             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10352         else
10353             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10354     } else if ((fromY == BOARD_HEIGHT-4)
10355                && (toX == fromX)
10356                && gameInfo.variant == VariantBerolina
10357                && (board[fromY][fromX] == WhitePawn)
10358                && (board[toY][toX] == EmptySquare)) {
10359         board[fromY][fromX] = EmptySquare;
10360         board[toY][toX] = WhitePawn;
10361         if(oldEP & EP_BEROLIN_A) {
10362                 captured = board[fromY][fromX-1];
10363                 board[fromY][fromX-1] = EmptySquare;
10364         }else{  captured = board[fromY][fromX+1];
10365                 board[fromY][fromX+1] = EmptySquare;
10366         }
10367     } else if (board[fromY][fromX] == king
10368         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10369                && toY == fromY && toX > fromX+1) {
10370         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10371         board[fromY][toX-1] = board[fromY][rookX];
10372         board[fromY][rookX] = EmptySquare;
10373         board[fromY][fromX] = EmptySquare;
10374         board[toY][toX] = king;
10375     } else if (board[fromY][fromX] == king
10376         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10377                && toY == fromY && toX < fromX-1) {
10378         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10379         board[fromY][toX+1] = board[fromY][rookX];
10380         board[fromY][rookX] = EmptySquare;
10381         board[fromY][fromX] = EmptySquare;
10382         board[toY][toX] = king;
10383     } else if (fromY == 7 && fromX == 3
10384                && board[fromY][fromX] == BlackKing
10385                && toY == 7 && toX == 5) {
10386         board[fromY][fromX] = EmptySquare;
10387         board[toY][toX] = BlackKing;
10388         board[fromY][7] = EmptySquare;
10389         board[toY][4] = BlackRook;
10390     } else if (fromY == 7 && fromX == 3
10391                && board[fromY][fromX] == BlackKing
10392                && toY == 7 && toX == 1) {
10393         board[fromY][fromX] = EmptySquare;
10394         board[toY][toX] = BlackKing;
10395         board[fromY][0] = EmptySquare;
10396         board[toY][2] = BlackRook;
10397     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10398                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10399                && toY < promoRank && promoChar
10400                ) {
10401         /* black pawn promotion */
10402         board[toY][toX] = CharToPiece(ToLower(promoChar));
10403         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10404             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10405         board[fromY][fromX] = EmptySquare;
10406     } else if ((fromY < BOARD_HEIGHT>>1)
10407                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10408                && (toX != fromX)
10409                && gameInfo.variant != VariantXiangqi
10410                && gameInfo.variant != VariantBerolina
10411                && (pawn == BlackPawn)
10412                && (board[toY][toX] == EmptySquare)) {
10413         board[fromY][fromX] = EmptySquare;
10414         board[toY][toX] = piece;
10415         if(toY == epRank - 128 - 1)
10416             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10417         else
10418             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10419     } else if ((fromY == 3)
10420                && (toX == fromX)
10421                && gameInfo.variant == VariantBerolina
10422                && (board[fromY][fromX] == BlackPawn)
10423                && (board[toY][toX] == EmptySquare)) {
10424         board[fromY][fromX] = EmptySquare;
10425         board[toY][toX] = BlackPawn;
10426         if(oldEP & EP_BEROLIN_A) {
10427                 captured = board[fromY][fromX-1];
10428                 board[fromY][fromX-1] = EmptySquare;
10429         }else{  captured = board[fromY][fromX+1];
10430                 board[fromY][fromX+1] = EmptySquare;
10431         }
10432     } else {
10433         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10434         board[fromY][fromX] = EmptySquare;
10435         board[toY][toX] = piece;
10436     }
10437   }
10438
10439     if (gameInfo.holdingsWidth != 0) {
10440
10441       /* !!A lot more code needs to be written to support holdings  */
10442       /* [HGM] OK, so I have written it. Holdings are stored in the */
10443       /* penultimate board files, so they are automaticlly stored   */
10444       /* in the game history.                                       */
10445       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10446                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10447         /* Delete from holdings, by decreasing count */
10448         /* and erasing image if necessary            */
10449         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10450         if(p < (int) BlackPawn) { /* white drop */
10451              p -= (int)WhitePawn;
10452                  p = PieceToNumber((ChessSquare)p);
10453              if(p >= gameInfo.holdingsSize) p = 0;
10454              if(--board[p][BOARD_WIDTH-2] <= 0)
10455                   board[p][BOARD_WIDTH-1] = EmptySquare;
10456              if((int)board[p][BOARD_WIDTH-2] < 0)
10457                         board[p][BOARD_WIDTH-2] = 0;
10458         } else {                  /* black drop */
10459              p -= (int)BlackPawn;
10460                  p = PieceToNumber((ChessSquare)p);
10461              if(p >= gameInfo.holdingsSize) p = 0;
10462              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10463                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10464              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10465                         board[BOARD_HEIGHT-1-p][1] = 0;
10466         }
10467       }
10468       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10469           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10470         /* [HGM] holdings: Add to holdings, if holdings exist */
10471         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10472                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10473                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10474         }
10475         p = (int) captured;
10476         if (p >= (int) BlackPawn) {
10477           p -= (int)BlackPawn;
10478           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10479                   /* Restore shogi-promoted piece to its original  first */
10480                   captured = (ChessSquare) (DEMOTED(captured));
10481                   p = DEMOTED(p);
10482           }
10483           p = PieceToNumber((ChessSquare)p);
10484           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10485           board[p][BOARD_WIDTH-2]++;
10486           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10487         } else {
10488           p -= (int)WhitePawn;
10489           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10490                   captured = (ChessSquare) (DEMOTED(captured));
10491                   p = DEMOTED(p);
10492           }
10493           p = PieceToNumber((ChessSquare)p);
10494           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10495           board[BOARD_HEIGHT-1-p][1]++;
10496           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10497         }
10498       }
10499     } else if (gameInfo.variant == VariantAtomic) {
10500       if (captured != EmptySquare) {
10501         int y, x;
10502         for (y = toY-1; y <= toY+1; y++) {
10503           for (x = toX-1; x <= toX+1; x++) {
10504             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10505                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10506               board[y][x] = EmptySquare;
10507             }
10508           }
10509         }
10510         board[toY][toX] = EmptySquare;
10511       }
10512     }
10513
10514     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10515         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10516     } else
10517     if(promoChar == '+') {
10518         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10519         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10520         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10521           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10522     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10523         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10524         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10525            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10526         board[toY][toX] = newPiece;
10527     }
10528     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10529                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10530         // [HGM] superchess: take promotion piece out of holdings
10531         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10532         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10533             if(!--board[k][BOARD_WIDTH-2])
10534                 board[k][BOARD_WIDTH-1] = EmptySquare;
10535         } else {
10536             if(!--board[BOARD_HEIGHT-1-k][1])
10537                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10538         }
10539     }
10540 }
10541
10542 /* Updates forwardMostMove */
10543 void
10544 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10545 {
10546     int x = toX, y = toY;
10547     char *s = parseList[forwardMostMove];
10548     ChessSquare p = boards[forwardMostMove][toY][toX];
10549 //    forwardMostMove++; // [HGM] bare: moved downstream
10550
10551     if(kill2X >= 0) x = kill2X, y = kill2Y; else
10552     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10553     (void) CoordsToAlgebraic(boards[forwardMostMove],
10554                              PosFlags(forwardMostMove),
10555                              fromY, fromX, y, x, (killX < 0)*promoChar,
10556                              s);
10557     if(kill2X >= 0 && kill2Y >= 0)
10558         sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
10559     if(killX >= 0 && killY >= 0)
10560         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
10561                                            toX + AAA, toY + ONE - '0', promoChar);
10562
10563     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10564         int timeLeft; static int lastLoadFlag=0; int king, piece;
10565         piece = boards[forwardMostMove][fromY][fromX];
10566         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10567         if(gameInfo.variant == VariantKnightmate)
10568             king += (int) WhiteUnicorn - (int) WhiteKing;
10569         if(forwardMostMove == 0) {
10570             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10571                 fprintf(serverMoves, "%s;", UserName());
10572             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10573                 fprintf(serverMoves, "%s;", second.tidy);
10574             fprintf(serverMoves, "%s;", first.tidy);
10575             if(gameMode == MachinePlaysWhite)
10576                 fprintf(serverMoves, "%s;", UserName());
10577             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10578                 fprintf(serverMoves, "%s;", second.tidy);
10579         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10580         lastLoadFlag = loadFlag;
10581         // print base move
10582         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10583         // print castling suffix
10584         if( toY == fromY && piece == king ) {
10585             if(toX-fromX > 1)
10586                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10587             if(fromX-toX >1)
10588                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10589         }
10590         // e.p. suffix
10591         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10592              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10593              boards[forwardMostMove][toY][toX] == EmptySquare
10594              && fromX != toX && fromY != toY)
10595                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10596         // promotion suffix
10597         if(promoChar != NULLCHAR) {
10598             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10599                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10600                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10601             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10602         }
10603         if(!loadFlag) {
10604                 char buf[MOVE_LEN*2], *p; int len;
10605             fprintf(serverMoves, "/%d/%d",
10606                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10607             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10608             else                      timeLeft = blackTimeRemaining/1000;
10609             fprintf(serverMoves, "/%d", timeLeft);
10610                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10611                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10612                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10613                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10614             fprintf(serverMoves, "/%s", buf);
10615         }
10616         fflush(serverMoves);
10617     }
10618
10619     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10620         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10621       return;
10622     }
10623     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10624     if (commentList[forwardMostMove+1] != NULL) {
10625         free(commentList[forwardMostMove+1]);
10626         commentList[forwardMostMove+1] = NULL;
10627     }
10628     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10629     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10630     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10631     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10632     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10633     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10634     adjustedClock = FALSE;
10635     gameInfo.result = GameUnfinished;
10636     if (gameInfo.resultDetails != NULL) {
10637         free(gameInfo.resultDetails);
10638         gameInfo.resultDetails = NULL;
10639     }
10640     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10641                               moveList[forwardMostMove - 1]);
10642     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10643       case MT_NONE:
10644       case MT_STALEMATE:
10645       default:
10646         break;
10647       case MT_CHECK:
10648         if(!IS_SHOGI(gameInfo.variant))
10649             strcat(parseList[forwardMostMove - 1], "+");
10650         break;
10651       case MT_CHECKMATE:
10652       case MT_STAINMATE:
10653         strcat(parseList[forwardMostMove - 1], "#");
10654         break;
10655     }
10656 }
10657
10658 /* Updates currentMove if not pausing */
10659 void
10660 ShowMove (int fromX, int fromY, int toX, int toY)
10661 {
10662     int instant = (gameMode == PlayFromGameFile) ?
10663         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10664     if(appData.noGUI) return;
10665     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10666         if (!instant) {
10667             if (forwardMostMove == currentMove + 1) {
10668                 AnimateMove(boards[forwardMostMove - 1],
10669                             fromX, fromY, toX, toY);
10670             }
10671         }
10672         currentMove = forwardMostMove;
10673     }
10674
10675     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
10676
10677     if (instant) return;
10678
10679     DisplayMove(currentMove - 1);
10680     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10681             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10682                 SetHighlights(fromX, fromY, toX, toY);
10683             }
10684     }
10685     DrawPosition(FALSE, boards[currentMove]);
10686     DisplayBothClocks();
10687     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10688 }
10689
10690 void
10691 SendEgtPath (ChessProgramState *cps)
10692 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10693         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10694
10695         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10696
10697         while(*p) {
10698             char c, *q = name+1, *r, *s;
10699
10700             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10701             while(*p && *p != ',') *q++ = *p++;
10702             *q++ = ':'; *q = 0;
10703             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10704                 strcmp(name, ",nalimov:") == 0 ) {
10705                 // take nalimov path from the menu-changeable option first, if it is defined
10706               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10707                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10708             } else
10709             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10710                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10711                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10712                 s = r = StrStr(s, ":") + 1; // beginning of path info
10713                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10714                 c = *r; *r = 0;             // temporarily null-terminate path info
10715                     *--q = 0;               // strip of trailig ':' from name
10716                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10717                 *r = c;
10718                 SendToProgram(buf,cps);     // send egtbpath command for this format
10719             }
10720             if(*p == ',') p++; // read away comma to position for next format name
10721         }
10722 }
10723
10724 static int
10725 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10726 {
10727       int width = 8, height = 8, holdings = 0;             // most common sizes
10728       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10729       // correct the deviations default for each variant
10730       if( v == VariantXiangqi ) width = 9,  height = 10;
10731       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10732       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10733       if( v == VariantCapablanca || v == VariantCapaRandom ||
10734           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10735                                 width = 10;
10736       if( v == VariantCourier ) width = 12;
10737       if( v == VariantSuper )                            holdings = 8;
10738       if( v == VariantGreat )   width = 10,              holdings = 8;
10739       if( v == VariantSChess )                           holdings = 7;
10740       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10741       if( v == VariantChuChess) width = 10, height = 10;
10742       if( v == VariantChu )     width = 12, height = 12;
10743       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10744              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10745              holdingsSize >= 0 && holdingsSize != holdings;
10746 }
10747
10748 char variantError[MSG_SIZ];
10749
10750 char *
10751 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10752 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10753       char *p, *variant = VariantName(v);
10754       static char b[MSG_SIZ];
10755       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10756            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10757                                                holdingsSize, variant); // cook up sized variant name
10758            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10759            if(StrStr(list, b) == NULL) {
10760                // specific sized variant not known, check if general sizing allowed
10761                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10762                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10763                             boardWidth, boardHeight, holdingsSize, engine);
10764                    return NULL;
10765                }
10766                /* [HGM] here we really should compare with the maximum supported board size */
10767            }
10768       } else snprintf(b, MSG_SIZ,"%s", variant);
10769       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10770       p = StrStr(list, b);
10771       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10772       if(p == NULL) {
10773           // occurs not at all in list, or only as sub-string
10774           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10775           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10776               int l = strlen(variantError);
10777               char *q;
10778               while(p != list && p[-1] != ',') p--;
10779               q = strchr(p, ',');
10780               if(q) *q = NULLCHAR;
10781               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10782               if(q) *q= ',';
10783           }
10784           return NULL;
10785       }
10786       return b;
10787 }
10788
10789 void
10790 InitChessProgram (ChessProgramState *cps, int setup)
10791 /* setup needed to setup FRC opening position */
10792 {
10793     char buf[MSG_SIZ], *b;
10794     if (appData.noChessProgram) return;
10795     hintRequested = FALSE;
10796     bookRequested = FALSE;
10797
10798     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10799     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10800     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10801     if(cps->memSize) { /* [HGM] memory */
10802       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10803         SendToProgram(buf, cps);
10804     }
10805     SendEgtPath(cps); /* [HGM] EGT */
10806     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10807       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10808         SendToProgram(buf, cps);
10809     }
10810
10811     setboardSpoiledMachineBlack = FALSE;
10812     SendToProgram(cps->initString, cps);
10813     if (gameInfo.variant != VariantNormal &&
10814         gameInfo.variant != VariantLoadable
10815         /* [HGM] also send variant if board size non-standard */
10816         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10817
10818       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10819                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10820
10821       if (b == NULL) {
10822         VariantClass v;
10823         char c, *q = cps->variants, *p = strchr(q, ',');
10824         if(p) *p = NULLCHAR;
10825         v = StringToVariant(q);
10826         DisplayError(variantError, 0);
10827         if(v != VariantUnknown && cps == &first) {
10828             int w, h, s;
10829             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10830                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10831             ASSIGN(appData.variant, q);
10832             Reset(TRUE, FALSE);
10833         }
10834         if(p) *p = ',';
10835         return;
10836       }
10837
10838       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10839       SendToProgram(buf, cps);
10840     }
10841     currentlyInitializedVariant = gameInfo.variant;
10842
10843     /* [HGM] send opening position in FRC to first engine */
10844     if(setup) {
10845           SendToProgram("force\n", cps);
10846           SendBoard(cps, 0);
10847           /* engine is now in force mode! Set flag to wake it up after first move. */
10848           setboardSpoiledMachineBlack = 1;
10849     }
10850
10851     if (cps->sendICS) {
10852       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10853       SendToProgram(buf, cps);
10854     }
10855     cps->maybeThinking = FALSE;
10856     cps->offeredDraw = 0;
10857     if (!appData.icsActive) {
10858         SendTimeControl(cps, movesPerSession, timeControl,
10859                         timeIncrement, appData.searchDepth,
10860                         searchTime);
10861     }
10862     if (appData.showThinking
10863         // [HGM] thinking: four options require thinking output to be sent
10864         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10865                                 ) {
10866         SendToProgram("post\n", cps);
10867     }
10868     SendToProgram("hard\n", cps);
10869     if (!appData.ponderNextMove) {
10870         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10871            it without being sure what state we are in first.  "hard"
10872            is not a toggle, so that one is OK.
10873          */
10874         SendToProgram("easy\n", cps);
10875     }
10876     if (cps->usePing) {
10877       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10878       SendToProgram(buf, cps);
10879     }
10880     cps->initDone = TRUE;
10881     ClearEngineOutputPane(cps == &second);
10882 }
10883
10884
10885 void
10886 ResendOptions (ChessProgramState *cps)
10887 { // send the stored value of the options
10888   int i;
10889   char buf[MSG_SIZ];
10890   Option *opt = cps->option;
10891   for(i=0; i<cps->nrOptions; i++, opt++) {
10892       switch(opt->type) {
10893         case Spin:
10894         case Slider:
10895         case CheckBox:
10896             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10897           break;
10898         case ComboBox:
10899           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10900           break;
10901         default:
10902             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10903           break;
10904         case Button:
10905         case SaveButton:
10906           continue;
10907       }
10908       SendToProgram(buf, cps);
10909   }
10910 }
10911
10912 void
10913 StartChessProgram (ChessProgramState *cps)
10914 {
10915     char buf[MSG_SIZ];
10916     int err;
10917
10918     if (appData.noChessProgram) return;
10919     cps->initDone = FALSE;
10920
10921     if (strcmp(cps->host, "localhost") == 0) {
10922         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10923     } else if (*appData.remoteShell == NULLCHAR) {
10924         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10925     } else {
10926         if (*appData.remoteUser == NULLCHAR) {
10927           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10928                     cps->program);
10929         } else {
10930           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10931                     cps->host, appData.remoteUser, cps->program);
10932         }
10933         err = StartChildProcess(buf, "", &cps->pr);
10934     }
10935
10936     if (err != 0) {
10937       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10938         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10939         if(cps != &first) return;
10940         appData.noChessProgram = TRUE;
10941         ThawUI();
10942         SetNCPMode();
10943 //      DisplayFatalError(buf, err, 1);
10944 //      cps->pr = NoProc;
10945 //      cps->isr = NULL;
10946         return;
10947     }
10948
10949     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10950     if (cps->protocolVersion > 1) {
10951       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10952       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10953         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10954         cps->comboCnt = 0;  //                and values of combo boxes
10955       }
10956       SendToProgram(buf, cps);
10957       if(cps->reload) ResendOptions(cps);
10958     } else {
10959       SendToProgram("xboard\n", cps);
10960     }
10961 }
10962
10963 void
10964 TwoMachinesEventIfReady P((void))
10965 {
10966   static int curMess = 0;
10967   if (first.lastPing != first.lastPong) {
10968     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10969     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10970     return;
10971   }
10972   if (second.lastPing != second.lastPong) {
10973     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10974     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10975     return;
10976   }
10977   DisplayMessage("", ""); curMess = 0;
10978   TwoMachinesEvent();
10979 }
10980
10981 char *
10982 MakeName (char *template)
10983 {
10984     time_t clock;
10985     struct tm *tm;
10986     static char buf[MSG_SIZ];
10987     char *p = buf;
10988     int i;
10989
10990     clock = time((time_t *)NULL);
10991     tm = localtime(&clock);
10992
10993     while(*p++ = *template++) if(p[-1] == '%') {
10994         switch(*template++) {
10995           case 0:   *p = 0; return buf;
10996           case 'Y': i = tm->tm_year+1900; break;
10997           case 'y': i = tm->tm_year-100; break;
10998           case 'M': i = tm->tm_mon+1; break;
10999           case 'd': i = tm->tm_mday; break;
11000           case 'h': i = tm->tm_hour; break;
11001           case 'm': i = tm->tm_min; break;
11002           case 's': i = tm->tm_sec; break;
11003           default:  i = 0;
11004         }
11005         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
11006     }
11007     return buf;
11008 }
11009
11010 int
11011 CountPlayers (char *p)
11012 {
11013     int n = 0;
11014     while(p = strchr(p, '\n')) p++, n++; // count participants
11015     return n;
11016 }
11017
11018 FILE *
11019 WriteTourneyFile (char *results, FILE *f)
11020 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
11021     if(f == NULL) f = fopen(appData.tourneyFile, "w");
11022     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
11023         // create a file with tournament description
11024         fprintf(f, "-participants {%s}\n", appData.participants);
11025         fprintf(f, "-seedBase %d\n", appData.seedBase);
11026         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
11027         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
11028         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
11029         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
11030         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
11031         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
11032         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
11033         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
11034         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
11035         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
11036         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
11037         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
11038         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
11039         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
11040         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
11041         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
11042         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
11043         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
11044         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
11045         fprintf(f, "-smpCores %d\n", appData.smpCores);
11046         if(searchTime > 0)
11047                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
11048         else {
11049                 fprintf(f, "-mps %d\n", appData.movesPerSession);
11050                 fprintf(f, "-tc %s\n", appData.timeControl);
11051                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
11052         }
11053         fprintf(f, "-results \"%s\"\n", results);
11054     }
11055     return f;
11056 }
11057
11058 char *command[MAXENGINES], *mnemonic[MAXENGINES];
11059
11060 void
11061 Substitute (char *participants, int expunge)
11062 {
11063     int i, changed, changes=0, nPlayers=0;
11064     char *p, *q, *r, buf[MSG_SIZ];
11065     if(participants == NULL) return;
11066     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11067     r = p = participants; q = appData.participants;
11068     while(*p && *p == *q) {
11069         if(*p == '\n') r = p+1, nPlayers++;
11070         p++; q++;
11071     }
11072     if(*p) { // difference
11073         while(*p && *p++ != '\n');
11074         while(*q && *q++ != '\n');
11075       changed = nPlayers;
11076         changes = 1 + (strcmp(p, q) != 0);
11077     }
11078     if(changes == 1) { // a single engine mnemonic was changed
11079         q = r; while(*q) nPlayers += (*q++ == '\n');
11080         p = buf; while(*r && (*p = *r++) != '\n') p++;
11081         *p = NULLCHAR;
11082         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11083         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11084         if(mnemonic[i]) { // The substitute is valid
11085             FILE *f;
11086             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11087                 flock(fileno(f), LOCK_EX);
11088                 ParseArgsFromFile(f);
11089                 fseek(f, 0, SEEK_SET);
11090                 FREE(appData.participants); appData.participants = participants;
11091                 if(expunge) { // erase results of replaced engine
11092                     int len = strlen(appData.results), w, b, dummy;
11093                     for(i=0; i<len; i++) {
11094                         Pairing(i, nPlayers, &w, &b, &dummy);
11095                         if((w == changed || b == changed) && appData.results[i] == '*') {
11096                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11097                             fclose(f);
11098                             return;
11099                         }
11100                     }
11101                     for(i=0; i<len; i++) {
11102                         Pairing(i, nPlayers, &w, &b, &dummy);
11103                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11104                     }
11105                 }
11106                 WriteTourneyFile(appData.results, f);
11107                 fclose(f); // release lock
11108                 return;
11109             }
11110         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11111     }
11112     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11113     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11114     free(participants);
11115     return;
11116 }
11117
11118 int
11119 CheckPlayers (char *participants)
11120 {
11121         int i;
11122         char buf[MSG_SIZ], *p;
11123         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11124         while(p = strchr(participants, '\n')) {
11125             *p = NULLCHAR;
11126             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11127             if(!mnemonic[i]) {
11128                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11129                 *p = '\n';
11130                 DisplayError(buf, 0);
11131                 return 1;
11132             }
11133             *p = '\n';
11134             participants = p + 1;
11135         }
11136         return 0;
11137 }
11138
11139 int
11140 CreateTourney (char *name)
11141 {
11142         FILE *f;
11143         if(matchMode && strcmp(name, appData.tourneyFile)) {
11144              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11145         }
11146         if(name[0] == NULLCHAR) {
11147             if(appData.participants[0])
11148                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11149             return 0;
11150         }
11151         f = fopen(name, "r");
11152         if(f) { // file exists
11153             ASSIGN(appData.tourneyFile, name);
11154             ParseArgsFromFile(f); // parse it
11155         } else {
11156             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11157             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11158                 DisplayError(_("Not enough participants"), 0);
11159                 return 0;
11160             }
11161             if(CheckPlayers(appData.participants)) return 0;
11162             ASSIGN(appData.tourneyFile, name);
11163             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11164             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11165         }
11166         fclose(f);
11167         appData.noChessProgram = FALSE;
11168         appData.clockMode = TRUE;
11169         SetGNUMode();
11170         return 1;
11171 }
11172
11173 int
11174 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11175 {
11176     char buf[MSG_SIZ], *p, *q;
11177     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11178     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11179     skip = !all && group[0]; // if group requested, we start in skip mode
11180     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11181         p = names; q = buf; header = 0;
11182         while(*p && *p != '\n') *q++ = *p++;
11183         *q = 0;
11184         if(*p == '\n') p++;
11185         if(buf[0] == '#') {
11186             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11187             depth++; // we must be entering a new group
11188             if(all) continue; // suppress printing group headers when complete list requested
11189             header = 1;
11190             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11191         }
11192         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11193         if(engineList[i]) free(engineList[i]);
11194         engineList[i] = strdup(buf);
11195         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11196         if(engineMnemonic[i]) free(engineMnemonic[i]);
11197         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11198             strcat(buf, " (");
11199             sscanf(q + 8, "%s", buf + strlen(buf));
11200             strcat(buf, ")");
11201         }
11202         engineMnemonic[i] = strdup(buf);
11203         i++;
11204     }
11205     engineList[i] = engineMnemonic[i] = NULL;
11206     return i;
11207 }
11208
11209 // following implemented as macro to avoid type limitations
11210 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11211
11212 void
11213 SwapEngines (int n)
11214 {   // swap settings for first engine and other engine (so far only some selected options)
11215     int h;
11216     char *p;
11217     if(n == 0) return;
11218     SWAP(directory, p)
11219     SWAP(chessProgram, p)
11220     SWAP(isUCI, h)
11221     SWAP(hasOwnBookUCI, h)
11222     SWAP(protocolVersion, h)
11223     SWAP(reuse, h)
11224     SWAP(scoreIsAbsolute, h)
11225     SWAP(timeOdds, h)
11226     SWAP(logo, p)
11227     SWAP(pgnName, p)
11228     SWAP(pvSAN, h)
11229     SWAP(engOptions, p)
11230     SWAP(engInitString, p)
11231     SWAP(computerString, p)
11232     SWAP(features, p)
11233     SWAP(fenOverride, p)
11234     SWAP(NPS, h)
11235     SWAP(accumulateTC, h)
11236     SWAP(drawDepth, h)
11237     SWAP(host, p)
11238     SWAP(pseudo, h)
11239 }
11240
11241 int
11242 GetEngineLine (char *s, int n)
11243 {
11244     int i;
11245     char buf[MSG_SIZ];
11246     extern char *icsNames;
11247     if(!s || !*s) return 0;
11248     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11249     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11250     if(!mnemonic[i]) return 0;
11251     if(n == 11) return 1; // just testing if there was a match
11252     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11253     if(n == 1) SwapEngines(n);
11254     ParseArgsFromString(buf);
11255     if(n == 1) SwapEngines(n);
11256     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11257         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11258         ParseArgsFromString(buf);
11259     }
11260     return 1;
11261 }
11262
11263 int
11264 SetPlayer (int player, char *p)
11265 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11266     int i;
11267     char buf[MSG_SIZ], *engineName;
11268     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11269     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11270     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11271     if(mnemonic[i]) {
11272         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11273         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11274         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11275         ParseArgsFromString(buf);
11276     } else { // no engine with this nickname is installed!
11277         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11278         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11279         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11280         ModeHighlight();
11281         DisplayError(buf, 0);
11282         return 0;
11283     }
11284     free(engineName);
11285     return i;
11286 }
11287
11288 char *recentEngines;
11289
11290 void
11291 RecentEngineEvent (int nr)
11292 {
11293     int n;
11294 //    SwapEngines(1); // bump first to second
11295 //    ReplaceEngine(&second, 1); // and load it there
11296     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11297     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11298     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11299         ReplaceEngine(&first, 0);
11300         FloatToFront(&appData.recentEngineList, command[n]);
11301     }
11302 }
11303
11304 int
11305 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11306 {   // determine players from game number
11307     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11308
11309     if(appData.tourneyType == 0) {
11310         roundsPerCycle = (nPlayers - 1) | 1;
11311         pairingsPerRound = nPlayers / 2;
11312     } else if(appData.tourneyType > 0) {
11313         roundsPerCycle = nPlayers - appData.tourneyType;
11314         pairingsPerRound = appData.tourneyType;
11315     }
11316     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11317     gamesPerCycle = gamesPerRound * roundsPerCycle;
11318     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11319     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11320     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11321     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11322     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11323     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11324
11325     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11326     if(appData.roundSync) *syncInterval = gamesPerRound;
11327
11328     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11329
11330     if(appData.tourneyType == 0) {
11331         if(curPairing == (nPlayers-1)/2 ) {
11332             *whitePlayer = curRound;
11333             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11334         } else {
11335             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11336             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11337             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11338             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11339         }
11340     } else if(appData.tourneyType > 1) {
11341         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11342         *whitePlayer = curRound + appData.tourneyType;
11343     } else if(appData.tourneyType > 0) {
11344         *whitePlayer = curPairing;
11345         *blackPlayer = curRound + appData.tourneyType;
11346     }
11347
11348     // take care of white/black alternation per round.
11349     // For cycles and games this is already taken care of by default, derived from matchGame!
11350     return curRound & 1;
11351 }
11352
11353 int
11354 NextTourneyGame (int nr, int *swapColors)
11355 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11356     char *p, *q;
11357     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11358     FILE *tf;
11359     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11360     tf = fopen(appData.tourneyFile, "r");
11361     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11362     ParseArgsFromFile(tf); fclose(tf);
11363     InitTimeControls(); // TC might be altered from tourney file
11364
11365     nPlayers = CountPlayers(appData.participants); // count participants
11366     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11367     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11368
11369     if(syncInterval) {
11370         p = q = appData.results;
11371         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11372         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11373             DisplayMessage(_("Waiting for other game(s)"),"");
11374             waitingForGame = TRUE;
11375             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11376             return 0;
11377         }
11378         waitingForGame = FALSE;
11379     }
11380
11381     if(appData.tourneyType < 0) {
11382         if(nr>=0 && !pairingReceived) {
11383             char buf[1<<16];
11384             if(pairing.pr == NoProc) {
11385                 if(!appData.pairingEngine[0]) {
11386                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11387                     return 0;
11388                 }
11389                 StartChessProgram(&pairing); // starts the pairing engine
11390             }
11391             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11392             SendToProgram(buf, &pairing);
11393             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11394             SendToProgram(buf, &pairing);
11395             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11396         }
11397         pairingReceived = 0;                              // ... so we continue here
11398         *swapColors = 0;
11399         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11400         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11401         matchGame = 1; roundNr = nr / syncInterval + 1;
11402     }
11403
11404     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11405
11406     // redefine engines, engine dir, etc.
11407     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11408     if(first.pr == NoProc) {
11409       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11410       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11411     }
11412     if(second.pr == NoProc) {
11413       SwapEngines(1);
11414       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11415       SwapEngines(1);         // and make that valid for second engine by swapping
11416       InitEngine(&second, 1);
11417     }
11418     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11419     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11420     return OK;
11421 }
11422
11423 void
11424 NextMatchGame ()
11425 {   // performs game initialization that does not invoke engines, and then tries to start the game
11426     int res, firstWhite, swapColors = 0;
11427     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11428     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
11429         char buf[MSG_SIZ];
11430         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11431         if(strcmp(buf, currentDebugFile)) { // name has changed
11432             FILE *f = fopen(buf, "w");
11433             if(f) { // if opening the new file failed, just keep using the old one
11434                 ASSIGN(currentDebugFile, buf);
11435                 fclose(debugFP);
11436                 debugFP = f;
11437             }
11438             if(appData.serverFileName) {
11439                 if(serverFP) fclose(serverFP);
11440                 serverFP = fopen(appData.serverFileName, "w");
11441                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11442                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11443             }
11444         }
11445     }
11446     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11447     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11448     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11449     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11450     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11451     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11452     Reset(FALSE, first.pr != NoProc);
11453     res = LoadGameOrPosition(matchGame); // setup game
11454     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11455     if(!res) return; // abort when bad game/pos file
11456     if(appData.epd) {// in EPD mode we make sure first engine is to move
11457         firstWhite = !(forwardMostMove & 1);
11458         first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11459         second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11460     }
11461     TwoMachinesEvent();
11462 }
11463
11464 void
11465 UserAdjudicationEvent (int result)
11466 {
11467     ChessMove gameResult = GameIsDrawn;
11468
11469     if( result > 0 ) {
11470         gameResult = WhiteWins;
11471     }
11472     else if( result < 0 ) {
11473         gameResult = BlackWins;
11474     }
11475
11476     if( gameMode == TwoMachinesPlay ) {
11477         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11478     }
11479 }
11480
11481
11482 // [HGM] save: calculate checksum of game to make games easily identifiable
11483 int
11484 StringCheckSum (char *s)
11485 {
11486         int i = 0;
11487         if(s==NULL) return 0;
11488         while(*s) i = i*259 + *s++;
11489         return i;
11490 }
11491
11492 int
11493 GameCheckSum ()
11494 {
11495         int i, sum=0;
11496         for(i=backwardMostMove; i<forwardMostMove; i++) {
11497                 sum += pvInfoList[i].depth;
11498                 sum += StringCheckSum(parseList[i]);
11499                 sum += StringCheckSum(commentList[i]);
11500                 sum *= 261;
11501         }
11502         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11503         return sum + StringCheckSum(commentList[i]);
11504 } // end of save patch
11505
11506 void
11507 GameEnds (ChessMove result, char *resultDetails, int whosays)
11508 {
11509     GameMode nextGameMode;
11510     int isIcsGame;
11511     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11512
11513     if(endingGame) return; /* [HGM] crash: forbid recursion */
11514     endingGame = 1;
11515     if(twoBoards) { // [HGM] dual: switch back to one board
11516         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11517         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11518     }
11519     if (appData.debugMode) {
11520       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11521               result, resultDetails ? resultDetails : "(null)", whosays);
11522     }
11523
11524     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11525
11526     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11527
11528     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11529         /* If we are playing on ICS, the server decides when the
11530            game is over, but the engine can offer to draw, claim
11531            a draw, or resign.
11532          */
11533 #if ZIPPY
11534         if (appData.zippyPlay && first.initDone) {
11535             if (result == GameIsDrawn) {
11536                 /* In case draw still needs to be claimed */
11537                 SendToICS(ics_prefix);
11538                 SendToICS("draw\n");
11539             } else if (StrCaseStr(resultDetails, "resign")) {
11540                 SendToICS(ics_prefix);
11541                 SendToICS("resign\n");
11542             }
11543         }
11544 #endif
11545         endingGame = 0; /* [HGM] crash */
11546         return;
11547     }
11548
11549     /* If we're loading the game from a file, stop */
11550     if (whosays == GE_FILE) {
11551       (void) StopLoadGameTimer();
11552       gameFileFP = NULL;
11553     }
11554
11555     /* Cancel draw offers */
11556     first.offeredDraw = second.offeredDraw = 0;
11557
11558     /* If this is an ICS game, only ICS can really say it's done;
11559        if not, anyone can. */
11560     isIcsGame = (gameMode == IcsPlayingWhite ||
11561                  gameMode == IcsPlayingBlack ||
11562                  gameMode == IcsObserving    ||
11563                  gameMode == IcsExamining);
11564
11565     if (!isIcsGame || whosays == GE_ICS) {
11566         /* OK -- not an ICS game, or ICS said it was done */
11567         StopClocks();
11568         if (!isIcsGame && !appData.noChessProgram)
11569           SetUserThinkingEnables();
11570
11571         /* [HGM] if a machine claims the game end we verify this claim */
11572         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11573             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11574                 char claimer;
11575                 ChessMove trueResult = (ChessMove) -1;
11576
11577                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11578                                             first.twoMachinesColor[0] :
11579                                             second.twoMachinesColor[0] ;
11580
11581                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11582                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11583                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11584                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11585                 } else
11586                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11587                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11588                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11589                 } else
11590                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11591                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11592                 }
11593
11594                 // now verify win claims, but not in drop games, as we don't understand those yet
11595                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11596                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11597                     (result == WhiteWins && claimer == 'w' ||
11598                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11599                       if (appData.debugMode) {
11600                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11601                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11602                       }
11603                       if(result != trueResult) {
11604                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11605                               result = claimer == 'w' ? BlackWins : WhiteWins;
11606                               resultDetails = buf;
11607                       }
11608                 } else
11609                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11610                     && (forwardMostMove <= backwardMostMove ||
11611                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11612                         (claimer=='b')==(forwardMostMove&1))
11613                                                                                   ) {
11614                       /* [HGM] verify: draws that were not flagged are false claims */
11615                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11616                       result = claimer == 'w' ? BlackWins : WhiteWins;
11617                       resultDetails = buf;
11618                 }
11619                 /* (Claiming a loss is accepted no questions asked!) */
11620             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11621                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11622                 result = GameUnfinished;
11623                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11624             }
11625             /* [HGM] bare: don't allow bare King to win */
11626             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11627                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11628                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11629                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11630                && result != GameIsDrawn)
11631             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11632                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11633                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11634                         if(p >= 0 && p <= (int)WhiteKing) k++;
11635                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11636                 }
11637                 if (appData.debugMode) {
11638                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11639                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11640                 }
11641                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11642                         result = GameIsDrawn;
11643                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11644                         resultDetails = buf;
11645                 }
11646             }
11647         }
11648
11649
11650         if(serverMoves != NULL && !loadFlag) { char c = '=';
11651             if(result==WhiteWins) c = '+';
11652             if(result==BlackWins) c = '-';
11653             if(resultDetails != NULL)
11654                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11655         }
11656         if (resultDetails != NULL) {
11657             gameInfo.result = result;
11658             gameInfo.resultDetails = StrSave(resultDetails);
11659
11660             /* display last move only if game was not loaded from file */
11661             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11662                 DisplayMove(currentMove - 1);
11663
11664             if (forwardMostMove != 0) {
11665                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11666                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11667                                                                 ) {
11668                     if (*appData.saveGameFile != NULLCHAR) {
11669                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11670                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11671                         else
11672                         SaveGameToFile(appData.saveGameFile, TRUE);
11673                     } else if (appData.autoSaveGames) {
11674                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11675                     }
11676                     if (*appData.savePositionFile != NULLCHAR) {
11677                         SavePositionToFile(appData.savePositionFile);
11678                     }
11679                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11680                 }
11681             }
11682
11683             /* Tell program how game ended in case it is learning */
11684             /* [HGM] Moved this to after saving the PGN, just in case */
11685             /* engine died and we got here through time loss. In that */
11686             /* case we will get a fatal error writing the pipe, which */
11687             /* would otherwise lose us the PGN.                       */
11688             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11689             /* output during GameEnds should never be fatal anymore   */
11690             if (gameMode == MachinePlaysWhite ||
11691                 gameMode == MachinePlaysBlack ||
11692                 gameMode == TwoMachinesPlay ||
11693                 gameMode == IcsPlayingWhite ||
11694                 gameMode == IcsPlayingBlack ||
11695                 gameMode == BeginningOfGame) {
11696                 char buf[MSG_SIZ];
11697                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11698                         resultDetails);
11699                 if (first.pr != NoProc) {
11700                     SendToProgram(buf, &first);
11701                 }
11702                 if (second.pr != NoProc &&
11703                     gameMode == TwoMachinesPlay) {
11704                     SendToProgram(buf, &second);
11705                 }
11706             }
11707         }
11708
11709         if (appData.icsActive) {
11710             if (appData.quietPlay &&
11711                 (gameMode == IcsPlayingWhite ||
11712                  gameMode == IcsPlayingBlack)) {
11713                 SendToICS(ics_prefix);
11714                 SendToICS("set shout 1\n");
11715             }
11716             nextGameMode = IcsIdle;
11717             ics_user_moved = FALSE;
11718             /* clean up premove.  It's ugly when the game has ended and the
11719              * premove highlights are still on the board.
11720              */
11721             if (gotPremove) {
11722               gotPremove = FALSE;
11723               ClearPremoveHighlights();
11724               DrawPosition(FALSE, boards[currentMove]);
11725             }
11726             if (whosays == GE_ICS) {
11727                 switch (result) {
11728                 case WhiteWins:
11729                     if (gameMode == IcsPlayingWhite)
11730                         PlayIcsWinSound();
11731                     else if(gameMode == IcsPlayingBlack)
11732                         PlayIcsLossSound();
11733                     break;
11734                 case BlackWins:
11735                     if (gameMode == IcsPlayingBlack)
11736                         PlayIcsWinSound();
11737                     else if(gameMode == IcsPlayingWhite)
11738                         PlayIcsLossSound();
11739                     break;
11740                 case GameIsDrawn:
11741                     PlayIcsDrawSound();
11742                     break;
11743                 default:
11744                     PlayIcsUnfinishedSound();
11745                 }
11746             }
11747             if(appData.quitNext) { ExitEvent(0); return; }
11748         } else if (gameMode == EditGame ||
11749                    gameMode == PlayFromGameFile ||
11750                    gameMode == AnalyzeMode ||
11751                    gameMode == AnalyzeFile) {
11752             nextGameMode = gameMode;
11753         } else {
11754             nextGameMode = EndOfGame;
11755         }
11756         pausing = FALSE;
11757         ModeHighlight();
11758     } else {
11759         nextGameMode = gameMode;
11760     }
11761
11762     if (appData.noChessProgram) {
11763         gameMode = nextGameMode;
11764         ModeHighlight();
11765         endingGame = 0; /* [HGM] crash */
11766         return;
11767     }
11768
11769     if (first.reuse) {
11770         /* Put first chess program into idle state */
11771         if (first.pr != NoProc &&
11772             (gameMode == MachinePlaysWhite ||
11773              gameMode == MachinePlaysBlack ||
11774              gameMode == TwoMachinesPlay ||
11775              gameMode == IcsPlayingWhite ||
11776              gameMode == IcsPlayingBlack ||
11777              gameMode == BeginningOfGame)) {
11778             SendToProgram("force\n", &first);
11779             if (first.usePing) {
11780               char buf[MSG_SIZ];
11781               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11782               SendToProgram(buf, &first);
11783             }
11784         }
11785     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11786         /* Kill off first chess program */
11787         if (first.isr != NULL)
11788           RemoveInputSource(first.isr);
11789         first.isr = NULL;
11790
11791         if (first.pr != NoProc) {
11792             ExitAnalyzeMode();
11793             DoSleep( appData.delayBeforeQuit );
11794             SendToProgram("quit\n", &first);
11795             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11796             first.reload = TRUE;
11797         }
11798         first.pr = NoProc;
11799     }
11800     if (second.reuse) {
11801         /* Put second chess program into idle state */
11802         if (second.pr != NoProc &&
11803             gameMode == TwoMachinesPlay) {
11804             SendToProgram("force\n", &second);
11805             if (second.usePing) {
11806               char buf[MSG_SIZ];
11807               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11808               SendToProgram(buf, &second);
11809             }
11810         }
11811     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11812         /* Kill off second chess program */
11813         if (second.isr != NULL)
11814           RemoveInputSource(second.isr);
11815         second.isr = NULL;
11816
11817         if (second.pr != NoProc) {
11818             DoSleep( appData.delayBeforeQuit );
11819             SendToProgram("quit\n", &second);
11820             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11821             second.reload = TRUE;
11822         }
11823         second.pr = NoProc;
11824     }
11825
11826     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11827         char resChar = '=';
11828         switch (result) {
11829         case WhiteWins:
11830           resChar = '+';
11831           if (first.twoMachinesColor[0] == 'w') {
11832             first.matchWins++;
11833           } else {
11834             second.matchWins++;
11835           }
11836           break;
11837         case BlackWins:
11838           resChar = '-';
11839           if (first.twoMachinesColor[0] == 'b') {
11840             first.matchWins++;
11841           } else {
11842             second.matchWins++;
11843           }
11844           break;
11845         case GameUnfinished:
11846           resChar = ' ';
11847         default:
11848           break;
11849         }
11850
11851         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11852         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11853             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11854             ReserveGame(nextGame, resChar); // sets nextGame
11855             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11856             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11857         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11858
11859         if (nextGame <= appData.matchGames && !abortMatch) {
11860             gameMode = nextGameMode;
11861             matchGame = nextGame; // this will be overruled in tourney mode!
11862             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11863             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11864             endingGame = 0; /* [HGM] crash */
11865             return;
11866         } else {
11867             gameMode = nextGameMode;
11868             if(appData.epd) {
11869                 snprintf(buf, MSG_SIZ, "-------------------------------------- ");
11870                 OutputKibitz(2, buf);
11871                 snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
11872                 OutputKibitz(2, buf);
11873                 snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
11874                 if(second.matchWins) OutputKibitz(2, buf);
11875                 snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
11876                 OutputKibitz(2, buf);
11877             }
11878             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11879                      first.tidy, second.tidy,
11880                      first.matchWins, second.matchWins,
11881                      appData.matchGames - (first.matchWins + second.matchWins));
11882             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11883             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11884             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11885             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11886                 first.twoMachinesColor = "black\n";
11887                 second.twoMachinesColor = "white\n";
11888             } else {
11889                 first.twoMachinesColor = "white\n";
11890                 second.twoMachinesColor = "black\n";
11891             }
11892         }
11893     }
11894     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11895         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11896       ExitAnalyzeMode();
11897     gameMode = nextGameMode;
11898     ModeHighlight();
11899     endingGame = 0;  /* [HGM] crash */
11900     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11901         if(matchMode == TRUE) { // match through command line: exit with or without popup
11902             if(ranking) {
11903                 ToNrEvent(forwardMostMove);
11904                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11905                 else ExitEvent(0);
11906             } else DisplayFatalError(buf, 0, 0);
11907         } else { // match through menu; just stop, with or without popup
11908             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11909             ModeHighlight();
11910             if(ranking){
11911                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11912             } else DisplayNote(buf);
11913       }
11914       if(ranking) free(ranking);
11915     }
11916 }
11917
11918 /* Assumes program was just initialized (initString sent).
11919    Leaves program in force mode. */
11920 void
11921 FeedMovesToProgram (ChessProgramState *cps, int upto)
11922 {
11923     int i;
11924
11925     if (appData.debugMode)
11926       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11927               startedFromSetupPosition ? "position and " : "",
11928               backwardMostMove, upto, cps->which);
11929     if(currentlyInitializedVariant != gameInfo.variant) {
11930       char buf[MSG_SIZ];
11931         // [HGM] variantswitch: make engine aware of new variant
11932         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11933                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11934                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11935         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11936         SendToProgram(buf, cps);
11937         currentlyInitializedVariant = gameInfo.variant;
11938     }
11939     SendToProgram("force\n", cps);
11940     if (startedFromSetupPosition) {
11941         SendBoard(cps, backwardMostMove);
11942     if (appData.debugMode) {
11943         fprintf(debugFP, "feedMoves\n");
11944     }
11945     }
11946     for (i = backwardMostMove; i < upto; i++) {
11947         SendMoveToProgram(i, cps);
11948     }
11949 }
11950
11951
11952 int
11953 ResurrectChessProgram ()
11954 {
11955      /* The chess program may have exited.
11956         If so, restart it and feed it all the moves made so far. */
11957     static int doInit = 0;
11958
11959     if (appData.noChessProgram) return 1;
11960
11961     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11962         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11963         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11964         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11965     } else {
11966         if (first.pr != NoProc) return 1;
11967         StartChessProgram(&first);
11968     }
11969     InitChessProgram(&first, FALSE);
11970     FeedMovesToProgram(&first, currentMove);
11971
11972     if (!first.sendTime) {
11973         /* can't tell gnuchess what its clock should read,
11974            so we bow to its notion. */
11975         ResetClocks();
11976         timeRemaining[0][currentMove] = whiteTimeRemaining;
11977         timeRemaining[1][currentMove] = blackTimeRemaining;
11978     }
11979
11980     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11981                 appData.icsEngineAnalyze) && first.analysisSupport) {
11982       SendToProgram("analyze\n", &first);
11983       first.analyzing = TRUE;
11984     }
11985     return 1;
11986 }
11987
11988 /*
11989  * Button procedures
11990  */
11991 void
11992 Reset (int redraw, int init)
11993 {
11994     int i;
11995
11996     if (appData.debugMode) {
11997         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11998                 redraw, init, gameMode);
11999     }
12000     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
12001     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
12002     CleanupTail(); // [HGM] vari: delete any stored variations
12003     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
12004     pausing = pauseExamInvalid = FALSE;
12005     startedFromSetupPosition = blackPlaysFirst = FALSE;
12006     firstMove = TRUE;
12007     whiteFlag = blackFlag = FALSE;
12008     userOfferedDraw = FALSE;
12009     hintRequested = bookRequested = FALSE;
12010     first.maybeThinking = FALSE;
12011     second.maybeThinking = FALSE;
12012     first.bookSuspend = FALSE; // [HGM] book
12013     second.bookSuspend = FALSE;
12014     thinkOutput[0] = NULLCHAR;
12015     lastHint[0] = NULLCHAR;
12016     ClearGameInfo(&gameInfo);
12017     gameInfo.variant = StringToVariant(appData.variant);
12018     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
12019         gameInfo.variant = VariantUnknown;
12020         strncpy(engineVariant, appData.variant, MSG_SIZ);
12021     }
12022     ics_user_moved = ics_clock_paused = FALSE;
12023     ics_getting_history = H_FALSE;
12024     ics_gamenum = -1;
12025     white_holding[0] = black_holding[0] = NULLCHAR;
12026     ClearProgramStats();
12027     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
12028
12029     ResetFrontEnd();
12030     ClearHighlights();
12031     flipView = appData.flipView;
12032     ClearPremoveHighlights();
12033     gotPremove = FALSE;
12034     alarmSounded = FALSE;
12035     killX = killY = kill2X = kill2Y = -1; // [HGM] lion
12036
12037     GameEnds(EndOfFile, NULL, GE_PLAYER);
12038     if(appData.serverMovesName != NULL) {
12039         /* [HGM] prepare to make moves file for broadcasting */
12040         clock_t t = clock();
12041         if(serverMoves != NULL) fclose(serverMoves);
12042         serverMoves = fopen(appData.serverMovesName, "r");
12043         if(serverMoves != NULL) {
12044             fclose(serverMoves);
12045             /* delay 15 sec before overwriting, so all clients can see end */
12046             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
12047         }
12048         serverMoves = fopen(appData.serverMovesName, "w");
12049     }
12050
12051     ExitAnalyzeMode();
12052     gameMode = BeginningOfGame;
12053     ModeHighlight();
12054     if(appData.icsActive) gameInfo.variant = VariantNormal;
12055     currentMove = forwardMostMove = backwardMostMove = 0;
12056     MarkTargetSquares(1);
12057     InitPosition(redraw);
12058     for (i = 0; i < MAX_MOVES; i++) {
12059         if (commentList[i] != NULL) {
12060             free(commentList[i]);
12061             commentList[i] = NULL;
12062         }
12063     }
12064     ResetClocks();
12065     timeRemaining[0][0] = whiteTimeRemaining;
12066     timeRemaining[1][0] = blackTimeRemaining;
12067
12068     if (first.pr == NoProc) {
12069         StartChessProgram(&first);
12070     }
12071     if (init) {
12072             InitChessProgram(&first, startedFromSetupPosition);
12073     }
12074     DisplayTitle("");
12075     DisplayMessage("", "");
12076     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12077     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
12078     ClearMap();        // [HGM] exclude: invalidate map
12079 }
12080
12081 void
12082 AutoPlayGameLoop ()
12083 {
12084     for (;;) {
12085         if (!AutoPlayOneMove())
12086           return;
12087         if (matchMode || appData.timeDelay == 0)
12088           continue;
12089         if (appData.timeDelay < 0)
12090           return;
12091         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12092         break;
12093     }
12094 }
12095
12096 void
12097 AnalyzeNextGame()
12098 {
12099     ReloadGame(1); // next game
12100 }
12101
12102 int
12103 AutoPlayOneMove ()
12104 {
12105     int fromX, fromY, toX, toY;
12106
12107     if (appData.debugMode) {
12108       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12109     }
12110
12111     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12112       return FALSE;
12113
12114     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12115       pvInfoList[currentMove].depth = programStats.depth;
12116       pvInfoList[currentMove].score = programStats.score;
12117       pvInfoList[currentMove].time  = 0;
12118       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12119       else { // append analysis of final position as comment
12120         char buf[MSG_SIZ];
12121         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12122         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12123       }
12124       programStats.depth = 0;
12125     }
12126
12127     if (currentMove >= forwardMostMove) {
12128       if(gameMode == AnalyzeFile) {
12129           if(appData.loadGameIndex == -1) {
12130             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12131           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12132           } else {
12133           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12134         }
12135       }
12136 //      gameMode = EndOfGame;
12137 //      ModeHighlight();
12138
12139       /* [AS] Clear current move marker at the end of a game */
12140       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12141
12142       return FALSE;
12143     }
12144
12145     toX = moveList[currentMove][2] - AAA;
12146     toY = moveList[currentMove][3] - ONE;
12147
12148     if (moveList[currentMove][1] == '@') {
12149         if (appData.highlightLastMove) {
12150             SetHighlights(-1, -1, toX, toY);
12151         }
12152     } else {
12153         fromX = moveList[currentMove][0] - AAA;
12154         fromY = moveList[currentMove][1] - ONE;
12155
12156         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12157
12158         if(moveList[currentMove][4] == ';') { // multi-leg
12159             killX = moveList[currentMove][5] - AAA;
12160             killY = moveList[currentMove][6] - ONE;
12161         }
12162         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12163         killX = killY = -1;
12164
12165         if (appData.highlightLastMove) {
12166             SetHighlights(fromX, fromY, toX, toY);
12167         }
12168     }
12169     DisplayMove(currentMove);
12170     SendMoveToProgram(currentMove++, &first);
12171     DisplayBothClocks();
12172     DrawPosition(FALSE, boards[currentMove]);
12173     // [HGM] PV info: always display, routine tests if empty
12174     DisplayComment(currentMove - 1, commentList[currentMove]);
12175     return TRUE;
12176 }
12177
12178
12179 int
12180 LoadGameOneMove (ChessMove readAhead)
12181 {
12182     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12183     char promoChar = NULLCHAR;
12184     ChessMove moveType;
12185     char move[MSG_SIZ];
12186     char *p, *q;
12187
12188     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12189         gameMode != AnalyzeMode && gameMode != Training) {
12190         gameFileFP = NULL;
12191         return FALSE;
12192     }
12193
12194     yyboardindex = forwardMostMove;
12195     if (readAhead != EndOfFile) {
12196       moveType = readAhead;
12197     } else {
12198       if (gameFileFP == NULL)
12199           return FALSE;
12200       moveType = (ChessMove) Myylex();
12201     }
12202
12203     done = FALSE;
12204     switch (moveType) {
12205       case Comment:
12206         if (appData.debugMode)
12207           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12208         p = yy_text;
12209
12210         /* append the comment but don't display it */
12211         AppendComment(currentMove, p, FALSE);
12212         return TRUE;
12213
12214       case WhiteCapturesEnPassant:
12215       case BlackCapturesEnPassant:
12216       case WhitePromotion:
12217       case BlackPromotion:
12218       case WhiteNonPromotion:
12219       case BlackNonPromotion:
12220       case NormalMove:
12221       case FirstLeg:
12222       case WhiteKingSideCastle:
12223       case WhiteQueenSideCastle:
12224       case BlackKingSideCastle:
12225       case BlackQueenSideCastle:
12226       case WhiteKingSideCastleWild:
12227       case WhiteQueenSideCastleWild:
12228       case BlackKingSideCastleWild:
12229       case BlackQueenSideCastleWild:
12230       /* PUSH Fabien */
12231       case WhiteHSideCastleFR:
12232       case WhiteASideCastleFR:
12233       case BlackHSideCastleFR:
12234       case BlackASideCastleFR:
12235       /* POP Fabien */
12236         if (appData.debugMode)
12237           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12238         fromX = currentMoveString[0] - AAA;
12239         fromY = currentMoveString[1] - ONE;
12240         toX = currentMoveString[2] - AAA;
12241         toY = currentMoveString[3] - ONE;
12242         promoChar = currentMoveString[4];
12243         if(promoChar == ';') promoChar = currentMoveString[7];
12244         break;
12245
12246       case WhiteDrop:
12247       case BlackDrop:
12248         if (appData.debugMode)
12249           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12250         fromX = moveType == WhiteDrop ?
12251           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12252         (int) CharToPiece(ToLower(currentMoveString[0]));
12253         fromY = DROP_RANK;
12254         toX = currentMoveString[2] - AAA;
12255         toY = currentMoveString[3] - ONE;
12256         break;
12257
12258       case WhiteWins:
12259       case BlackWins:
12260       case GameIsDrawn:
12261       case GameUnfinished:
12262         if (appData.debugMode)
12263           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12264         p = strchr(yy_text, '{');
12265         if (p == NULL) p = strchr(yy_text, '(');
12266         if (p == NULL) {
12267             p = yy_text;
12268             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12269         } else {
12270             q = strchr(p, *p == '{' ? '}' : ')');
12271             if (q != NULL) *q = NULLCHAR;
12272             p++;
12273         }
12274         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12275         GameEnds(moveType, p, GE_FILE);
12276         done = TRUE;
12277         if (cmailMsgLoaded) {
12278             ClearHighlights();
12279             flipView = WhiteOnMove(currentMove);
12280             if (moveType == GameUnfinished) flipView = !flipView;
12281             if (appData.debugMode)
12282               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12283         }
12284         break;
12285
12286       case EndOfFile:
12287         if (appData.debugMode)
12288           fprintf(debugFP, "Parser hit end of file\n");
12289         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12290           case MT_NONE:
12291           case MT_CHECK:
12292             break;
12293           case MT_CHECKMATE:
12294           case MT_STAINMATE:
12295             if (WhiteOnMove(currentMove)) {
12296                 GameEnds(BlackWins, "Black mates", GE_FILE);
12297             } else {
12298                 GameEnds(WhiteWins, "White mates", GE_FILE);
12299             }
12300             break;
12301           case MT_STALEMATE:
12302             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12303             break;
12304         }
12305         done = TRUE;
12306         break;
12307
12308       case MoveNumberOne:
12309         if (lastLoadGameStart == GNUChessGame) {
12310             /* GNUChessGames have numbers, but they aren't move numbers */
12311             if (appData.debugMode)
12312               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12313                       yy_text, (int) moveType);
12314             return LoadGameOneMove(EndOfFile); /* tail recursion */
12315         }
12316         /* else fall thru */
12317
12318       case XBoardGame:
12319       case GNUChessGame:
12320       case PGNTag:
12321         /* Reached start of next game in file */
12322         if (appData.debugMode)
12323           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12324         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12325           case MT_NONE:
12326           case MT_CHECK:
12327             break;
12328           case MT_CHECKMATE:
12329           case MT_STAINMATE:
12330             if (WhiteOnMove(currentMove)) {
12331                 GameEnds(BlackWins, "Black mates", GE_FILE);
12332             } else {
12333                 GameEnds(WhiteWins, "White mates", GE_FILE);
12334             }
12335             break;
12336           case MT_STALEMATE:
12337             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12338             break;
12339         }
12340         done = TRUE;
12341         break;
12342
12343       case PositionDiagram:     /* should not happen; ignore */
12344       case ElapsedTime:         /* ignore */
12345       case NAG:                 /* ignore */
12346         if (appData.debugMode)
12347           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12348                   yy_text, (int) moveType);
12349         return LoadGameOneMove(EndOfFile); /* tail recursion */
12350
12351       case IllegalMove:
12352         if (appData.testLegality) {
12353             if (appData.debugMode)
12354               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12355             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12356                     (forwardMostMove / 2) + 1,
12357                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12358             DisplayError(move, 0);
12359             done = TRUE;
12360         } else {
12361             if (appData.debugMode)
12362               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12363                       yy_text, currentMoveString);
12364             if(currentMoveString[1] == '@') {
12365                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12366                 fromY = DROP_RANK;
12367             } else {
12368                 fromX = currentMoveString[0] - AAA;
12369                 fromY = currentMoveString[1] - ONE;
12370             }
12371             toX = currentMoveString[2] - AAA;
12372             toY = currentMoveString[3] - ONE;
12373             promoChar = currentMoveString[4];
12374         }
12375         break;
12376
12377       case AmbiguousMove:
12378         if (appData.debugMode)
12379           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12380         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12381                 (forwardMostMove / 2) + 1,
12382                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12383         DisplayError(move, 0);
12384         done = TRUE;
12385         break;
12386
12387       default:
12388       case ImpossibleMove:
12389         if (appData.debugMode)
12390           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12391         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12392                 (forwardMostMove / 2) + 1,
12393                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12394         DisplayError(move, 0);
12395         done = TRUE;
12396         break;
12397     }
12398
12399     if (done) {
12400         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12401             DrawPosition(FALSE, boards[currentMove]);
12402             DisplayBothClocks();
12403             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12404               DisplayComment(currentMove - 1, commentList[currentMove]);
12405         }
12406         (void) StopLoadGameTimer();
12407         gameFileFP = NULL;
12408         cmailOldMove = forwardMostMove;
12409         return FALSE;
12410     } else {
12411         /* currentMoveString is set as a side-effect of yylex */
12412
12413         thinkOutput[0] = NULLCHAR;
12414         MakeMove(fromX, fromY, toX, toY, promoChar);
12415         killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
12416         currentMove = forwardMostMove;
12417         return TRUE;
12418     }
12419 }
12420
12421 /* Load the nth game from the given file */
12422 int
12423 LoadGameFromFile (char *filename, int n, char *title, int useList)
12424 {
12425     FILE *f;
12426     char buf[MSG_SIZ];
12427
12428     if (strcmp(filename, "-") == 0) {
12429         f = stdin;
12430         title = "stdin";
12431     } else {
12432         f = fopen(filename, "rb");
12433         if (f == NULL) {
12434           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12435             DisplayError(buf, errno);
12436             return FALSE;
12437         }
12438     }
12439     if (fseek(f, 0, 0) == -1) {
12440         /* f is not seekable; probably a pipe */
12441         useList = FALSE;
12442     }
12443     if (useList && n == 0) {
12444         int error = GameListBuild(f);
12445         if (error) {
12446             DisplayError(_("Cannot build game list"), error);
12447         } else if (!ListEmpty(&gameList) &&
12448                    ((ListGame *) gameList.tailPred)->number > 1) {
12449             GameListPopUp(f, title);
12450             return TRUE;
12451         }
12452         GameListDestroy();
12453         n = 1;
12454     }
12455     if (n == 0) n = 1;
12456     return LoadGame(f, n, title, FALSE);
12457 }
12458
12459
12460 void
12461 MakeRegisteredMove ()
12462 {
12463     int fromX, fromY, toX, toY;
12464     char promoChar;
12465     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12466         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12467           case CMAIL_MOVE:
12468           case CMAIL_DRAW:
12469             if (appData.debugMode)
12470               fprintf(debugFP, "Restoring %s for game %d\n",
12471                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12472
12473             thinkOutput[0] = NULLCHAR;
12474             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12475             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12476             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12477             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12478             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12479             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12480             MakeMove(fromX, fromY, toX, toY, promoChar);
12481             ShowMove(fromX, fromY, toX, toY);
12482
12483             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12484               case MT_NONE:
12485               case MT_CHECK:
12486                 break;
12487
12488               case MT_CHECKMATE:
12489               case MT_STAINMATE:
12490                 if (WhiteOnMove(currentMove)) {
12491                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12492                 } else {
12493                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12494                 }
12495                 break;
12496
12497               case MT_STALEMATE:
12498                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12499                 break;
12500             }
12501
12502             break;
12503
12504           case CMAIL_RESIGN:
12505             if (WhiteOnMove(currentMove)) {
12506                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12507             } else {
12508                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12509             }
12510             break;
12511
12512           case CMAIL_ACCEPT:
12513             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12514             break;
12515
12516           default:
12517             break;
12518         }
12519     }
12520
12521     return;
12522 }
12523
12524 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12525 int
12526 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12527 {
12528     int retVal;
12529
12530     if (gameNumber > nCmailGames) {
12531         DisplayError(_("No more games in this message"), 0);
12532         return FALSE;
12533     }
12534     if (f == lastLoadGameFP) {
12535         int offset = gameNumber - lastLoadGameNumber;
12536         if (offset == 0) {
12537             cmailMsg[0] = NULLCHAR;
12538             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12539                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12540                 nCmailMovesRegistered--;
12541             }
12542             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12543             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12544                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12545             }
12546         } else {
12547             if (! RegisterMove()) return FALSE;
12548         }
12549     }
12550
12551     retVal = LoadGame(f, gameNumber, title, useList);
12552
12553     /* Make move registered during previous look at this game, if any */
12554     MakeRegisteredMove();
12555
12556     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12557         commentList[currentMove]
12558           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12559         DisplayComment(currentMove - 1, commentList[currentMove]);
12560     }
12561
12562     return retVal;
12563 }
12564
12565 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12566 int
12567 ReloadGame (int offset)
12568 {
12569     int gameNumber = lastLoadGameNumber + offset;
12570     if (lastLoadGameFP == NULL) {
12571         DisplayError(_("No game has been loaded yet"), 0);
12572         return FALSE;
12573     }
12574     if (gameNumber <= 0) {
12575         DisplayError(_("Can't back up any further"), 0);
12576         return FALSE;
12577     }
12578     if (cmailMsgLoaded) {
12579         return CmailLoadGame(lastLoadGameFP, gameNumber,
12580                              lastLoadGameTitle, lastLoadGameUseList);
12581     } else {
12582         return LoadGame(lastLoadGameFP, gameNumber,
12583                         lastLoadGameTitle, lastLoadGameUseList);
12584     }
12585 }
12586
12587 int keys[EmptySquare+1];
12588
12589 int
12590 PositionMatches (Board b1, Board b2)
12591 {
12592     int r, f, sum=0;
12593     switch(appData.searchMode) {
12594         case 1: return CompareWithRights(b1, b2);
12595         case 2:
12596             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12597                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12598             }
12599             return TRUE;
12600         case 3:
12601             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12602               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12603                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12604             }
12605             return sum==0;
12606         case 4:
12607             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12608                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12609             }
12610             return sum==0;
12611     }
12612     return TRUE;
12613 }
12614
12615 #define Q_PROMO  4
12616 #define Q_EP     3
12617 #define Q_BCASTL 2
12618 #define Q_WCASTL 1
12619
12620 int pieceList[256], quickBoard[256];
12621 ChessSquare pieceType[256] = { EmptySquare };
12622 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12623 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12624 int soughtTotal, turn;
12625 Boolean epOK, flipSearch;
12626
12627 typedef struct {
12628     unsigned char piece, to;
12629 } Move;
12630
12631 #define DSIZE (250000)
12632
12633 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12634 Move *moveDatabase = initialSpace;
12635 unsigned int movePtr, dataSize = DSIZE;
12636
12637 int
12638 MakePieceList (Board board, int *counts)
12639 {
12640     int r, f, n=Q_PROMO, total=0;
12641     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12642     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12643         int sq = f + (r<<4);
12644         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12645             quickBoard[sq] = ++n;
12646             pieceList[n] = sq;
12647             pieceType[n] = board[r][f];
12648             counts[board[r][f]]++;
12649             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12650             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12651             total++;
12652         }
12653     }
12654     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12655     return total;
12656 }
12657
12658 void
12659 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12660 {
12661     int sq = fromX + (fromY<<4);
12662     int piece = quickBoard[sq], rook;
12663     quickBoard[sq] = 0;
12664     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12665     if(piece == pieceList[1] && fromY == toY) {
12666       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12667         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12668         moveDatabase[movePtr++].piece = Q_WCASTL;
12669         quickBoard[sq] = piece;
12670         piece = quickBoard[from]; quickBoard[from] = 0;
12671         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12672       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12673         quickBoard[sq] = 0; // remove Rook
12674         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12675         moveDatabase[movePtr++].piece = Q_WCASTL;
12676         quickBoard[sq] = pieceList[1]; // put King
12677         piece = rook;
12678         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12679       }
12680     } else
12681     if(piece == pieceList[2] && fromY == toY) {
12682       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12683         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12684         moveDatabase[movePtr++].piece = Q_BCASTL;
12685         quickBoard[sq] = piece;
12686         piece = quickBoard[from]; quickBoard[from] = 0;
12687         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12688       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12689         quickBoard[sq] = 0; // remove Rook
12690         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12691         moveDatabase[movePtr++].piece = Q_BCASTL;
12692         quickBoard[sq] = pieceList[2]; // put King
12693         piece = rook;
12694         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12695       }
12696     } else
12697     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12698         quickBoard[(fromY<<4)+toX] = 0;
12699         moveDatabase[movePtr].piece = Q_EP;
12700         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12701         moveDatabase[movePtr].to = sq;
12702     } else
12703     if(promoPiece != pieceType[piece]) {
12704         moveDatabase[movePtr++].piece = Q_PROMO;
12705         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12706     }
12707     moveDatabase[movePtr].piece = piece;
12708     quickBoard[sq] = piece;
12709     movePtr++;
12710 }
12711
12712 int
12713 PackGame (Board board)
12714 {
12715     Move *newSpace = NULL;
12716     moveDatabase[movePtr].piece = 0; // terminate previous game
12717     if(movePtr > dataSize) {
12718         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12719         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12720         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12721         if(newSpace) {
12722             int i;
12723             Move *p = moveDatabase, *q = newSpace;
12724             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12725             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12726             moveDatabase = newSpace;
12727         } else { // calloc failed, we must be out of memory. Too bad...
12728             dataSize = 0; // prevent calloc events for all subsequent games
12729             return 0;     // and signal this one isn't cached
12730         }
12731     }
12732     movePtr++;
12733     MakePieceList(board, counts);
12734     return movePtr;
12735 }
12736
12737 int
12738 QuickCompare (Board board, int *minCounts, int *maxCounts)
12739 {   // compare according to search mode
12740     int r, f;
12741     switch(appData.searchMode)
12742     {
12743       case 1: // exact position match
12744         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12745         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12746             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12747         }
12748         break;
12749       case 2: // can have extra material on empty squares
12750         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12751             if(board[r][f] == EmptySquare) continue;
12752             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12753         }
12754         break;
12755       case 3: // material with exact Pawn structure
12756         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12757             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12758             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12759         } // fall through to material comparison
12760       case 4: // exact material
12761         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12762         break;
12763       case 6: // material range with given imbalance
12764         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12765         // fall through to range comparison
12766       case 5: // material range
12767         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12768     }
12769     return TRUE;
12770 }
12771
12772 int
12773 QuickScan (Board board, Move *move)
12774 {   // reconstruct game,and compare all positions in it
12775     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12776     do {
12777         int piece = move->piece;
12778         int to = move->to, from = pieceList[piece];
12779         if(found < 0) { // if already found just scan to game end for final piece count
12780           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12781            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12782            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12783                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12784             ) {
12785             static int lastCounts[EmptySquare+1];
12786             int i;
12787             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12788             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12789           } else stretch = 0;
12790           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12791           if(found >= 0 && !appData.minPieces) return found;
12792         }
12793         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12794           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12795           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12796             piece = (++move)->piece;
12797             from = pieceList[piece];
12798             counts[pieceType[piece]]--;
12799             pieceType[piece] = (ChessSquare) move->to;
12800             counts[move->to]++;
12801           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12802             counts[pieceType[quickBoard[to]]]--;
12803             quickBoard[to] = 0; total--;
12804             move++;
12805             continue;
12806           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12807             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12808             from  = pieceList[piece]; // so this must be King
12809             quickBoard[from] = 0;
12810             pieceList[piece] = to;
12811             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12812             quickBoard[from] = 0; // rook
12813             quickBoard[to] = piece;
12814             to = move->to; piece = move->piece;
12815             goto aftercastle;
12816           }
12817         }
12818         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12819         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12820         quickBoard[from] = 0;
12821       aftercastle:
12822         quickBoard[to] = piece;
12823         pieceList[piece] = to;
12824         cnt++; turn ^= 3;
12825         move++;
12826     } while(1);
12827 }
12828
12829 void
12830 InitSearch ()
12831 {
12832     int r, f;
12833     flipSearch = FALSE;
12834     CopyBoard(soughtBoard, boards[currentMove]);
12835     soughtTotal = MakePieceList(soughtBoard, maxSought);
12836     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12837     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12838     CopyBoard(reverseBoard, boards[currentMove]);
12839     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12840         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12841         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12842         reverseBoard[r][f] = piece;
12843     }
12844     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12845     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12846     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12847                  || (boards[currentMove][CASTLING][2] == NoRights ||
12848                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12849                  && (boards[currentMove][CASTLING][5] == NoRights ||
12850                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12851       ) {
12852         flipSearch = TRUE;
12853         CopyBoard(flipBoard, soughtBoard);
12854         CopyBoard(rotateBoard, reverseBoard);
12855         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12856             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12857             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12858         }
12859     }
12860     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12861     if(appData.searchMode >= 5) {
12862         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12863         MakePieceList(soughtBoard, minSought);
12864         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12865     }
12866     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12867         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12868 }
12869
12870 GameInfo dummyInfo;
12871 static int creatingBook;
12872
12873 int
12874 GameContainsPosition (FILE *f, ListGame *lg)
12875 {
12876     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12877     int fromX, fromY, toX, toY;
12878     char promoChar;
12879     static int initDone=FALSE;
12880
12881     // weed out games based on numerical tag comparison
12882     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12883     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12884     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12885     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12886     if(!initDone) {
12887         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12888         initDone = TRUE;
12889     }
12890     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12891     else CopyBoard(boards[scratch], initialPosition); // default start position
12892     if(lg->moves) {
12893         turn = btm + 1;
12894         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12895         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12896     }
12897     if(btm) plyNr++;
12898     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12899     fseek(f, lg->offset, 0);
12900     yynewfile(f);
12901     while(1) {
12902         yyboardindex = scratch;
12903         quickFlag = plyNr+1;
12904         next = Myylex();
12905         quickFlag = 0;
12906         switch(next) {
12907             case PGNTag:
12908                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12909             default:
12910                 continue;
12911
12912             case XBoardGame:
12913             case GNUChessGame:
12914                 if(plyNr) return -1; // after we have seen moves, this is for new game
12915               continue;
12916
12917             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12918             case ImpossibleMove:
12919             case WhiteWins: // game ends here with these four
12920             case BlackWins:
12921             case GameIsDrawn:
12922             case GameUnfinished:
12923                 return -1;
12924
12925             case IllegalMove:
12926                 if(appData.testLegality) return -1;
12927             case WhiteCapturesEnPassant:
12928             case BlackCapturesEnPassant:
12929             case WhitePromotion:
12930             case BlackPromotion:
12931             case WhiteNonPromotion:
12932             case BlackNonPromotion:
12933             case NormalMove:
12934             case FirstLeg:
12935             case WhiteKingSideCastle:
12936             case WhiteQueenSideCastle:
12937             case BlackKingSideCastle:
12938             case BlackQueenSideCastle:
12939             case WhiteKingSideCastleWild:
12940             case WhiteQueenSideCastleWild:
12941             case BlackKingSideCastleWild:
12942             case BlackQueenSideCastleWild:
12943             case WhiteHSideCastleFR:
12944             case WhiteASideCastleFR:
12945             case BlackHSideCastleFR:
12946             case BlackASideCastleFR:
12947                 fromX = currentMoveString[0] - AAA;
12948                 fromY = currentMoveString[1] - ONE;
12949                 toX = currentMoveString[2] - AAA;
12950                 toY = currentMoveString[3] - ONE;
12951                 promoChar = currentMoveString[4];
12952                 break;
12953             case WhiteDrop:
12954             case BlackDrop:
12955                 fromX = next == WhiteDrop ?
12956                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12957                   (int) CharToPiece(ToLower(currentMoveString[0]));
12958                 fromY = DROP_RANK;
12959                 toX = currentMoveString[2] - AAA;
12960                 toY = currentMoveString[3] - ONE;
12961                 promoChar = 0;
12962                 break;
12963         }
12964         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12965         plyNr++;
12966         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12967         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12968         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12969         if(appData.findMirror) {
12970             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12971             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12972         }
12973     }
12974 }
12975
12976 /* Load the nth game from open file f */
12977 int
12978 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12979 {
12980     ChessMove cm;
12981     char buf[MSG_SIZ];
12982     int gn = gameNumber;
12983     ListGame *lg = NULL;
12984     int numPGNTags = 0, i;
12985     int err, pos = -1;
12986     GameMode oldGameMode;
12987     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12988     char oldName[MSG_SIZ];
12989
12990     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12991
12992     if (appData.debugMode)
12993         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12994
12995     if (gameMode == Training )
12996         SetTrainingModeOff();
12997
12998     oldGameMode = gameMode;
12999     if (gameMode != BeginningOfGame) {
13000       Reset(FALSE, TRUE);
13001     }
13002     killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
13003
13004     gameFileFP = f;
13005     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
13006         fclose(lastLoadGameFP);
13007     }
13008
13009     if (useList) {
13010         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
13011
13012         if (lg) {
13013             fseek(f, lg->offset, 0);
13014             GameListHighlight(gameNumber);
13015             pos = lg->position;
13016             gn = 1;
13017         }
13018         else {
13019             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
13020               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
13021             else
13022             DisplayError(_("Game number out of range"), 0);
13023             return FALSE;
13024         }
13025     } else {
13026         GameListDestroy();
13027         if (fseek(f, 0, 0) == -1) {
13028             if (f == lastLoadGameFP ?
13029                 gameNumber == lastLoadGameNumber + 1 :
13030                 gameNumber == 1) {
13031                 gn = 1;
13032             } else {
13033                 DisplayError(_("Can't seek on game file"), 0);
13034                 return FALSE;
13035             }
13036         }
13037     }
13038     lastLoadGameFP = f;
13039     lastLoadGameNumber = gameNumber;
13040     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
13041     lastLoadGameUseList = useList;
13042
13043     yynewfile(f);
13044
13045     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
13046       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
13047                 lg->gameInfo.black);
13048             DisplayTitle(buf);
13049     } else if (*title != NULLCHAR) {
13050         if (gameNumber > 1) {
13051           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
13052             DisplayTitle(buf);
13053         } else {
13054             DisplayTitle(title);
13055         }
13056     }
13057
13058     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
13059         gameMode = PlayFromGameFile;
13060         ModeHighlight();
13061     }
13062
13063     currentMove = forwardMostMove = backwardMostMove = 0;
13064     CopyBoard(boards[0], initialPosition);
13065     StopClocks();
13066
13067     /*
13068      * Skip the first gn-1 games in the file.
13069      * Also skip over anything that precedes an identifiable
13070      * start of game marker, to avoid being confused by
13071      * garbage at the start of the file.  Currently
13072      * recognized start of game markers are the move number "1",
13073      * the pattern "gnuchess .* game", the pattern
13074      * "^[#;%] [^ ]* game file", and a PGN tag block.
13075      * A game that starts with one of the latter two patterns
13076      * will also have a move number 1, possibly
13077      * following a position diagram.
13078      * 5-4-02: Let's try being more lenient and allowing a game to
13079      * start with an unnumbered move.  Does that break anything?
13080      */
13081     cm = lastLoadGameStart = EndOfFile;
13082     while (gn > 0) {
13083         yyboardindex = forwardMostMove;
13084         cm = (ChessMove) Myylex();
13085         switch (cm) {
13086           case EndOfFile:
13087             if (cmailMsgLoaded) {
13088                 nCmailGames = CMAIL_MAX_GAMES - gn;
13089             } else {
13090                 Reset(TRUE, TRUE);
13091                 DisplayError(_("Game not found in file"), 0);
13092             }
13093             return FALSE;
13094
13095           case GNUChessGame:
13096           case XBoardGame:
13097             gn--;
13098             lastLoadGameStart = cm;
13099             break;
13100
13101           case MoveNumberOne:
13102             switch (lastLoadGameStart) {
13103               case GNUChessGame:
13104               case XBoardGame:
13105               case PGNTag:
13106                 break;
13107               case MoveNumberOne:
13108               case EndOfFile:
13109                 gn--;           /* count this game */
13110                 lastLoadGameStart = cm;
13111                 break;
13112               default:
13113                 /* impossible */
13114                 break;
13115             }
13116             break;
13117
13118           case PGNTag:
13119             switch (lastLoadGameStart) {
13120               case GNUChessGame:
13121               case PGNTag:
13122               case MoveNumberOne:
13123               case EndOfFile:
13124                 gn--;           /* count this game */
13125                 lastLoadGameStart = cm;
13126                 break;
13127               case XBoardGame:
13128                 lastLoadGameStart = cm; /* game counted already */
13129                 break;
13130               default:
13131                 /* impossible */
13132                 break;
13133             }
13134             if (gn > 0) {
13135                 do {
13136                     yyboardindex = forwardMostMove;
13137                     cm = (ChessMove) Myylex();
13138                 } while (cm == PGNTag || cm == Comment);
13139             }
13140             break;
13141
13142           case WhiteWins:
13143           case BlackWins:
13144           case GameIsDrawn:
13145             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13146                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13147                     != CMAIL_OLD_RESULT) {
13148                     nCmailResults ++ ;
13149                     cmailResult[  CMAIL_MAX_GAMES
13150                                 - gn - 1] = CMAIL_OLD_RESULT;
13151                 }
13152             }
13153             break;
13154
13155           case NormalMove:
13156           case FirstLeg:
13157             /* Only a NormalMove can be at the start of a game
13158              * without a position diagram. */
13159             if (lastLoadGameStart == EndOfFile ) {
13160               gn--;
13161               lastLoadGameStart = MoveNumberOne;
13162             }
13163             break;
13164
13165           default:
13166             break;
13167         }
13168     }
13169
13170     if (appData.debugMode)
13171       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13172
13173     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
13174
13175     if (cm == XBoardGame) {
13176         /* Skip any header junk before position diagram and/or move 1 */
13177         for (;;) {
13178             yyboardindex = forwardMostMove;
13179             cm = (ChessMove) Myylex();
13180
13181             if (cm == EndOfFile ||
13182                 cm == GNUChessGame || cm == XBoardGame) {
13183                 /* Empty game; pretend end-of-file and handle later */
13184                 cm = EndOfFile;
13185                 break;
13186             }
13187
13188             if (cm == MoveNumberOne || cm == PositionDiagram ||
13189                 cm == PGNTag || cm == Comment)
13190               break;
13191         }
13192     } else if (cm == GNUChessGame) {
13193         if (gameInfo.event != NULL) {
13194             free(gameInfo.event);
13195         }
13196         gameInfo.event = StrSave(yy_text);
13197     }
13198
13199     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13200     while (cm == PGNTag) {
13201         if (appData.debugMode)
13202           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13203         err = ParsePGNTag(yy_text, &gameInfo);
13204         if (!err) numPGNTags++;
13205
13206         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13207         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13208             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13209             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13210             InitPosition(TRUE);
13211             oldVariant = gameInfo.variant;
13212             if (appData.debugMode)
13213               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13214         }
13215
13216
13217         if (gameInfo.fen != NULL) {
13218           Board initial_position;
13219           startedFromSetupPosition = TRUE;
13220           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13221             Reset(TRUE, TRUE);
13222             DisplayError(_("Bad FEN position in file"), 0);
13223             return FALSE;
13224           }
13225           CopyBoard(boards[0], initial_position);
13226           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13227             CopyBoard(initialPosition, initial_position);
13228           if (blackPlaysFirst) {
13229             currentMove = forwardMostMove = backwardMostMove = 1;
13230             CopyBoard(boards[1], initial_position);
13231             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13232             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13233             timeRemaining[0][1] = whiteTimeRemaining;
13234             timeRemaining[1][1] = blackTimeRemaining;
13235             if (commentList[0] != NULL) {
13236               commentList[1] = commentList[0];
13237               commentList[0] = NULL;
13238             }
13239           } else {
13240             currentMove = forwardMostMove = backwardMostMove = 0;
13241           }
13242           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13243           {   int i;
13244               initialRulePlies = FENrulePlies;
13245               for( i=0; i< nrCastlingRights; i++ )
13246                   initialRights[i] = initial_position[CASTLING][i];
13247           }
13248           yyboardindex = forwardMostMove;
13249           free(gameInfo.fen);
13250           gameInfo.fen = NULL;
13251         }
13252
13253         yyboardindex = forwardMostMove;
13254         cm = (ChessMove) Myylex();
13255
13256         /* Handle comments interspersed among the tags */
13257         while (cm == Comment) {
13258             char *p;
13259             if (appData.debugMode)
13260               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13261             p = yy_text;
13262             AppendComment(currentMove, p, FALSE);
13263             yyboardindex = forwardMostMove;
13264             cm = (ChessMove) Myylex();
13265         }
13266     }
13267
13268     /* don't rely on existence of Event tag since if game was
13269      * pasted from clipboard the Event tag may not exist
13270      */
13271     if (numPGNTags > 0){
13272         char *tags;
13273         if (gameInfo.variant == VariantNormal) {
13274           VariantClass v = StringToVariant(gameInfo.event);
13275           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13276           if(v < VariantShogi) gameInfo.variant = v;
13277         }
13278         if (!matchMode) {
13279           if( appData.autoDisplayTags ) {
13280             tags = PGNTags(&gameInfo);
13281             TagsPopUp(tags, CmailMsg());
13282             free(tags);
13283           }
13284         }
13285     } else {
13286         /* Make something up, but don't display it now */
13287         SetGameInfo();
13288         TagsPopDown();
13289     }
13290
13291     if (cm == PositionDiagram) {
13292         int i, j;
13293         char *p;
13294         Board initial_position;
13295
13296         if (appData.debugMode)
13297           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13298
13299         if (!startedFromSetupPosition) {
13300             p = yy_text;
13301             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13302               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13303                 switch (*p) {
13304                   case '{':
13305                   case '[':
13306                   case '-':
13307                   case ' ':
13308                   case '\t':
13309                   case '\n':
13310                   case '\r':
13311                     break;
13312                   default:
13313                     initial_position[i][j++] = CharToPiece(*p);
13314                     break;
13315                 }
13316             while (*p == ' ' || *p == '\t' ||
13317                    *p == '\n' || *p == '\r') p++;
13318
13319             if (strncmp(p, "black", strlen("black"))==0)
13320               blackPlaysFirst = TRUE;
13321             else
13322               blackPlaysFirst = FALSE;
13323             startedFromSetupPosition = TRUE;
13324
13325             CopyBoard(boards[0], initial_position);
13326             if (blackPlaysFirst) {
13327                 currentMove = forwardMostMove = backwardMostMove = 1;
13328                 CopyBoard(boards[1], initial_position);
13329                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13330                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13331                 timeRemaining[0][1] = whiteTimeRemaining;
13332                 timeRemaining[1][1] = blackTimeRemaining;
13333                 if (commentList[0] != NULL) {
13334                     commentList[1] = commentList[0];
13335                     commentList[0] = NULL;
13336                 }
13337             } else {
13338                 currentMove = forwardMostMove = backwardMostMove = 0;
13339             }
13340         }
13341         yyboardindex = forwardMostMove;
13342         cm = (ChessMove) Myylex();
13343     }
13344
13345   if(!creatingBook) {
13346     if (first.pr == NoProc) {
13347         StartChessProgram(&first);
13348     }
13349     InitChessProgram(&first, FALSE);
13350     if(gameInfo.variant == VariantUnknown && *oldName) {
13351         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13352         gameInfo.variant = v;
13353     }
13354     SendToProgram("force\n", &first);
13355     if (startedFromSetupPosition) {
13356         SendBoard(&first, forwardMostMove);
13357     if (appData.debugMode) {
13358         fprintf(debugFP, "Load Game\n");
13359     }
13360         DisplayBothClocks();
13361     }
13362   }
13363
13364     /* [HGM] server: flag to write setup moves in broadcast file as one */
13365     loadFlag = appData.suppressLoadMoves;
13366
13367     while (cm == Comment) {
13368         char *p;
13369         if (appData.debugMode)
13370           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13371         p = yy_text;
13372         AppendComment(currentMove, p, FALSE);
13373         yyboardindex = forwardMostMove;
13374         cm = (ChessMove) Myylex();
13375     }
13376
13377     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13378         cm == WhiteWins || cm == BlackWins ||
13379         cm == GameIsDrawn || cm == GameUnfinished) {
13380         DisplayMessage("", _("No moves in game"));
13381         if (cmailMsgLoaded) {
13382             if (appData.debugMode)
13383               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13384             ClearHighlights();
13385             flipView = FALSE;
13386         }
13387         DrawPosition(FALSE, boards[currentMove]);
13388         DisplayBothClocks();
13389         gameMode = EditGame;
13390         ModeHighlight();
13391         gameFileFP = NULL;
13392         cmailOldMove = 0;
13393         return TRUE;
13394     }
13395
13396     // [HGM] PV info: routine tests if comment empty
13397     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13398         DisplayComment(currentMove - 1, commentList[currentMove]);
13399     }
13400     if (!matchMode && appData.timeDelay != 0)
13401       DrawPosition(FALSE, boards[currentMove]);
13402
13403     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13404       programStats.ok_to_send = 1;
13405     }
13406
13407     /* if the first token after the PGN tags is a move
13408      * and not move number 1, retrieve it from the parser
13409      */
13410     if (cm != MoveNumberOne)
13411         LoadGameOneMove(cm);
13412
13413     /* load the remaining moves from the file */
13414     while (LoadGameOneMove(EndOfFile)) {
13415       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13416       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13417     }
13418
13419     /* rewind to the start of the game */
13420     currentMove = backwardMostMove;
13421
13422     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13423
13424     if (oldGameMode == AnalyzeFile) {
13425       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13426       AnalyzeFileEvent();
13427     } else
13428     if (oldGameMode == AnalyzeMode) {
13429       AnalyzeFileEvent();
13430     }
13431
13432     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13433         long int w, b; // [HGM] adjourn: restore saved clock times
13434         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13435         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13436             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13437             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13438         }
13439     }
13440
13441     if(creatingBook) return TRUE;
13442     if (!matchMode && pos > 0) {
13443         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13444     } else
13445     if (matchMode || appData.timeDelay == 0) {
13446       ToEndEvent();
13447     } else if (appData.timeDelay > 0) {
13448       AutoPlayGameLoop();
13449     }
13450
13451     if (appData.debugMode)
13452         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13453
13454     loadFlag = 0; /* [HGM] true game starts */
13455     return TRUE;
13456 }
13457
13458 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13459 int
13460 ReloadPosition (int offset)
13461 {
13462     int positionNumber = lastLoadPositionNumber + offset;
13463     if (lastLoadPositionFP == NULL) {
13464         DisplayError(_("No position has been loaded yet"), 0);
13465         return FALSE;
13466     }
13467     if (positionNumber <= 0) {
13468         DisplayError(_("Can't back up any further"), 0);
13469         return FALSE;
13470     }
13471     return LoadPosition(lastLoadPositionFP, positionNumber,
13472                         lastLoadPositionTitle);
13473 }
13474
13475 /* Load the nth position from the given file */
13476 int
13477 LoadPositionFromFile (char *filename, int n, char *title)
13478 {
13479     FILE *f;
13480     char buf[MSG_SIZ];
13481
13482     if (strcmp(filename, "-") == 0) {
13483         return LoadPosition(stdin, n, "stdin");
13484     } else {
13485         f = fopen(filename, "rb");
13486         if (f == NULL) {
13487             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13488             DisplayError(buf, errno);
13489             return FALSE;
13490         } else {
13491             return LoadPosition(f, n, title);
13492         }
13493     }
13494 }
13495
13496 /* Load the nth position from the given open file, and close it */
13497 int
13498 LoadPosition (FILE *f, int positionNumber, char *title)
13499 {
13500     char *p, line[MSG_SIZ];
13501     Board initial_position;
13502     int i, j, fenMode, pn;
13503
13504     if (gameMode == Training )
13505         SetTrainingModeOff();
13506
13507     if (gameMode != BeginningOfGame) {
13508         Reset(FALSE, TRUE);
13509     }
13510     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13511         fclose(lastLoadPositionFP);
13512     }
13513     if (positionNumber == 0) positionNumber = 1;
13514     lastLoadPositionFP = f;
13515     lastLoadPositionNumber = positionNumber;
13516     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13517     if (first.pr == NoProc && !appData.noChessProgram) {
13518       StartChessProgram(&first);
13519       InitChessProgram(&first, FALSE);
13520     }
13521     pn = positionNumber;
13522     if (positionNumber < 0) {
13523         /* Negative position number means to seek to that byte offset */
13524         if (fseek(f, -positionNumber, 0) == -1) {
13525             DisplayError(_("Can't seek on position file"), 0);
13526             return FALSE;
13527         };
13528         pn = 1;
13529     } else {
13530         if (fseek(f, 0, 0) == -1) {
13531             if (f == lastLoadPositionFP ?
13532                 positionNumber == lastLoadPositionNumber + 1 :
13533                 positionNumber == 1) {
13534                 pn = 1;
13535             } else {
13536                 DisplayError(_("Can't seek on position file"), 0);
13537                 return FALSE;
13538             }
13539         }
13540     }
13541     /* See if this file is FEN or old-style xboard */
13542     if (fgets(line, MSG_SIZ, f) == NULL) {
13543         DisplayError(_("Position not found in file"), 0);
13544         return FALSE;
13545     }
13546     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13547     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13548
13549     if (pn >= 2) {
13550         if (fenMode || line[0] == '#') pn--;
13551         while (pn > 0) {
13552             /* skip positions before number pn */
13553             if (fgets(line, MSG_SIZ, f) == NULL) {
13554                 Reset(TRUE, TRUE);
13555                 DisplayError(_("Position not found in file"), 0);
13556                 return FALSE;
13557             }
13558             if (fenMode || line[0] == '#') pn--;
13559         }
13560     }
13561
13562     if (fenMode) {
13563         char *p;
13564         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13565             DisplayError(_("Bad FEN position in file"), 0);
13566             return FALSE;
13567         }
13568         if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
13569             sscanf(p+4, "%[^;]", bestMove);
13570         } else *bestMove = NULLCHAR;
13571         if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
13572             sscanf(p+4, "%[^;]", avoidMove);
13573         } else *avoidMove = NULLCHAR;
13574     } else {
13575         (void) fgets(line, MSG_SIZ, f);
13576         (void) fgets(line, MSG_SIZ, f);
13577
13578         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13579             (void) fgets(line, MSG_SIZ, f);
13580             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13581                 if (*p == ' ')
13582                   continue;
13583                 initial_position[i][j++] = CharToPiece(*p);
13584             }
13585         }
13586
13587         blackPlaysFirst = FALSE;
13588         if (!feof(f)) {
13589             (void) fgets(line, MSG_SIZ, f);
13590             if (strncmp(line, "black", strlen("black"))==0)
13591               blackPlaysFirst = TRUE;
13592         }
13593     }
13594     startedFromSetupPosition = TRUE;
13595
13596     CopyBoard(boards[0], initial_position);
13597     if (blackPlaysFirst) {
13598         currentMove = forwardMostMove = backwardMostMove = 1;
13599         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13600         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13601         CopyBoard(boards[1], initial_position);
13602         DisplayMessage("", _("Black to play"));
13603     } else {
13604         currentMove = forwardMostMove = backwardMostMove = 0;
13605         DisplayMessage("", _("White to play"));
13606     }
13607     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13608     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13609         SendToProgram("force\n", &first);
13610         SendBoard(&first, forwardMostMove);
13611     }
13612     if (appData.debugMode) {
13613 int i, j;
13614   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13615   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13616         fprintf(debugFP, "Load Position\n");
13617     }
13618
13619     if (positionNumber > 1) {
13620       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13621         DisplayTitle(line);
13622     } else {
13623         DisplayTitle(title);
13624     }
13625     gameMode = EditGame;
13626     ModeHighlight();
13627     ResetClocks();
13628     timeRemaining[0][1] = whiteTimeRemaining;
13629     timeRemaining[1][1] = blackTimeRemaining;
13630     DrawPosition(FALSE, boards[currentMove]);
13631
13632     return TRUE;
13633 }
13634
13635
13636 void
13637 CopyPlayerNameIntoFileName (char **dest, char *src)
13638 {
13639     while (*src != NULLCHAR && *src != ',') {
13640         if (*src == ' ') {
13641             *(*dest)++ = '_';
13642             src++;
13643         } else {
13644             *(*dest)++ = *src++;
13645         }
13646     }
13647 }
13648
13649 char *
13650 DefaultFileName (char *ext)
13651 {
13652     static char def[MSG_SIZ];
13653     char *p;
13654
13655     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13656         p = def;
13657         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13658         *p++ = '-';
13659         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13660         *p++ = '.';
13661         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13662     } else {
13663         def[0] = NULLCHAR;
13664     }
13665     return def;
13666 }
13667
13668 /* Save the current game to the given file */
13669 int
13670 SaveGameToFile (char *filename, int append)
13671 {
13672     FILE *f;
13673     char buf[MSG_SIZ];
13674     int result, i, t,tot=0;
13675
13676     if (strcmp(filename, "-") == 0) {
13677         return SaveGame(stdout, 0, NULL);
13678     } else {
13679         for(i=0; i<10; i++) { // upto 10 tries
13680              f = fopen(filename, append ? "a" : "w");
13681              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13682              if(f || errno != 13) break;
13683              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13684              tot += t;
13685         }
13686         if (f == NULL) {
13687             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13688             DisplayError(buf, errno);
13689             return FALSE;
13690         } else {
13691             safeStrCpy(buf, lastMsg, MSG_SIZ);
13692             DisplayMessage(_("Waiting for access to save file"), "");
13693             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13694             DisplayMessage(_("Saving game"), "");
13695             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13696             result = SaveGame(f, 0, NULL);
13697             DisplayMessage(buf, "");
13698             return result;
13699         }
13700     }
13701 }
13702
13703 char *
13704 SavePart (char *str)
13705 {
13706     static char buf[MSG_SIZ];
13707     char *p;
13708
13709     p = strchr(str, ' ');
13710     if (p == NULL) return str;
13711     strncpy(buf, str, p - str);
13712     buf[p - str] = NULLCHAR;
13713     return buf;
13714 }
13715
13716 #define PGN_MAX_LINE 75
13717
13718 #define PGN_SIDE_WHITE  0
13719 #define PGN_SIDE_BLACK  1
13720
13721 static int
13722 FindFirstMoveOutOfBook (int side)
13723 {
13724     int result = -1;
13725
13726     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13727         int index = backwardMostMove;
13728         int has_book_hit = 0;
13729
13730         if( (index % 2) != side ) {
13731             index++;
13732         }
13733
13734         while( index < forwardMostMove ) {
13735             /* Check to see if engine is in book */
13736             int depth = pvInfoList[index].depth;
13737             int score = pvInfoList[index].score;
13738             int in_book = 0;
13739
13740             if( depth <= 2 ) {
13741                 in_book = 1;
13742             }
13743             else if( score == 0 && depth == 63 ) {
13744                 in_book = 1; /* Zappa */
13745             }
13746             else if( score == 2 && depth == 99 ) {
13747                 in_book = 1; /* Abrok */
13748             }
13749
13750             has_book_hit += in_book;
13751
13752             if( ! in_book ) {
13753                 result = index;
13754
13755                 break;
13756             }
13757
13758             index += 2;
13759         }
13760     }
13761
13762     return result;
13763 }
13764
13765 void
13766 GetOutOfBookInfo (char * buf)
13767 {
13768     int oob[2];
13769     int i;
13770     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13771
13772     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13773     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13774
13775     *buf = '\0';
13776
13777     if( oob[0] >= 0 || oob[1] >= 0 ) {
13778         for( i=0; i<2; i++ ) {
13779             int idx = oob[i];
13780
13781             if( idx >= 0 ) {
13782                 if( i > 0 && oob[0] >= 0 ) {
13783                     strcat( buf, "   " );
13784                 }
13785
13786                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13787                 sprintf( buf+strlen(buf), "%s%.2f",
13788                     pvInfoList[idx].score >= 0 ? "+" : "",
13789                     pvInfoList[idx].score / 100.0 );
13790             }
13791         }
13792     }
13793 }
13794
13795 /* Save game in PGN style */
13796 static void
13797 SaveGamePGN2 (FILE *f)
13798 {
13799     int i, offset, linelen, newblock;
13800 //    char *movetext;
13801     char numtext[32];
13802     int movelen, numlen, blank;
13803     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13804
13805     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13806
13807     PrintPGNTags(f, &gameInfo);
13808
13809     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13810
13811     if (backwardMostMove > 0 || startedFromSetupPosition) {
13812         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13813         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13814         fprintf(f, "\n{--------------\n");
13815         PrintPosition(f, backwardMostMove);
13816         fprintf(f, "--------------}\n");
13817         free(fen);
13818     }
13819     else {
13820         /* [AS] Out of book annotation */
13821         if( appData.saveOutOfBookInfo ) {
13822             char buf[64];
13823
13824             GetOutOfBookInfo( buf );
13825
13826             if( buf[0] != '\0' ) {
13827                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13828             }
13829         }
13830
13831         fprintf(f, "\n");
13832     }
13833
13834     i = backwardMostMove;
13835     linelen = 0;
13836     newblock = TRUE;
13837
13838     while (i < forwardMostMove) {
13839         /* Print comments preceding this move */
13840         if (commentList[i] != NULL) {
13841             if (linelen > 0) fprintf(f, "\n");
13842             fprintf(f, "%s", commentList[i]);
13843             linelen = 0;
13844             newblock = TRUE;
13845         }
13846
13847         /* Format move number */
13848         if ((i % 2) == 0)
13849           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13850         else
13851           if (newblock)
13852             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13853           else
13854             numtext[0] = NULLCHAR;
13855
13856         numlen = strlen(numtext);
13857         newblock = FALSE;
13858
13859         /* Print move number */
13860         blank = linelen > 0 && numlen > 0;
13861         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13862             fprintf(f, "\n");
13863             linelen = 0;
13864             blank = 0;
13865         }
13866         if (blank) {
13867             fprintf(f, " ");
13868             linelen++;
13869         }
13870         fprintf(f, "%s", numtext);
13871         linelen += numlen;
13872
13873         /* Get move */
13874         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13875         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13876
13877         /* Print move */
13878         blank = linelen > 0 && movelen > 0;
13879         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13880             fprintf(f, "\n");
13881             linelen = 0;
13882             blank = 0;
13883         }
13884         if (blank) {
13885             fprintf(f, " ");
13886             linelen++;
13887         }
13888         fprintf(f, "%s", move_buffer);
13889         linelen += movelen;
13890
13891         /* [AS] Add PV info if present */
13892         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13893             /* [HGM] add time */
13894             char buf[MSG_SIZ]; int seconds;
13895
13896             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13897
13898             if( seconds <= 0)
13899               buf[0] = 0;
13900             else
13901               if( seconds < 30 )
13902                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13903               else
13904                 {
13905                   seconds = (seconds + 4)/10; // round to full seconds
13906                   if( seconds < 60 )
13907                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13908                   else
13909                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13910                 }
13911
13912             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13913                       pvInfoList[i].score >= 0 ? "+" : "",
13914                       pvInfoList[i].score / 100.0,
13915                       pvInfoList[i].depth,
13916                       buf );
13917
13918             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13919
13920             /* Print score/depth */
13921             blank = linelen > 0 && movelen > 0;
13922             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13923                 fprintf(f, "\n");
13924                 linelen = 0;
13925                 blank = 0;
13926             }
13927             if (blank) {
13928                 fprintf(f, " ");
13929                 linelen++;
13930             }
13931             fprintf(f, "%s", move_buffer);
13932             linelen += movelen;
13933         }
13934
13935         i++;
13936     }
13937
13938     /* Start a new line */
13939     if (linelen > 0) fprintf(f, "\n");
13940
13941     /* Print comments after last move */
13942     if (commentList[i] != NULL) {
13943         fprintf(f, "%s\n", commentList[i]);
13944     }
13945
13946     /* Print result */
13947     if (gameInfo.resultDetails != NULL &&
13948         gameInfo.resultDetails[0] != NULLCHAR) {
13949         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13950         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13951            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13952             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13953         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13954     } else {
13955         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13956     }
13957 }
13958
13959 /* Save game in PGN style and close the file */
13960 int
13961 SaveGamePGN (FILE *f)
13962 {
13963     SaveGamePGN2(f);
13964     fclose(f);
13965     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13966     return TRUE;
13967 }
13968
13969 /* Save game in old style and close the file */
13970 int
13971 SaveGameOldStyle (FILE *f)
13972 {
13973     int i, offset;
13974     time_t tm;
13975
13976     tm = time((time_t *) NULL);
13977
13978     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13979     PrintOpponents(f);
13980
13981     if (backwardMostMove > 0 || startedFromSetupPosition) {
13982         fprintf(f, "\n[--------------\n");
13983         PrintPosition(f, backwardMostMove);
13984         fprintf(f, "--------------]\n");
13985     } else {
13986         fprintf(f, "\n");
13987     }
13988
13989     i = backwardMostMove;
13990     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13991
13992     while (i < forwardMostMove) {
13993         if (commentList[i] != NULL) {
13994             fprintf(f, "[%s]\n", commentList[i]);
13995         }
13996
13997         if ((i % 2) == 1) {
13998             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13999             i++;
14000         } else {
14001             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
14002             i++;
14003             if (commentList[i] != NULL) {
14004                 fprintf(f, "\n");
14005                 continue;
14006             }
14007             if (i >= forwardMostMove) {
14008                 fprintf(f, "\n");
14009                 break;
14010             }
14011             fprintf(f, "%s\n", parseList[i]);
14012             i++;
14013         }
14014     }
14015
14016     if (commentList[i] != NULL) {
14017         fprintf(f, "[%s]\n", commentList[i]);
14018     }
14019
14020     /* This isn't really the old style, but it's close enough */
14021     if (gameInfo.resultDetails != NULL &&
14022         gameInfo.resultDetails[0] != NULLCHAR) {
14023         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
14024                 gameInfo.resultDetails);
14025     } else {
14026         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
14027     }
14028
14029     fclose(f);
14030     return TRUE;
14031 }
14032
14033 /* Save the current game to open file f and close the file */
14034 int
14035 SaveGame (FILE *f, int dummy, char *dummy2)
14036 {
14037     if (gameMode == EditPosition) EditPositionDone(TRUE);
14038     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
14039     if (appData.oldSaveStyle)
14040       return SaveGameOldStyle(f);
14041     else
14042       return SaveGamePGN(f);
14043 }
14044
14045 /* Save the current position to the given file */
14046 int
14047 SavePositionToFile (char *filename)
14048 {
14049     FILE *f;
14050     char buf[MSG_SIZ];
14051
14052     if (strcmp(filename, "-") == 0) {
14053         return SavePosition(stdout, 0, NULL);
14054     } else {
14055         f = fopen(filename, "a");
14056         if (f == NULL) {
14057             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
14058             DisplayError(buf, errno);
14059             return FALSE;
14060         } else {
14061             safeStrCpy(buf, lastMsg, MSG_SIZ);
14062             DisplayMessage(_("Waiting for access to save file"), "");
14063             flock(fileno(f), LOCK_EX); // [HGM] lock
14064             DisplayMessage(_("Saving position"), "");
14065             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
14066             SavePosition(f, 0, NULL);
14067             DisplayMessage(buf, "");
14068             return TRUE;
14069         }
14070     }
14071 }
14072
14073 /* Save the current position to the given open file and close the file */
14074 int
14075 SavePosition (FILE *f, int dummy, char *dummy2)
14076 {
14077     time_t tm;
14078     char *fen;
14079
14080     if (gameMode == EditPosition) EditPositionDone(TRUE);
14081     if (appData.oldSaveStyle) {
14082         tm = time((time_t *) NULL);
14083
14084         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14085         PrintOpponents(f);
14086         fprintf(f, "[--------------\n");
14087         PrintPosition(f, currentMove);
14088         fprintf(f, "--------------]\n");
14089     } else {
14090         fen = PositionToFEN(currentMove, NULL, 1);
14091         fprintf(f, "%s\n", fen);
14092         free(fen);
14093     }
14094     fclose(f);
14095     return TRUE;
14096 }
14097
14098 void
14099 ReloadCmailMsgEvent (int unregister)
14100 {
14101 #if !WIN32
14102     static char *inFilename = NULL;
14103     static char *outFilename;
14104     int i;
14105     struct stat inbuf, outbuf;
14106     int status;
14107
14108     /* Any registered moves are unregistered if unregister is set, */
14109     /* i.e. invoked by the signal handler */
14110     if (unregister) {
14111         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14112             cmailMoveRegistered[i] = FALSE;
14113             if (cmailCommentList[i] != NULL) {
14114                 free(cmailCommentList[i]);
14115                 cmailCommentList[i] = NULL;
14116             }
14117         }
14118         nCmailMovesRegistered = 0;
14119     }
14120
14121     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14122         cmailResult[i] = CMAIL_NOT_RESULT;
14123     }
14124     nCmailResults = 0;
14125
14126     if (inFilename == NULL) {
14127         /* Because the filenames are static they only get malloced once  */
14128         /* and they never get freed                                      */
14129         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14130         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14131
14132         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14133         sprintf(outFilename, "%s.out", appData.cmailGameName);
14134     }
14135
14136     status = stat(outFilename, &outbuf);
14137     if (status < 0) {
14138         cmailMailedMove = FALSE;
14139     } else {
14140         status = stat(inFilename, &inbuf);
14141         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14142     }
14143
14144     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14145        counts the games, notes how each one terminated, etc.
14146
14147        It would be nice to remove this kludge and instead gather all
14148        the information while building the game list.  (And to keep it
14149        in the game list nodes instead of having a bunch of fixed-size
14150        parallel arrays.)  Note this will require getting each game's
14151        termination from the PGN tags, as the game list builder does
14152        not process the game moves.  --mann
14153        */
14154     cmailMsgLoaded = TRUE;
14155     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14156
14157     /* Load first game in the file or popup game menu */
14158     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14159
14160 #endif /* !WIN32 */
14161     return;
14162 }
14163
14164 int
14165 RegisterMove ()
14166 {
14167     FILE *f;
14168     char string[MSG_SIZ];
14169
14170     if (   cmailMailedMove
14171         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14172         return TRUE;            /* Allow free viewing  */
14173     }
14174
14175     /* Unregister move to ensure that we don't leave RegisterMove        */
14176     /* with the move registered when the conditions for registering no   */
14177     /* longer hold                                                       */
14178     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14179         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14180         nCmailMovesRegistered --;
14181
14182         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14183           {
14184               free(cmailCommentList[lastLoadGameNumber - 1]);
14185               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14186           }
14187     }
14188
14189     if (cmailOldMove == -1) {
14190         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14191         return FALSE;
14192     }
14193
14194     if (currentMove > cmailOldMove + 1) {
14195         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14196         return FALSE;
14197     }
14198
14199     if (currentMove < cmailOldMove) {
14200         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14201         return FALSE;
14202     }
14203
14204     if (forwardMostMove > currentMove) {
14205         /* Silently truncate extra moves */
14206         TruncateGame();
14207     }
14208
14209     if (   (currentMove == cmailOldMove + 1)
14210         || (   (currentMove == cmailOldMove)
14211             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14212                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14213         if (gameInfo.result != GameUnfinished) {
14214             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14215         }
14216
14217         if (commentList[currentMove] != NULL) {
14218             cmailCommentList[lastLoadGameNumber - 1]
14219               = StrSave(commentList[currentMove]);
14220         }
14221         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14222
14223         if (appData.debugMode)
14224           fprintf(debugFP, "Saving %s for game %d\n",
14225                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14226
14227         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14228
14229         f = fopen(string, "w");
14230         if (appData.oldSaveStyle) {
14231             SaveGameOldStyle(f); /* also closes the file */
14232
14233             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14234             f = fopen(string, "w");
14235             SavePosition(f, 0, NULL); /* also closes the file */
14236         } else {
14237             fprintf(f, "{--------------\n");
14238             PrintPosition(f, currentMove);
14239             fprintf(f, "--------------}\n\n");
14240
14241             SaveGame(f, 0, NULL); /* also closes the file*/
14242         }
14243
14244         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14245         nCmailMovesRegistered ++;
14246     } else if (nCmailGames == 1) {
14247         DisplayError(_("You have not made a move yet"), 0);
14248         return FALSE;
14249     }
14250
14251     return TRUE;
14252 }
14253
14254 void
14255 MailMoveEvent ()
14256 {
14257 #if !WIN32
14258     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14259     FILE *commandOutput;
14260     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14261     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14262     int nBuffers;
14263     int i;
14264     int archived;
14265     char *arcDir;
14266
14267     if (! cmailMsgLoaded) {
14268         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14269         return;
14270     }
14271
14272     if (nCmailGames == nCmailResults) {
14273         DisplayError(_("No unfinished games"), 0);
14274         return;
14275     }
14276
14277 #if CMAIL_PROHIBIT_REMAIL
14278     if (cmailMailedMove) {
14279       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);
14280         DisplayError(msg, 0);
14281         return;
14282     }
14283 #endif
14284
14285     if (! (cmailMailedMove || RegisterMove())) return;
14286
14287     if (   cmailMailedMove
14288         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14289       snprintf(string, MSG_SIZ, partCommandString,
14290                appData.debugMode ? " -v" : "", appData.cmailGameName);
14291         commandOutput = popen(string, "r");
14292
14293         if (commandOutput == NULL) {
14294             DisplayError(_("Failed to invoke cmail"), 0);
14295         } else {
14296             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14297                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14298             }
14299             if (nBuffers > 1) {
14300                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14301                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14302                 nBytes = MSG_SIZ - 1;
14303             } else {
14304                 (void) memcpy(msg, buffer, nBytes);
14305             }
14306             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14307
14308             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14309                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14310
14311                 archived = TRUE;
14312                 for (i = 0; i < nCmailGames; i ++) {
14313                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14314                         archived = FALSE;
14315                     }
14316                 }
14317                 if (   archived
14318                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14319                         != NULL)) {
14320                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14321                            arcDir,
14322                            appData.cmailGameName,
14323                            gameInfo.date);
14324                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14325                     cmailMsgLoaded = FALSE;
14326                 }
14327             }
14328
14329             DisplayInformation(msg);
14330             pclose(commandOutput);
14331         }
14332     } else {
14333         if ((*cmailMsg) != '\0') {
14334             DisplayInformation(cmailMsg);
14335         }
14336     }
14337
14338     return;
14339 #endif /* !WIN32 */
14340 }
14341
14342 char *
14343 CmailMsg ()
14344 {
14345 #if WIN32
14346     return NULL;
14347 #else
14348     int  prependComma = 0;
14349     char number[5];
14350     char string[MSG_SIZ];       /* Space for game-list */
14351     int  i;
14352
14353     if (!cmailMsgLoaded) return "";
14354
14355     if (cmailMailedMove) {
14356       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14357     } else {
14358         /* Create a list of games left */
14359       snprintf(string, MSG_SIZ, "[");
14360         for (i = 0; i < nCmailGames; i ++) {
14361             if (! (   cmailMoveRegistered[i]
14362                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14363                 if (prependComma) {
14364                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14365                 } else {
14366                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14367                     prependComma = 1;
14368                 }
14369
14370                 strcat(string, number);
14371             }
14372         }
14373         strcat(string, "]");
14374
14375         if (nCmailMovesRegistered + nCmailResults == 0) {
14376             switch (nCmailGames) {
14377               case 1:
14378                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14379                 break;
14380
14381               case 2:
14382                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14383                 break;
14384
14385               default:
14386                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14387                          nCmailGames);
14388                 break;
14389             }
14390         } else {
14391             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14392               case 1:
14393                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14394                          string);
14395                 break;
14396
14397               case 0:
14398                 if (nCmailResults == nCmailGames) {
14399                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14400                 } else {
14401                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14402                 }
14403                 break;
14404
14405               default:
14406                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14407                          string);
14408             }
14409         }
14410     }
14411     return cmailMsg;
14412 #endif /* WIN32 */
14413 }
14414
14415 void
14416 ResetGameEvent ()
14417 {
14418     if (gameMode == Training)
14419       SetTrainingModeOff();
14420
14421     Reset(TRUE, TRUE);
14422     cmailMsgLoaded = FALSE;
14423     if (appData.icsActive) {
14424       SendToICS(ics_prefix);
14425       SendToICS("refresh\n");
14426     }
14427 }
14428
14429 void
14430 ExitEvent (int status)
14431 {
14432     exiting++;
14433     if (exiting > 2) {
14434       /* Give up on clean exit */
14435       exit(status);
14436     }
14437     if (exiting > 1) {
14438       /* Keep trying for clean exit */
14439       return;
14440     }
14441
14442     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14443     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14444
14445     if (telnetISR != NULL) {
14446       RemoveInputSource(telnetISR);
14447     }
14448     if (icsPR != NoProc) {
14449       DestroyChildProcess(icsPR, TRUE);
14450     }
14451
14452     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14453     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14454
14455     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14456     /* make sure this other one finishes before killing it!                  */
14457     if(endingGame) { int count = 0;
14458         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14459         while(endingGame && count++ < 10) DoSleep(1);
14460         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14461     }
14462
14463     /* Kill off chess programs */
14464     if (first.pr != NoProc) {
14465         ExitAnalyzeMode();
14466
14467         DoSleep( appData.delayBeforeQuit );
14468         SendToProgram("quit\n", &first);
14469         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14470     }
14471     if (second.pr != NoProc) {
14472         DoSleep( appData.delayBeforeQuit );
14473         SendToProgram("quit\n", &second);
14474         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14475     }
14476     if (first.isr != NULL) {
14477         RemoveInputSource(first.isr);
14478     }
14479     if (second.isr != NULL) {
14480         RemoveInputSource(second.isr);
14481     }
14482
14483     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14484     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14485
14486     ShutDownFrontEnd();
14487     exit(status);
14488 }
14489
14490 void
14491 PauseEngine (ChessProgramState *cps)
14492 {
14493     SendToProgram("pause\n", cps);
14494     cps->pause = 2;
14495 }
14496
14497 void
14498 UnPauseEngine (ChessProgramState *cps)
14499 {
14500     SendToProgram("resume\n", cps);
14501     cps->pause = 1;
14502 }
14503
14504 void
14505 PauseEvent ()
14506 {
14507     if (appData.debugMode)
14508         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14509     if (pausing) {
14510         pausing = FALSE;
14511         ModeHighlight();
14512         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14513             StartClocks();
14514             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14515                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14516                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14517             }
14518             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14519             HandleMachineMove(stashedInputMove, stalledEngine);
14520             stalledEngine = NULL;
14521             return;
14522         }
14523         if (gameMode == MachinePlaysWhite ||
14524             gameMode == TwoMachinesPlay   ||
14525             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14526             if(first.pause)  UnPauseEngine(&first);
14527             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14528             if(second.pause) UnPauseEngine(&second);
14529             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14530             StartClocks();
14531         } else {
14532             DisplayBothClocks();
14533         }
14534         if (gameMode == PlayFromGameFile) {
14535             if (appData.timeDelay >= 0)
14536                 AutoPlayGameLoop();
14537         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14538             Reset(FALSE, TRUE);
14539             SendToICS(ics_prefix);
14540             SendToICS("refresh\n");
14541         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14542             ForwardInner(forwardMostMove);
14543         }
14544         pauseExamInvalid = FALSE;
14545     } else {
14546         switch (gameMode) {
14547           default:
14548             return;
14549           case IcsExamining:
14550             pauseExamForwardMostMove = forwardMostMove;
14551             pauseExamInvalid = FALSE;
14552             /* fall through */
14553           case IcsObserving:
14554           case IcsPlayingWhite:
14555           case IcsPlayingBlack:
14556             pausing = TRUE;
14557             ModeHighlight();
14558             return;
14559           case PlayFromGameFile:
14560             (void) StopLoadGameTimer();
14561             pausing = TRUE;
14562             ModeHighlight();
14563             break;
14564           case BeginningOfGame:
14565             if (appData.icsActive) return;
14566             /* else fall through */
14567           case MachinePlaysWhite:
14568           case MachinePlaysBlack:
14569           case TwoMachinesPlay:
14570             if (forwardMostMove == 0)
14571               return;           /* don't pause if no one has moved */
14572             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14573                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14574                 if(onMove->pause) {           // thinking engine can be paused
14575                     PauseEngine(onMove);      // do it
14576                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14577                         PauseEngine(onMove->other);
14578                     else
14579                         SendToProgram("easy\n", onMove->other);
14580                     StopClocks();
14581                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14582             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14583                 if(first.pause) {
14584                     PauseEngine(&first);
14585                     StopClocks();
14586                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14587             } else { // human on move, pause pondering by either method
14588                 if(first.pause)
14589                     PauseEngine(&first);
14590                 else if(appData.ponderNextMove)
14591                     SendToProgram("easy\n", &first);
14592                 StopClocks();
14593             }
14594             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14595           case AnalyzeMode:
14596             pausing = TRUE;
14597             ModeHighlight();
14598             break;
14599         }
14600     }
14601 }
14602
14603 void
14604 EditCommentEvent ()
14605 {
14606     char title[MSG_SIZ];
14607
14608     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14609       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14610     } else {
14611       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14612                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14613                parseList[currentMove - 1]);
14614     }
14615
14616     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14617 }
14618
14619
14620 void
14621 EditTagsEvent ()
14622 {
14623     char *tags = PGNTags(&gameInfo);
14624     bookUp = FALSE;
14625     EditTagsPopUp(tags, NULL);
14626     free(tags);
14627 }
14628
14629 void
14630 ToggleSecond ()
14631 {
14632   if(second.analyzing) {
14633     SendToProgram("exit\n", &second);
14634     second.analyzing = FALSE;
14635   } else {
14636     if (second.pr == NoProc) StartChessProgram(&second);
14637     InitChessProgram(&second, FALSE);
14638     FeedMovesToProgram(&second, currentMove);
14639
14640     SendToProgram("analyze\n", &second);
14641     second.analyzing = TRUE;
14642   }
14643 }
14644
14645 /* Toggle ShowThinking */
14646 void
14647 ToggleShowThinking()
14648 {
14649   appData.showThinking = !appData.showThinking;
14650   ShowThinkingEvent();
14651 }
14652
14653 int
14654 AnalyzeModeEvent ()
14655 {
14656     char buf[MSG_SIZ];
14657
14658     if (!first.analysisSupport) {
14659       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14660       DisplayError(buf, 0);
14661       return 0;
14662     }
14663     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14664     if (appData.icsActive) {
14665         if (gameMode != IcsObserving) {
14666           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14667             DisplayError(buf, 0);
14668             /* secure check */
14669             if (appData.icsEngineAnalyze) {
14670                 if (appData.debugMode)
14671                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14672                 ExitAnalyzeMode();
14673                 ModeHighlight();
14674             }
14675             return 0;
14676         }
14677         /* if enable, user wants to disable icsEngineAnalyze */
14678         if (appData.icsEngineAnalyze) {
14679                 ExitAnalyzeMode();
14680                 ModeHighlight();
14681                 return 0;
14682         }
14683         appData.icsEngineAnalyze = TRUE;
14684         if (appData.debugMode)
14685             fprintf(debugFP, "ICS engine analyze starting... \n");
14686     }
14687
14688     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14689     if (appData.noChessProgram || gameMode == AnalyzeMode)
14690       return 0;
14691
14692     if (gameMode != AnalyzeFile) {
14693         if (!appData.icsEngineAnalyze) {
14694                EditGameEvent();
14695                if (gameMode != EditGame) return 0;
14696         }
14697         if (!appData.showThinking) ToggleShowThinking();
14698         ResurrectChessProgram();
14699         SendToProgram("analyze\n", &first);
14700         first.analyzing = TRUE;
14701         /*first.maybeThinking = TRUE;*/
14702         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14703         EngineOutputPopUp();
14704     }
14705     if (!appData.icsEngineAnalyze) {
14706         gameMode = AnalyzeMode;
14707         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14708     }
14709     pausing = FALSE;
14710     ModeHighlight();
14711     SetGameInfo();
14712
14713     StartAnalysisClock();
14714     GetTimeMark(&lastNodeCountTime);
14715     lastNodeCount = 0;
14716     return 1;
14717 }
14718
14719 void
14720 AnalyzeFileEvent ()
14721 {
14722     if (appData.noChessProgram || gameMode == AnalyzeFile)
14723       return;
14724
14725     if (!first.analysisSupport) {
14726       char buf[MSG_SIZ];
14727       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14728       DisplayError(buf, 0);
14729       return;
14730     }
14731
14732     if (gameMode != AnalyzeMode) {
14733         keepInfo = 1; // mere annotating should not alter PGN tags
14734         EditGameEvent();
14735         keepInfo = 0;
14736         if (gameMode != EditGame) return;
14737         if (!appData.showThinking) ToggleShowThinking();
14738         ResurrectChessProgram();
14739         SendToProgram("analyze\n", &first);
14740         first.analyzing = TRUE;
14741         /*first.maybeThinking = TRUE;*/
14742         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14743         EngineOutputPopUp();
14744     }
14745     gameMode = AnalyzeFile;
14746     pausing = FALSE;
14747     ModeHighlight();
14748
14749     StartAnalysisClock();
14750     GetTimeMark(&lastNodeCountTime);
14751     lastNodeCount = 0;
14752     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14753     AnalysisPeriodicEvent(1);
14754 }
14755
14756 void
14757 MachineWhiteEvent ()
14758 {
14759     char buf[MSG_SIZ];
14760     char *bookHit = NULL;
14761
14762     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14763       return;
14764
14765
14766     if (gameMode == PlayFromGameFile ||
14767         gameMode == TwoMachinesPlay  ||
14768         gameMode == Training         ||
14769         gameMode == AnalyzeMode      ||
14770         gameMode == EndOfGame)
14771         EditGameEvent();
14772
14773     if (gameMode == EditPosition)
14774         EditPositionDone(TRUE);
14775
14776     if (!WhiteOnMove(currentMove)) {
14777         DisplayError(_("It is not White's turn"), 0);
14778         return;
14779     }
14780
14781     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14782       ExitAnalyzeMode();
14783
14784     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14785         gameMode == AnalyzeFile)
14786         TruncateGame();
14787
14788     ResurrectChessProgram();    /* in case it isn't running */
14789     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14790         gameMode = MachinePlaysWhite;
14791         ResetClocks();
14792     } else
14793     gameMode = MachinePlaysWhite;
14794     pausing = FALSE;
14795     ModeHighlight();
14796     SetGameInfo();
14797     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14798     DisplayTitle(buf);
14799     if (first.sendName) {
14800       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14801       SendToProgram(buf, &first);
14802     }
14803     if (first.sendTime) {
14804       if (first.useColors) {
14805         SendToProgram("black\n", &first); /*gnu kludge*/
14806       }
14807       SendTimeRemaining(&first, TRUE);
14808     }
14809     if (first.useColors) {
14810       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14811     }
14812     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14813     SetMachineThinkingEnables();
14814     first.maybeThinking = TRUE;
14815     StartClocks();
14816     firstMove = FALSE;
14817
14818     if (appData.autoFlipView && !flipView) {
14819       flipView = !flipView;
14820       DrawPosition(FALSE, NULL);
14821       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14822     }
14823
14824     if(bookHit) { // [HGM] book: simulate book reply
14825         static char bookMove[MSG_SIZ]; // a bit generous?
14826
14827         programStats.nodes = programStats.depth = programStats.time =
14828         programStats.score = programStats.got_only_move = 0;
14829         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14830
14831         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14832         strcat(bookMove, bookHit);
14833         HandleMachineMove(bookMove, &first);
14834     }
14835 }
14836
14837 void
14838 MachineBlackEvent ()
14839 {
14840   char buf[MSG_SIZ];
14841   char *bookHit = NULL;
14842
14843     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14844         return;
14845
14846
14847     if (gameMode == PlayFromGameFile ||
14848         gameMode == TwoMachinesPlay  ||
14849         gameMode == Training         ||
14850         gameMode == AnalyzeMode      ||
14851         gameMode == EndOfGame)
14852         EditGameEvent();
14853
14854     if (gameMode == EditPosition)
14855         EditPositionDone(TRUE);
14856
14857     if (WhiteOnMove(currentMove)) {
14858         DisplayError(_("It is not Black's turn"), 0);
14859         return;
14860     }
14861
14862     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14863       ExitAnalyzeMode();
14864
14865     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14866         gameMode == AnalyzeFile)
14867         TruncateGame();
14868
14869     ResurrectChessProgram();    /* in case it isn't running */
14870     gameMode = MachinePlaysBlack;
14871     pausing = FALSE;
14872     ModeHighlight();
14873     SetGameInfo();
14874     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14875     DisplayTitle(buf);
14876     if (first.sendName) {
14877       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14878       SendToProgram(buf, &first);
14879     }
14880     if (first.sendTime) {
14881       if (first.useColors) {
14882         SendToProgram("white\n", &first); /*gnu kludge*/
14883       }
14884       SendTimeRemaining(&first, FALSE);
14885     }
14886     if (first.useColors) {
14887       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14888     }
14889     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14890     SetMachineThinkingEnables();
14891     first.maybeThinking = TRUE;
14892     StartClocks();
14893
14894     if (appData.autoFlipView && flipView) {
14895       flipView = !flipView;
14896       DrawPosition(FALSE, NULL);
14897       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14898     }
14899     if(bookHit) { // [HGM] book: simulate book reply
14900         static char bookMove[MSG_SIZ]; // a bit generous?
14901
14902         programStats.nodes = programStats.depth = programStats.time =
14903         programStats.score = programStats.got_only_move = 0;
14904         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14905
14906         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14907         strcat(bookMove, bookHit);
14908         HandleMachineMove(bookMove, &first);
14909     }
14910 }
14911
14912
14913 void
14914 DisplayTwoMachinesTitle ()
14915 {
14916     char buf[MSG_SIZ];
14917     if (appData.matchGames > 0) {
14918         if(appData.tourneyFile[0]) {
14919           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14920                    gameInfo.white, _("vs."), gameInfo.black,
14921                    nextGame+1, appData.matchGames+1,
14922                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14923         } else
14924         if (first.twoMachinesColor[0] == 'w') {
14925           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14926                    gameInfo.white, _("vs."),  gameInfo.black,
14927                    first.matchWins, second.matchWins,
14928                    matchGame - 1 - (first.matchWins + second.matchWins));
14929         } else {
14930           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14931                    gameInfo.white, _("vs."), gameInfo.black,
14932                    second.matchWins, first.matchWins,
14933                    matchGame - 1 - (first.matchWins + second.matchWins));
14934         }
14935     } else {
14936       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14937     }
14938     DisplayTitle(buf);
14939 }
14940
14941 void
14942 SettingsMenuIfReady ()
14943 {
14944   if (second.lastPing != second.lastPong) {
14945     DisplayMessage("", _("Waiting for second chess program"));
14946     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14947     return;
14948   }
14949   ThawUI();
14950   DisplayMessage("", "");
14951   SettingsPopUp(&second);
14952 }
14953
14954 int
14955 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14956 {
14957     char buf[MSG_SIZ];
14958     if (cps->pr == NoProc) {
14959         StartChessProgram(cps);
14960         if (cps->protocolVersion == 1) {
14961           retry();
14962           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14963         } else {
14964           /* kludge: allow timeout for initial "feature" command */
14965           if(retry != TwoMachinesEventIfReady) FreezeUI();
14966           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14967           DisplayMessage("", buf);
14968           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14969         }
14970         return 1;
14971     }
14972     return 0;
14973 }
14974
14975 void
14976 TwoMachinesEvent P((void))
14977 {
14978     int i;
14979     char buf[MSG_SIZ];
14980     ChessProgramState *onmove;
14981     char *bookHit = NULL;
14982     static int stalling = 0;
14983     TimeMark now;
14984     long wait;
14985
14986     if (appData.noChessProgram) return;
14987
14988     switch (gameMode) {
14989       case TwoMachinesPlay:
14990         return;
14991       case MachinePlaysWhite:
14992       case MachinePlaysBlack:
14993         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14994             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14995             return;
14996         }
14997         /* fall through */
14998       case BeginningOfGame:
14999       case PlayFromGameFile:
15000       case EndOfGame:
15001         EditGameEvent();
15002         if (gameMode != EditGame) return;
15003         break;
15004       case EditPosition:
15005         EditPositionDone(TRUE);
15006         break;
15007       case AnalyzeMode:
15008       case AnalyzeFile:
15009         ExitAnalyzeMode();
15010         break;
15011       case EditGame:
15012       default:
15013         break;
15014     }
15015
15016 //    forwardMostMove = currentMove;
15017     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
15018     startingEngine = TRUE;
15019
15020     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
15021
15022     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
15023     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
15024       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15025       return;
15026     }
15027     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
15028
15029     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
15030                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
15031         startingEngine = matchMode = FALSE;
15032         DisplayError("second engine does not play this", 0);
15033         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
15034         EditGameEvent(); // switch back to EditGame mode
15035         return;
15036     }
15037
15038     if(!stalling) {
15039       InitChessProgram(&second, FALSE); // unbalances ping of second engine
15040       SendToProgram("force\n", &second);
15041       stalling = 1;
15042       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
15043       return;
15044     }
15045     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
15046     if(appData.matchPause>10000 || appData.matchPause<10)
15047                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
15048     wait = SubtractTimeMarks(&now, &pauseStart);
15049     if(wait < appData.matchPause) {
15050         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
15051         return;
15052     }
15053     // we are now committed to starting the game
15054     stalling = 0;
15055     DisplayMessage("", "");
15056     if (startedFromSetupPosition) {
15057         SendBoard(&second, backwardMostMove);
15058     if (appData.debugMode) {
15059         fprintf(debugFP, "Two Machines\n");
15060     }
15061     }
15062     for (i = backwardMostMove; i < forwardMostMove; i++) {
15063         SendMoveToProgram(i, &second);
15064     }
15065
15066     gameMode = TwoMachinesPlay;
15067     pausing = startingEngine = FALSE;
15068     ModeHighlight(); // [HGM] logo: this triggers display update of logos
15069     SetGameInfo();
15070     DisplayTwoMachinesTitle();
15071     firstMove = TRUE;
15072     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
15073         onmove = &first;
15074     } else {
15075         onmove = &second;
15076     }
15077     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
15078     SendToProgram(first.computerString, &first);
15079     if (first.sendName) {
15080       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15081       SendToProgram(buf, &first);
15082     }
15083     SendToProgram(second.computerString, &second);
15084     if (second.sendName) {
15085       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15086       SendToProgram(buf, &second);
15087     }
15088
15089     ResetClocks();
15090     if (!first.sendTime || !second.sendTime) {
15091         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15092         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15093     }
15094     if (onmove->sendTime) {
15095       if (onmove->useColors) {
15096         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15097       }
15098       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15099     }
15100     if (onmove->useColors) {
15101       SendToProgram(onmove->twoMachinesColor, onmove);
15102     }
15103     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15104 //    SendToProgram("go\n", onmove);
15105     onmove->maybeThinking = TRUE;
15106     SetMachineThinkingEnables();
15107
15108     StartClocks();
15109
15110     if(bookHit) { // [HGM] book: simulate book reply
15111         static char bookMove[MSG_SIZ]; // a bit generous?
15112
15113         programStats.nodes = programStats.depth = programStats.time =
15114         programStats.score = programStats.got_only_move = 0;
15115         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15116
15117         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15118         strcat(bookMove, bookHit);
15119         savedMessage = bookMove; // args for deferred call
15120         savedState = onmove;
15121         ScheduleDelayedEvent(DeferredBookMove, 1);
15122     }
15123 }
15124
15125 void
15126 TrainingEvent ()
15127 {
15128     if (gameMode == Training) {
15129       SetTrainingModeOff();
15130       gameMode = PlayFromGameFile;
15131       DisplayMessage("", _("Training mode off"));
15132     } else {
15133       gameMode = Training;
15134       animateTraining = appData.animate;
15135
15136       /* make sure we are not already at the end of the game */
15137       if (currentMove < forwardMostMove) {
15138         SetTrainingModeOn();
15139         DisplayMessage("", _("Training mode on"));
15140       } else {
15141         gameMode = PlayFromGameFile;
15142         DisplayError(_("Already at end of game"), 0);
15143       }
15144     }
15145     ModeHighlight();
15146 }
15147
15148 void
15149 IcsClientEvent ()
15150 {
15151     if (!appData.icsActive) return;
15152     switch (gameMode) {
15153       case IcsPlayingWhite:
15154       case IcsPlayingBlack:
15155       case IcsObserving:
15156       case IcsIdle:
15157       case BeginningOfGame:
15158       case IcsExamining:
15159         return;
15160
15161       case EditGame:
15162         break;
15163
15164       case EditPosition:
15165         EditPositionDone(TRUE);
15166         break;
15167
15168       case AnalyzeMode:
15169       case AnalyzeFile:
15170         ExitAnalyzeMode();
15171         break;
15172
15173       default:
15174         EditGameEvent();
15175         break;
15176     }
15177
15178     gameMode = IcsIdle;
15179     ModeHighlight();
15180     return;
15181 }
15182
15183 void
15184 EditGameEvent ()
15185 {
15186     int i;
15187
15188     switch (gameMode) {
15189       case Training:
15190         SetTrainingModeOff();
15191         break;
15192       case MachinePlaysWhite:
15193       case MachinePlaysBlack:
15194       case BeginningOfGame:
15195         SendToProgram("force\n", &first);
15196         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15197             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15198                 char buf[MSG_SIZ];
15199                 abortEngineThink = TRUE;
15200                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15201                 SendToProgram(buf, &first);
15202                 DisplayMessage("Aborting engine think", "");
15203                 FreezeUI();
15204             }
15205         }
15206         SetUserThinkingEnables();
15207         break;
15208       case PlayFromGameFile:
15209         (void) StopLoadGameTimer();
15210         if (gameFileFP != NULL) {
15211             gameFileFP = NULL;
15212         }
15213         break;
15214       case EditPosition:
15215         EditPositionDone(TRUE);
15216         break;
15217       case AnalyzeMode:
15218       case AnalyzeFile:
15219         ExitAnalyzeMode();
15220         SendToProgram("force\n", &first);
15221         break;
15222       case TwoMachinesPlay:
15223         GameEnds(EndOfFile, NULL, GE_PLAYER);
15224         ResurrectChessProgram();
15225         SetUserThinkingEnables();
15226         break;
15227       case EndOfGame:
15228         ResurrectChessProgram();
15229         break;
15230       case IcsPlayingBlack:
15231       case IcsPlayingWhite:
15232         DisplayError(_("Warning: You are still playing a game"), 0);
15233         break;
15234       case IcsObserving:
15235         DisplayError(_("Warning: You are still observing a game"), 0);
15236         break;
15237       case IcsExamining:
15238         DisplayError(_("Warning: You are still examining a game"), 0);
15239         break;
15240       case IcsIdle:
15241         break;
15242       case EditGame:
15243       default:
15244         return;
15245     }
15246
15247     pausing = FALSE;
15248     StopClocks();
15249     first.offeredDraw = second.offeredDraw = 0;
15250
15251     if (gameMode == PlayFromGameFile) {
15252         whiteTimeRemaining = timeRemaining[0][currentMove];
15253         blackTimeRemaining = timeRemaining[1][currentMove];
15254         DisplayTitle("");
15255     }
15256
15257     if (gameMode == MachinePlaysWhite ||
15258         gameMode == MachinePlaysBlack ||
15259         gameMode == TwoMachinesPlay ||
15260         gameMode == EndOfGame) {
15261         i = forwardMostMove;
15262         while (i > currentMove) {
15263             SendToProgram("undo\n", &first);
15264             i--;
15265         }
15266         if(!adjustedClock) {
15267         whiteTimeRemaining = timeRemaining[0][currentMove];
15268         blackTimeRemaining = timeRemaining[1][currentMove];
15269         DisplayBothClocks();
15270         }
15271         if (whiteFlag || blackFlag) {
15272             whiteFlag = blackFlag = 0;
15273         }
15274         DisplayTitle("");
15275     }
15276
15277     gameMode = EditGame;
15278     ModeHighlight();
15279     SetGameInfo();
15280 }
15281
15282
15283 void
15284 EditPositionEvent ()
15285 {
15286     if (gameMode == EditPosition) {
15287         EditGameEvent();
15288         return;
15289     }
15290
15291     EditGameEvent();
15292     if (gameMode != EditGame) return;
15293
15294     gameMode = EditPosition;
15295     ModeHighlight();
15296     SetGameInfo();
15297     if (currentMove > 0)
15298       CopyBoard(boards[0], boards[currentMove]);
15299
15300     blackPlaysFirst = !WhiteOnMove(currentMove);
15301     ResetClocks();
15302     currentMove = forwardMostMove = backwardMostMove = 0;
15303     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15304     DisplayMove(-1);
15305     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15306 }
15307
15308 void
15309 ExitAnalyzeMode ()
15310 {
15311     /* [DM] icsEngineAnalyze - possible call from other functions */
15312     if (appData.icsEngineAnalyze) {
15313         appData.icsEngineAnalyze = FALSE;
15314
15315         DisplayMessage("",_("Close ICS engine analyze..."));
15316     }
15317     if (first.analysisSupport && first.analyzing) {
15318       SendToBoth("exit\n");
15319       first.analyzing = second.analyzing = FALSE;
15320     }
15321     thinkOutput[0] = NULLCHAR;
15322 }
15323
15324 void
15325 EditPositionDone (Boolean fakeRights)
15326 {
15327     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15328
15329     startedFromSetupPosition = TRUE;
15330     InitChessProgram(&first, FALSE);
15331     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15332       boards[0][EP_STATUS] = EP_NONE;
15333       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15334       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15335         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15336         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15337       } else boards[0][CASTLING][2] = NoRights;
15338       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15339         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15340         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15341       } else boards[0][CASTLING][5] = NoRights;
15342       if(gameInfo.variant == VariantSChess) {
15343         int i;
15344         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15345           boards[0][VIRGIN][i] = 0;
15346           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15347           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15348         }
15349       }
15350     }
15351     SendToProgram("force\n", &first);
15352     if (blackPlaysFirst) {
15353         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15354         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15355         currentMove = forwardMostMove = backwardMostMove = 1;
15356         CopyBoard(boards[1], boards[0]);
15357     } else {
15358         currentMove = forwardMostMove = backwardMostMove = 0;
15359     }
15360     SendBoard(&first, forwardMostMove);
15361     if (appData.debugMode) {
15362         fprintf(debugFP, "EditPosDone\n");
15363     }
15364     DisplayTitle("");
15365     DisplayMessage("", "");
15366     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15367     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15368     gameMode = EditGame;
15369     ModeHighlight();
15370     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15371     ClearHighlights(); /* [AS] */
15372 }
15373
15374 /* Pause for `ms' milliseconds */
15375 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15376 void
15377 TimeDelay (long ms)
15378 {
15379     TimeMark m1, m2;
15380
15381     GetTimeMark(&m1);
15382     do {
15383         GetTimeMark(&m2);
15384     } while (SubtractTimeMarks(&m2, &m1) < ms);
15385 }
15386
15387 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15388 void
15389 SendMultiLineToICS (char *buf)
15390 {
15391     char temp[MSG_SIZ+1], *p;
15392     int len;
15393
15394     len = strlen(buf);
15395     if (len > MSG_SIZ)
15396       len = MSG_SIZ;
15397
15398     strncpy(temp, buf, len);
15399     temp[len] = 0;
15400
15401     p = temp;
15402     while (*p) {
15403         if (*p == '\n' || *p == '\r')
15404           *p = ' ';
15405         ++p;
15406     }
15407
15408     strcat(temp, "\n");
15409     SendToICS(temp);
15410     SendToPlayer(temp, strlen(temp));
15411 }
15412
15413 void
15414 SetWhiteToPlayEvent ()
15415 {
15416     if (gameMode == EditPosition) {
15417         blackPlaysFirst = FALSE;
15418         DisplayBothClocks();    /* works because currentMove is 0 */
15419     } else if (gameMode == IcsExamining) {
15420         SendToICS(ics_prefix);
15421         SendToICS("tomove white\n");
15422     }
15423 }
15424
15425 void
15426 SetBlackToPlayEvent ()
15427 {
15428     if (gameMode == EditPosition) {
15429         blackPlaysFirst = TRUE;
15430         currentMove = 1;        /* kludge */
15431         DisplayBothClocks();
15432         currentMove = 0;
15433     } else if (gameMode == IcsExamining) {
15434         SendToICS(ics_prefix);
15435         SendToICS("tomove black\n");
15436     }
15437 }
15438
15439 void
15440 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15441 {
15442     char buf[MSG_SIZ];
15443     ChessSquare piece = boards[0][y][x];
15444     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15445     static int lastVariant;
15446
15447     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15448
15449     switch (selection) {
15450       case ClearBoard:
15451         fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15452         MarkTargetSquares(1);
15453         CopyBoard(currentBoard, boards[0]);
15454         CopyBoard(menuBoard, initialPosition);
15455         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15456             SendToICS(ics_prefix);
15457             SendToICS("bsetup clear\n");
15458         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15459             SendToICS(ics_prefix);
15460             SendToICS("clearboard\n");
15461         } else {
15462             int nonEmpty = 0;
15463             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15464                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15465                 for (y = 0; y < BOARD_HEIGHT; y++) {
15466                     if (gameMode == IcsExamining) {
15467                         if (boards[currentMove][y][x] != EmptySquare) {
15468                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15469                                     AAA + x, ONE + y);
15470                             SendToICS(buf);
15471                         }
15472                     } else if(boards[0][y][x] != DarkSquare) {
15473                         if(boards[0][y][x] != p) nonEmpty++;
15474                         boards[0][y][x] = p;
15475                     }
15476                 }
15477             }
15478             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15479                 int r;
15480                 for(r = 0; r < BOARD_HEIGHT; r++) {
15481                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15482                     ChessSquare p = menuBoard[r][x];
15483                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15484                   }
15485                 }
15486                 DisplayMessage("Clicking clock again restores position", "");
15487                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15488                 if(!nonEmpty) { // asked to clear an empty board
15489                     CopyBoard(boards[0], menuBoard);
15490                 } else
15491                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15492                     CopyBoard(boards[0], initialPosition);
15493                 } else
15494                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15495                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15496                     CopyBoard(boards[0], erasedBoard);
15497                 } else
15498                     CopyBoard(erasedBoard, currentBoard);
15499
15500             }
15501         }
15502         if (gameMode == EditPosition) {
15503             DrawPosition(FALSE, boards[0]);
15504         }
15505         break;
15506
15507       case WhitePlay:
15508         SetWhiteToPlayEvent();
15509         break;
15510
15511       case BlackPlay:
15512         SetBlackToPlayEvent();
15513         break;
15514
15515       case EmptySquare:
15516         if (gameMode == IcsExamining) {
15517             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15518             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15519             SendToICS(buf);
15520         } else {
15521             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15522                 if(x == BOARD_LEFT-2) {
15523                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15524                     boards[0][y][1] = 0;
15525                 } else
15526                 if(x == BOARD_RGHT+1) {
15527                     if(y >= gameInfo.holdingsSize) break;
15528                     boards[0][y][BOARD_WIDTH-2] = 0;
15529                 } else break;
15530             }
15531             boards[0][y][x] = EmptySquare;
15532             DrawPosition(FALSE, boards[0]);
15533         }
15534         break;
15535
15536       case PromotePiece:
15537         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15538            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15539             selection = (ChessSquare) (PROMOTED(piece));
15540         } else if(piece == EmptySquare) selection = WhiteSilver;
15541         else selection = (ChessSquare)((int)piece - 1);
15542         goto defaultlabel;
15543
15544       case DemotePiece:
15545         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15546            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15547             selection = (ChessSquare) (DEMOTED(piece));
15548         } else if(piece == EmptySquare) selection = BlackSilver;
15549         else selection = (ChessSquare)((int)piece + 1);
15550         goto defaultlabel;
15551
15552       case WhiteQueen:
15553       case BlackQueen:
15554         if(gameInfo.variant == VariantShatranj ||
15555            gameInfo.variant == VariantXiangqi  ||
15556            gameInfo.variant == VariantCourier  ||
15557            gameInfo.variant == VariantASEAN    ||
15558            gameInfo.variant == VariantMakruk     )
15559             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15560         goto defaultlabel;
15561
15562       case WhiteKing:
15563       case BlackKing:
15564         if(gameInfo.variant == VariantXiangqi)
15565             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15566         if(gameInfo.variant == VariantKnightmate)
15567             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15568       default:
15569         defaultlabel:
15570         if (gameMode == IcsExamining) {
15571             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15572             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15573                      PieceToChar(selection), AAA + x, ONE + y);
15574             SendToICS(buf);
15575         } else {
15576             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15577                 int n;
15578                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15579                     n = PieceToNumber(selection - BlackPawn);
15580                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15581                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15582                     boards[0][BOARD_HEIGHT-1-n][1]++;
15583                 } else
15584                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15585                     n = PieceToNumber(selection);
15586                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15587                     boards[0][n][BOARD_WIDTH-1] = selection;
15588                     boards[0][n][BOARD_WIDTH-2]++;
15589                 }
15590             } else
15591             boards[0][y][x] = selection;
15592             DrawPosition(TRUE, boards[0]);
15593             ClearHighlights();
15594             fromX = fromY = -1;
15595         }
15596         break;
15597     }
15598 }
15599
15600
15601 void
15602 DropMenuEvent (ChessSquare selection, int x, int y)
15603 {
15604     ChessMove moveType;
15605
15606     switch (gameMode) {
15607       case IcsPlayingWhite:
15608       case MachinePlaysBlack:
15609         if (!WhiteOnMove(currentMove)) {
15610             DisplayMoveError(_("It is Black's turn"));
15611             return;
15612         }
15613         moveType = WhiteDrop;
15614         break;
15615       case IcsPlayingBlack:
15616       case MachinePlaysWhite:
15617         if (WhiteOnMove(currentMove)) {
15618             DisplayMoveError(_("It is White's turn"));
15619             return;
15620         }
15621         moveType = BlackDrop;
15622         break;
15623       case EditGame:
15624         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15625         break;
15626       default:
15627         return;
15628     }
15629
15630     if (moveType == BlackDrop && selection < BlackPawn) {
15631       selection = (ChessSquare) ((int) selection
15632                                  + (int) BlackPawn - (int) WhitePawn);
15633     }
15634     if (boards[currentMove][y][x] != EmptySquare) {
15635         DisplayMoveError(_("That square is occupied"));
15636         return;
15637     }
15638
15639     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15640 }
15641
15642 void
15643 AcceptEvent ()
15644 {
15645     /* Accept a pending offer of any kind from opponent */
15646
15647     if (appData.icsActive) {
15648         SendToICS(ics_prefix);
15649         SendToICS("accept\n");
15650     } else if (cmailMsgLoaded) {
15651         if (currentMove == cmailOldMove &&
15652             commentList[cmailOldMove] != NULL &&
15653             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15654                    "Black offers a draw" : "White offers a draw")) {
15655             TruncateGame();
15656             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15657             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15658         } else {
15659             DisplayError(_("There is no pending offer on this move"), 0);
15660             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15661         }
15662     } else {
15663         /* Not used for offers from chess program */
15664     }
15665 }
15666
15667 void
15668 DeclineEvent ()
15669 {
15670     /* Decline a pending offer of any kind from opponent */
15671
15672     if (appData.icsActive) {
15673         SendToICS(ics_prefix);
15674         SendToICS("decline\n");
15675     } else if (cmailMsgLoaded) {
15676         if (currentMove == cmailOldMove &&
15677             commentList[cmailOldMove] != NULL &&
15678             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15679                    "Black offers a draw" : "White offers a draw")) {
15680 #ifdef NOTDEF
15681             AppendComment(cmailOldMove, "Draw declined", TRUE);
15682             DisplayComment(cmailOldMove - 1, "Draw declined");
15683 #endif /*NOTDEF*/
15684         } else {
15685             DisplayError(_("There is no pending offer on this move"), 0);
15686         }
15687     } else {
15688         /* Not used for offers from chess program */
15689     }
15690 }
15691
15692 void
15693 RematchEvent ()
15694 {
15695     /* Issue ICS rematch command */
15696     if (appData.icsActive) {
15697         SendToICS(ics_prefix);
15698         SendToICS("rematch\n");
15699     }
15700 }
15701
15702 void
15703 CallFlagEvent ()
15704 {
15705     /* Call your opponent's flag (claim a win on time) */
15706     if (appData.icsActive) {
15707         SendToICS(ics_prefix);
15708         SendToICS("flag\n");
15709     } else {
15710         switch (gameMode) {
15711           default:
15712             return;
15713           case MachinePlaysWhite:
15714             if (whiteFlag) {
15715                 if (blackFlag)
15716                   GameEnds(GameIsDrawn, "Both players ran out of time",
15717                            GE_PLAYER);
15718                 else
15719                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15720             } else {
15721                 DisplayError(_("Your opponent is not out of time"), 0);
15722             }
15723             break;
15724           case MachinePlaysBlack:
15725             if (blackFlag) {
15726                 if (whiteFlag)
15727                   GameEnds(GameIsDrawn, "Both players ran out of time",
15728                            GE_PLAYER);
15729                 else
15730                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15731             } else {
15732                 DisplayError(_("Your opponent is not out of time"), 0);
15733             }
15734             break;
15735         }
15736     }
15737 }
15738
15739 void
15740 ClockClick (int which)
15741 {       // [HGM] code moved to back-end from winboard.c
15742         if(which) { // black clock
15743           if (gameMode == EditPosition || gameMode == IcsExamining) {
15744             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15745             SetBlackToPlayEvent();
15746           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15747                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15748           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15749           } else if (shiftKey) {
15750             AdjustClock(which, -1);
15751           } else if (gameMode == IcsPlayingWhite ||
15752                      gameMode == MachinePlaysBlack) {
15753             CallFlagEvent();
15754           }
15755         } else { // white clock
15756           if (gameMode == EditPosition || gameMode == IcsExamining) {
15757             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15758             SetWhiteToPlayEvent();
15759           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15760                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15761           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15762           } else if (shiftKey) {
15763             AdjustClock(which, -1);
15764           } else if (gameMode == IcsPlayingBlack ||
15765                    gameMode == MachinePlaysWhite) {
15766             CallFlagEvent();
15767           }
15768         }
15769 }
15770
15771 void
15772 DrawEvent ()
15773 {
15774     /* Offer draw or accept pending draw offer from opponent */
15775
15776     if (appData.icsActive) {
15777         /* Note: tournament rules require draw offers to be
15778            made after you make your move but before you punch
15779            your clock.  Currently ICS doesn't let you do that;
15780            instead, you immediately punch your clock after making
15781            a move, but you can offer a draw at any time. */
15782
15783         SendToICS(ics_prefix);
15784         SendToICS("draw\n");
15785         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
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             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15792             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15793         } else if (currentMove == cmailOldMove + 1) {
15794             char *offer = WhiteOnMove(cmailOldMove) ?
15795               "White offers a draw" : "Black offers a draw";
15796             AppendComment(currentMove, offer, TRUE);
15797             DisplayComment(currentMove - 1, offer);
15798             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15799         } else {
15800             DisplayError(_("You must make your move before offering a draw"), 0);
15801             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15802         }
15803     } else if (first.offeredDraw) {
15804         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15805     } else {
15806         if (first.sendDrawOffers) {
15807             SendToProgram("draw\n", &first);
15808             userOfferedDraw = TRUE;
15809         }
15810     }
15811 }
15812
15813 void
15814 AdjournEvent ()
15815 {
15816     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15817
15818     if (appData.icsActive) {
15819         SendToICS(ics_prefix);
15820         SendToICS("adjourn\n");
15821     } else {
15822         /* Currently GNU Chess doesn't offer or accept Adjourns */
15823     }
15824 }
15825
15826
15827 void
15828 AbortEvent ()
15829 {
15830     /* Offer Abort or accept pending Abort offer from opponent */
15831
15832     if (appData.icsActive) {
15833         SendToICS(ics_prefix);
15834         SendToICS("abort\n");
15835     } else {
15836         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15837     }
15838 }
15839
15840 void
15841 ResignEvent ()
15842 {
15843     /* Resign.  You can do this even if it's not your turn. */
15844
15845     if (appData.icsActive) {
15846         SendToICS(ics_prefix);
15847         SendToICS("resign\n");
15848     } else {
15849         switch (gameMode) {
15850           case MachinePlaysWhite:
15851             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15852             break;
15853           case MachinePlaysBlack:
15854             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15855             break;
15856           case EditGame:
15857             if (cmailMsgLoaded) {
15858                 TruncateGame();
15859                 if (WhiteOnMove(cmailOldMove)) {
15860                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15861                 } else {
15862                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15863                 }
15864                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15865             }
15866             break;
15867           default:
15868             break;
15869         }
15870     }
15871 }
15872
15873
15874 void
15875 StopObservingEvent ()
15876 {
15877     /* Stop observing current games */
15878     SendToICS(ics_prefix);
15879     SendToICS("unobserve\n");
15880 }
15881
15882 void
15883 StopExaminingEvent ()
15884 {
15885     /* Stop observing current game */
15886     SendToICS(ics_prefix);
15887     SendToICS("unexamine\n");
15888 }
15889
15890 void
15891 ForwardInner (int target)
15892 {
15893     int limit; int oldSeekGraphUp = seekGraphUp;
15894
15895     if (appData.debugMode)
15896         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15897                 target, currentMove, forwardMostMove);
15898
15899     if (gameMode == EditPosition)
15900       return;
15901
15902     seekGraphUp = FALSE;
15903     MarkTargetSquares(1);
15904     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
15905
15906     if (gameMode == PlayFromGameFile && !pausing)
15907       PauseEvent();
15908
15909     if (gameMode == IcsExamining && pausing)
15910       limit = pauseExamForwardMostMove;
15911     else
15912       limit = forwardMostMove;
15913
15914     if (target > limit) target = limit;
15915
15916     if (target > 0 && moveList[target - 1][0]) {
15917         int fromX, fromY, toX, toY;
15918         toX = moveList[target - 1][2] - AAA;
15919         toY = moveList[target - 1][3] - ONE;
15920         if (moveList[target - 1][1] == '@') {
15921             if (appData.highlightLastMove) {
15922                 SetHighlights(-1, -1, toX, toY);
15923             }
15924         } else {
15925             fromX = moveList[target - 1][0] - AAA;
15926             fromY = moveList[target - 1][1] - ONE;
15927             if (target == currentMove + 1) {
15928                 if(moveList[target - 1][4] == ';') { // multi-leg
15929                     killX = moveList[target - 1][5] - AAA;
15930                     killY = moveList[target - 1][6] - ONE;
15931                 }
15932                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15933                 killX = killY = -1;
15934             }
15935             if (appData.highlightLastMove) {
15936                 SetHighlights(fromX, fromY, toX, toY);
15937             }
15938         }
15939     }
15940     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15941         gameMode == Training || gameMode == PlayFromGameFile ||
15942         gameMode == AnalyzeFile) {
15943         while (currentMove < target) {
15944             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15945             SendMoveToProgram(currentMove++, &first);
15946         }
15947     } else {
15948         currentMove = target;
15949     }
15950
15951     if (gameMode == EditGame || gameMode == EndOfGame) {
15952         whiteTimeRemaining = timeRemaining[0][currentMove];
15953         blackTimeRemaining = timeRemaining[1][currentMove];
15954     }
15955     DisplayBothClocks();
15956     DisplayMove(currentMove - 1);
15957     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15958     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15959     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15960         DisplayComment(currentMove - 1, commentList[currentMove]);
15961     }
15962     ClearMap(); // [HGM] exclude: invalidate map
15963 }
15964
15965
15966 void
15967 ForwardEvent ()
15968 {
15969     if (gameMode == IcsExamining && !pausing) {
15970         SendToICS(ics_prefix);
15971         SendToICS("forward\n");
15972     } else {
15973         ForwardInner(currentMove + 1);
15974     }
15975 }
15976
15977 void
15978 ToEndEvent ()
15979 {
15980     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15981         /* to optimze, we temporarily turn off analysis mode while we feed
15982          * the remaining moves to the engine. Otherwise we get analysis output
15983          * after each move.
15984          */
15985         if (first.analysisSupport) {
15986           SendToProgram("exit\nforce\n", &first);
15987           first.analyzing = FALSE;
15988         }
15989     }
15990
15991     if (gameMode == IcsExamining && !pausing) {
15992         SendToICS(ics_prefix);
15993         SendToICS("forward 999999\n");
15994     } else {
15995         ForwardInner(forwardMostMove);
15996     }
15997
15998     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15999         /* we have fed all the moves, so reactivate analysis mode */
16000         SendToProgram("analyze\n", &first);
16001         first.analyzing = TRUE;
16002         /*first.maybeThinking = TRUE;*/
16003         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16004     }
16005 }
16006
16007 void
16008 BackwardInner (int target)
16009 {
16010     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
16011
16012     if (appData.debugMode)
16013         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
16014                 target, currentMove, forwardMostMove);
16015
16016     if (gameMode == EditPosition) return;
16017     seekGraphUp = FALSE;
16018     MarkTargetSquares(1);
16019     fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
16020     if (currentMove <= backwardMostMove) {
16021         ClearHighlights();
16022         DrawPosition(full_redraw, boards[currentMove]);
16023         return;
16024     }
16025     if (gameMode == PlayFromGameFile && !pausing)
16026       PauseEvent();
16027
16028     if (moveList[target][0]) {
16029         int fromX, fromY, toX, toY;
16030         toX = moveList[target][2] - AAA;
16031         toY = moveList[target][3] - ONE;
16032         if (moveList[target][1] == '@') {
16033             if (appData.highlightLastMove) {
16034                 SetHighlights(-1, -1, toX, toY);
16035             }
16036         } else {
16037             fromX = moveList[target][0] - AAA;
16038             fromY = moveList[target][1] - ONE;
16039             if (target == currentMove - 1) {
16040                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
16041             }
16042             if (appData.highlightLastMove) {
16043                 SetHighlights(fromX, fromY, toX, toY);
16044             }
16045         }
16046     }
16047     if (gameMode == EditGame || gameMode==AnalyzeMode ||
16048         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
16049         while (currentMove > target) {
16050             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
16051                 // null move cannot be undone. Reload program with move history before it.
16052                 int i;
16053                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
16054                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
16055                 }
16056                 SendBoard(&first, i);
16057               if(second.analyzing) SendBoard(&second, i);
16058                 for(currentMove=i; currentMove<target; currentMove++) {
16059                     SendMoveToProgram(currentMove, &first);
16060                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
16061                 }
16062                 break;
16063             }
16064             SendToBoth("undo\n");
16065             currentMove--;
16066         }
16067     } else {
16068         currentMove = target;
16069     }
16070
16071     if (gameMode == EditGame || gameMode == EndOfGame) {
16072         whiteTimeRemaining = timeRemaining[0][currentMove];
16073         blackTimeRemaining = timeRemaining[1][currentMove];
16074     }
16075     DisplayBothClocks();
16076     DisplayMove(currentMove - 1);
16077     DrawPosition(full_redraw, boards[currentMove]);
16078     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16079     // [HGM] PV info: routine tests if comment empty
16080     DisplayComment(currentMove - 1, commentList[currentMove]);
16081     ClearMap(); // [HGM] exclude: invalidate map
16082 }
16083
16084 void
16085 BackwardEvent ()
16086 {
16087     if (gameMode == IcsExamining && !pausing) {
16088         SendToICS(ics_prefix);
16089         SendToICS("backward\n");
16090     } else {
16091         BackwardInner(currentMove - 1);
16092     }
16093 }
16094
16095 void
16096 ToStartEvent ()
16097 {
16098     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16099         /* to optimize, we temporarily turn off analysis mode while we undo
16100          * all the moves. Otherwise we get analysis output after each undo.
16101          */
16102         if (first.analysisSupport) {
16103           SendToProgram("exit\nforce\n", &first);
16104           first.analyzing = FALSE;
16105         }
16106     }
16107
16108     if (gameMode == IcsExamining && !pausing) {
16109         SendToICS(ics_prefix);
16110         SendToICS("backward 999999\n");
16111     } else {
16112         BackwardInner(backwardMostMove);
16113     }
16114
16115     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16116         /* we have fed all the moves, so reactivate analysis mode */
16117         SendToProgram("analyze\n", &first);
16118         first.analyzing = TRUE;
16119         /*first.maybeThinking = TRUE;*/
16120         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16121     }
16122 }
16123
16124 void
16125 ToNrEvent (int to)
16126 {
16127   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16128   if (to >= forwardMostMove) to = forwardMostMove;
16129   if (to <= backwardMostMove) to = backwardMostMove;
16130   if (to < currentMove) {
16131     BackwardInner(to);
16132   } else {
16133     ForwardInner(to);
16134   }
16135 }
16136
16137 void
16138 RevertEvent (Boolean annotate)
16139 {
16140     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16141         return;
16142     }
16143     if (gameMode != IcsExamining) {
16144         DisplayError(_("You are not examining a game"), 0);
16145         return;
16146     }
16147     if (pausing) {
16148         DisplayError(_("You can't revert while pausing"), 0);
16149         return;
16150     }
16151     SendToICS(ics_prefix);
16152     SendToICS("revert\n");
16153 }
16154
16155 void
16156 RetractMoveEvent ()
16157 {
16158     switch (gameMode) {
16159       case MachinePlaysWhite:
16160       case MachinePlaysBlack:
16161         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16162             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16163             return;
16164         }
16165         if (forwardMostMove < 2) return;
16166         currentMove = forwardMostMove = forwardMostMove - 2;
16167         whiteTimeRemaining = timeRemaining[0][currentMove];
16168         blackTimeRemaining = timeRemaining[1][currentMove];
16169         DisplayBothClocks();
16170         DisplayMove(currentMove - 1);
16171         ClearHighlights();/*!! could figure this out*/
16172         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16173         SendToProgram("remove\n", &first);
16174         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16175         break;
16176
16177       case BeginningOfGame:
16178       default:
16179         break;
16180
16181       case IcsPlayingWhite:
16182       case IcsPlayingBlack:
16183         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16184             SendToICS(ics_prefix);
16185             SendToICS("takeback 2\n");
16186         } else {
16187             SendToICS(ics_prefix);
16188             SendToICS("takeback 1\n");
16189         }
16190         break;
16191     }
16192 }
16193
16194 void
16195 MoveNowEvent ()
16196 {
16197     ChessProgramState *cps;
16198
16199     switch (gameMode) {
16200       case MachinePlaysWhite:
16201         if (!WhiteOnMove(forwardMostMove)) {
16202             DisplayError(_("It is your turn"), 0);
16203             return;
16204         }
16205         cps = &first;
16206         break;
16207       case MachinePlaysBlack:
16208         if (WhiteOnMove(forwardMostMove)) {
16209             DisplayError(_("It is your turn"), 0);
16210             return;
16211         }
16212         cps = &first;
16213         break;
16214       case TwoMachinesPlay:
16215         if (WhiteOnMove(forwardMostMove) ==
16216             (first.twoMachinesColor[0] == 'w')) {
16217             cps = &first;
16218         } else {
16219             cps = &second;
16220         }
16221         break;
16222       case BeginningOfGame:
16223       default:
16224         return;
16225     }
16226     SendToProgram("?\n", cps);
16227 }
16228
16229 void
16230 TruncateGameEvent ()
16231 {
16232     EditGameEvent();
16233     if (gameMode != EditGame) return;
16234     TruncateGame();
16235 }
16236
16237 void
16238 TruncateGame ()
16239 {
16240     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16241     if (forwardMostMove > currentMove) {
16242         if (gameInfo.resultDetails != NULL) {
16243             free(gameInfo.resultDetails);
16244             gameInfo.resultDetails = NULL;
16245             gameInfo.result = GameUnfinished;
16246         }
16247         forwardMostMove = currentMove;
16248         HistorySet(parseList, backwardMostMove, forwardMostMove,
16249                    currentMove-1);
16250     }
16251 }
16252
16253 void
16254 HintEvent ()
16255 {
16256     if (appData.noChessProgram) return;
16257     switch (gameMode) {
16258       case MachinePlaysWhite:
16259         if (WhiteOnMove(forwardMostMove)) {
16260             DisplayError(_("Wait until your turn."), 0);
16261             return;
16262         }
16263         break;
16264       case BeginningOfGame:
16265       case MachinePlaysBlack:
16266         if (!WhiteOnMove(forwardMostMove)) {
16267             DisplayError(_("Wait until your turn."), 0);
16268             return;
16269         }
16270         break;
16271       default:
16272         DisplayError(_("No hint available"), 0);
16273         return;
16274     }
16275     SendToProgram("hint\n", &first);
16276     hintRequested = TRUE;
16277 }
16278
16279 int
16280 SaveSelected (FILE *g, int dummy, char *dummy2)
16281 {
16282     ListGame * lg = (ListGame *) gameList.head;
16283     int nItem, cnt=0;
16284     FILE *f;
16285
16286     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16287         DisplayError(_("Game list not loaded or empty"), 0);
16288         return 0;
16289     }
16290
16291     creatingBook = TRUE; // suppresses stuff during load game
16292
16293     /* Get list size */
16294     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16295         if(lg->position >= 0) { // selected?
16296             LoadGame(f, nItem, "", TRUE);
16297             SaveGamePGN2(g); // leaves g open
16298             cnt++; DoEvents();
16299         }
16300         lg = (ListGame *) lg->node.succ;
16301     }
16302
16303     fclose(g);
16304     creatingBook = FALSE;
16305
16306     return cnt;
16307 }
16308
16309 void
16310 CreateBookEvent ()
16311 {
16312     ListGame * lg = (ListGame *) gameList.head;
16313     FILE *f, *g;
16314     int nItem;
16315     static int secondTime = FALSE;
16316
16317     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16318         DisplayError(_("Game list not loaded or empty"), 0);
16319         return;
16320     }
16321
16322     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16323         fclose(g);
16324         secondTime++;
16325         DisplayNote(_("Book file exists! Try again for overwrite."));
16326         return;
16327     }
16328
16329     creatingBook = TRUE;
16330     secondTime = FALSE;
16331
16332     /* Get list size */
16333     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16334         if(lg->position >= 0) {
16335             LoadGame(f, nItem, "", TRUE);
16336             AddGameToBook(TRUE);
16337             DoEvents();
16338         }
16339         lg = (ListGame *) lg->node.succ;
16340     }
16341
16342     creatingBook = FALSE;
16343     FlushBook();
16344 }
16345
16346 void
16347 BookEvent ()
16348 {
16349     if (appData.noChessProgram) return;
16350     switch (gameMode) {
16351       case MachinePlaysWhite:
16352         if (WhiteOnMove(forwardMostMove)) {
16353             DisplayError(_("Wait until your turn."), 0);
16354             return;
16355         }
16356         break;
16357       case BeginningOfGame:
16358       case MachinePlaysBlack:
16359         if (!WhiteOnMove(forwardMostMove)) {
16360             DisplayError(_("Wait until your turn."), 0);
16361             return;
16362         }
16363         break;
16364       case EditPosition:
16365         EditPositionDone(TRUE);
16366         break;
16367       case TwoMachinesPlay:
16368         return;
16369       default:
16370         break;
16371     }
16372     SendToProgram("bk\n", &first);
16373     bookOutput[0] = NULLCHAR;
16374     bookRequested = TRUE;
16375 }
16376
16377 void
16378 AboutGameEvent ()
16379 {
16380     char *tags = PGNTags(&gameInfo);
16381     TagsPopUp(tags, CmailMsg());
16382     free(tags);
16383 }
16384
16385 /* end button procedures */
16386
16387 void
16388 PrintPosition (FILE *fp, int move)
16389 {
16390     int i, j;
16391
16392     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16393         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16394             char c = PieceToChar(boards[move][i][j]);
16395             fputc(c == '?' ? '.' : c, fp);
16396             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16397         }
16398     }
16399     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16400       fprintf(fp, "white to play\n");
16401     else
16402       fprintf(fp, "black to play\n");
16403 }
16404
16405 void
16406 PrintOpponents (FILE *fp)
16407 {
16408     if (gameInfo.white != NULL) {
16409         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16410     } else {
16411         fprintf(fp, "\n");
16412     }
16413 }
16414
16415 /* Find last component of program's own name, using some heuristics */
16416 void
16417 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16418 {
16419     char *p, *q, c;
16420     int local = (strcmp(host, "localhost") == 0);
16421     while (!local && (p = strchr(prog, ';')) != NULL) {
16422         p++;
16423         while (*p == ' ') p++;
16424         prog = p;
16425     }
16426     if (*prog == '"' || *prog == '\'') {
16427         q = strchr(prog + 1, *prog);
16428     } else {
16429         q = strchr(prog, ' ');
16430     }
16431     if (q == NULL) q = prog + strlen(prog);
16432     p = q;
16433     while (p >= prog && *p != '/' && *p != '\\') p--;
16434     p++;
16435     if(p == prog && *p == '"') p++;
16436     c = *q; *q = 0;
16437     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16438     memcpy(buf, p, q - p);
16439     buf[q - p] = NULLCHAR;
16440     if (!local) {
16441         strcat(buf, "@");
16442         strcat(buf, host);
16443     }
16444 }
16445
16446 char *
16447 TimeControlTagValue ()
16448 {
16449     char buf[MSG_SIZ];
16450     if (!appData.clockMode) {
16451       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16452     } else if (movesPerSession > 0) {
16453       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16454     } else if (timeIncrement == 0) {
16455       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16456     } else {
16457       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16458     }
16459     return StrSave(buf);
16460 }
16461
16462 void
16463 SetGameInfo ()
16464 {
16465     /* This routine is used only for certain modes */
16466     VariantClass v = gameInfo.variant;
16467     ChessMove r = GameUnfinished;
16468     char *p = NULL;
16469
16470     if(keepInfo) return;
16471
16472     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16473         r = gameInfo.result;
16474         p = gameInfo.resultDetails;
16475         gameInfo.resultDetails = NULL;
16476     }
16477     ClearGameInfo(&gameInfo);
16478     gameInfo.variant = v;
16479
16480     switch (gameMode) {
16481       case MachinePlaysWhite:
16482         gameInfo.event = StrSave( appData.pgnEventHeader );
16483         gameInfo.site = StrSave(HostName());
16484         gameInfo.date = PGNDate();
16485         gameInfo.round = StrSave("-");
16486         gameInfo.white = StrSave(first.tidy);
16487         gameInfo.black = StrSave(UserName());
16488         gameInfo.timeControl = TimeControlTagValue();
16489         break;
16490
16491       case MachinePlaysBlack:
16492         gameInfo.event = StrSave( appData.pgnEventHeader );
16493         gameInfo.site = StrSave(HostName());
16494         gameInfo.date = PGNDate();
16495         gameInfo.round = StrSave("-");
16496         gameInfo.white = StrSave(UserName());
16497         gameInfo.black = StrSave(first.tidy);
16498         gameInfo.timeControl = TimeControlTagValue();
16499         break;
16500
16501       case TwoMachinesPlay:
16502         gameInfo.event = StrSave( appData.pgnEventHeader );
16503         gameInfo.site = StrSave(HostName());
16504         gameInfo.date = PGNDate();
16505         if (roundNr > 0) {
16506             char buf[MSG_SIZ];
16507             snprintf(buf, MSG_SIZ, "%d", roundNr);
16508             gameInfo.round = StrSave(buf);
16509         } else {
16510             gameInfo.round = StrSave("-");
16511         }
16512         if (first.twoMachinesColor[0] == 'w') {
16513             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16514             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16515         } else {
16516             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16517             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16518         }
16519         gameInfo.timeControl = TimeControlTagValue();
16520         break;
16521
16522       case EditGame:
16523         gameInfo.event = StrSave("Edited game");
16524         gameInfo.site = StrSave(HostName());
16525         gameInfo.date = PGNDate();
16526         gameInfo.round = StrSave("-");
16527         gameInfo.white = StrSave("-");
16528         gameInfo.black = StrSave("-");
16529         gameInfo.result = r;
16530         gameInfo.resultDetails = p;
16531         break;
16532
16533       case EditPosition:
16534         gameInfo.event = StrSave("Edited position");
16535         gameInfo.site = StrSave(HostName());
16536         gameInfo.date = PGNDate();
16537         gameInfo.round = StrSave("-");
16538         gameInfo.white = StrSave("-");
16539         gameInfo.black = StrSave("-");
16540         break;
16541
16542       case IcsPlayingWhite:
16543       case IcsPlayingBlack:
16544       case IcsObserving:
16545       case IcsExamining:
16546         break;
16547
16548       case PlayFromGameFile:
16549         gameInfo.event = StrSave("Game from non-PGN file");
16550         gameInfo.site = StrSave(HostName());
16551         gameInfo.date = PGNDate();
16552         gameInfo.round = StrSave("-");
16553         gameInfo.white = StrSave("?");
16554         gameInfo.black = StrSave("?");
16555         break;
16556
16557       default:
16558         break;
16559     }
16560 }
16561
16562 void
16563 ReplaceComment (int index, char *text)
16564 {
16565     int len;
16566     char *p;
16567     float score;
16568
16569     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16570        pvInfoList[index-1].depth == len &&
16571        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16572        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16573     while (*text == '\n') text++;
16574     len = strlen(text);
16575     while (len > 0 && text[len - 1] == '\n') len--;
16576
16577     if (commentList[index] != NULL)
16578       free(commentList[index]);
16579
16580     if (len == 0) {
16581         commentList[index] = NULL;
16582         return;
16583     }
16584   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16585       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16586       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16587     commentList[index] = (char *) malloc(len + 2);
16588     strncpy(commentList[index], text, len);
16589     commentList[index][len] = '\n';
16590     commentList[index][len + 1] = NULLCHAR;
16591   } else {
16592     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16593     char *p;
16594     commentList[index] = (char *) malloc(len + 7);
16595     safeStrCpy(commentList[index], "{\n", 3);
16596     safeStrCpy(commentList[index]+2, text, len+1);
16597     commentList[index][len+2] = NULLCHAR;
16598     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16599     strcat(commentList[index], "\n}\n");
16600   }
16601 }
16602
16603 void
16604 CrushCRs (char *text)
16605 {
16606   char *p = text;
16607   char *q = text;
16608   char ch;
16609
16610   do {
16611     ch = *p++;
16612     if (ch == '\r') continue;
16613     *q++ = ch;
16614   } while (ch != '\0');
16615 }
16616
16617 void
16618 AppendComment (int index, char *text, Boolean addBraces)
16619 /* addBraces  tells if we should add {} */
16620 {
16621     int oldlen, len;
16622     char *old;
16623
16624 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16625     if(addBraces == 3) addBraces = 0; else // force appending literally
16626     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16627
16628     CrushCRs(text);
16629     while (*text == '\n') text++;
16630     len = strlen(text);
16631     while (len > 0 && text[len - 1] == '\n') len--;
16632     text[len] = NULLCHAR;
16633
16634     if (len == 0) return;
16635
16636     if (commentList[index] != NULL) {
16637       Boolean addClosingBrace = addBraces;
16638         old = commentList[index];
16639         oldlen = strlen(old);
16640         while(commentList[index][oldlen-1] ==  '\n')
16641           commentList[index][--oldlen] = NULLCHAR;
16642         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16643         safeStrCpy(commentList[index], old, oldlen + len + 6);
16644         free(old);
16645         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16646         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16647           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16648           while (*text == '\n') { text++; len--; }
16649           commentList[index][--oldlen] = NULLCHAR;
16650       }
16651         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16652         else          strcat(commentList[index], "\n");
16653         strcat(commentList[index], text);
16654         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16655         else          strcat(commentList[index], "\n");
16656     } else {
16657         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16658         if(addBraces)
16659           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16660         else commentList[index][0] = NULLCHAR;
16661         strcat(commentList[index], text);
16662         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16663         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16664     }
16665 }
16666
16667 static char *
16668 FindStr (char * text, char * sub_text)
16669 {
16670     char * result = strstr( text, sub_text );
16671
16672     if( result != NULL ) {
16673         result += strlen( sub_text );
16674     }
16675
16676     return result;
16677 }
16678
16679 /* [AS] Try to extract PV info from PGN comment */
16680 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16681 char *
16682 GetInfoFromComment (int index, char * text)
16683 {
16684     char * sep = text, *p;
16685
16686     if( text != NULL && index > 0 ) {
16687         int score = 0;
16688         int depth = 0;
16689         int time = -1, sec = 0, deci;
16690         char * s_eval = FindStr( text, "[%eval " );
16691         char * s_emt = FindStr( text, "[%emt " );
16692 #if 0
16693         if( s_eval != NULL || s_emt != NULL ) {
16694 #else
16695         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16696 #endif
16697             /* New style */
16698             char delim;
16699
16700             if( s_eval != NULL ) {
16701                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16702                     return text;
16703                 }
16704
16705                 if( delim != ']' ) {
16706                     return text;
16707                 }
16708             }
16709
16710             if( s_emt != NULL ) {
16711             }
16712                 return text;
16713         }
16714         else {
16715             /* We expect something like: [+|-]nnn.nn/dd */
16716             int score_lo = 0;
16717
16718             if(*text != '{') return text; // [HGM] braces: must be normal comment
16719
16720             sep = strchr( text, '/' );
16721             if( sep == NULL || sep < (text+4) ) {
16722                 return text;
16723             }
16724
16725             p = text;
16726             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16727             if(p[1] == '(') { // comment starts with PV
16728                p = strchr(p, ')'); // locate end of PV
16729                if(p == NULL || sep < p+5) return text;
16730                // at this point we have something like "{(.*) +0.23/6 ..."
16731                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16732                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16733                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16734             }
16735             time = -1; sec = -1; deci = -1;
16736             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16737                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16738                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16739                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16740                 return text;
16741             }
16742
16743             if( score_lo < 0 || score_lo >= 100 ) {
16744                 return text;
16745             }
16746
16747             if(sec >= 0) time = 600*time + 10*sec; else
16748             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16749
16750             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16751
16752             /* [HGM] PV time: now locate end of PV info */
16753             while( *++sep >= '0' && *sep <= '9'); // strip depth
16754             if(time >= 0)
16755             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16756             if(sec >= 0)
16757             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16758             if(deci >= 0)
16759             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16760             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16761         }
16762
16763         if( depth <= 0 ) {
16764             return text;
16765         }
16766
16767         if( time < 0 ) {
16768             time = -1;
16769         }
16770
16771         pvInfoList[index-1].depth = depth;
16772         pvInfoList[index-1].score = score;
16773         pvInfoList[index-1].time  = 10*time; // centi-sec
16774         if(*sep == '}') *sep = 0; else *--sep = '{';
16775         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16776     }
16777     return sep;
16778 }
16779
16780 void
16781 SendToProgram (char *message, ChessProgramState *cps)
16782 {
16783     int count, outCount, error;
16784     char buf[MSG_SIZ];
16785
16786     if (cps->pr == NoProc) return;
16787     Attention(cps);
16788
16789     if (appData.debugMode) {
16790         TimeMark now;
16791         GetTimeMark(&now);
16792         fprintf(debugFP, "%ld >%-6s: %s",
16793                 SubtractTimeMarks(&now, &programStartTime),
16794                 cps->which, message);
16795         if(serverFP)
16796             fprintf(serverFP, "%ld >%-6s: %s",
16797                 SubtractTimeMarks(&now, &programStartTime),
16798                 cps->which, message), fflush(serverFP);
16799     }
16800
16801     count = strlen(message);
16802     outCount = OutputToProcess(cps->pr, message, count, &error);
16803     if (outCount < count && !exiting
16804                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16805       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16806       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16807         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16808             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16809                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16810                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16811                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16812             } else {
16813                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16814                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16815                 gameInfo.result = res;
16816             }
16817             gameInfo.resultDetails = StrSave(buf);
16818         }
16819         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16820         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16821     }
16822 }
16823
16824 void
16825 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16826 {
16827     char *end_str;
16828     char buf[MSG_SIZ];
16829     ChessProgramState *cps = (ChessProgramState *)closure;
16830
16831     if (isr != cps->isr) return; /* Killed intentionally */
16832     if (count <= 0) {
16833         if (count == 0) {
16834             RemoveInputSource(cps->isr);
16835             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16836                     _(cps->which), cps->program);
16837             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16838             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16839                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16840                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16841                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16842                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16843                 } else {
16844                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16845                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16846                     gameInfo.result = res;
16847                 }
16848                 gameInfo.resultDetails = StrSave(buf);
16849             }
16850             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16851             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16852         } else {
16853             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16854                     _(cps->which), cps->program);
16855             RemoveInputSource(cps->isr);
16856
16857             /* [AS] Program is misbehaving badly... kill it */
16858             if( count == -2 ) {
16859                 DestroyChildProcess( cps->pr, 9 );
16860                 cps->pr = NoProc;
16861             }
16862
16863             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16864         }
16865         return;
16866     }
16867
16868     if ((end_str = strchr(message, '\r')) != NULL)
16869       *end_str = NULLCHAR;
16870     if ((end_str = strchr(message, '\n')) != NULL)
16871       *end_str = NULLCHAR;
16872
16873     if (appData.debugMode) {
16874         TimeMark now; int print = 1;
16875         char *quote = ""; char c; int i;
16876
16877         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16878                 char start = message[0];
16879                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16880                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16881                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16882                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16883                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16884                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16885                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16886                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16887                    sscanf(message, "hint: %c", &c)!=1 &&
16888                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16889                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16890                     print = (appData.engineComments >= 2);
16891                 }
16892                 message[0] = start; // restore original message
16893         }
16894         if(print) {
16895                 GetTimeMark(&now);
16896                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16897                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16898                         quote,
16899                         message);
16900                 if(serverFP)
16901                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16902                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16903                         quote,
16904                         message), fflush(serverFP);
16905         }
16906     }
16907
16908     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16909     if (appData.icsEngineAnalyze) {
16910         if (strstr(message, "whisper") != NULL ||
16911              strstr(message, "kibitz") != NULL ||
16912             strstr(message, "tellics") != NULL) return;
16913     }
16914
16915     HandleMachineMove(message, cps);
16916 }
16917
16918
16919 void
16920 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16921 {
16922     char buf[MSG_SIZ];
16923     int seconds;
16924
16925     if( timeControl_2 > 0 ) {
16926         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16927             tc = timeControl_2;
16928         }
16929     }
16930     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16931     inc /= cps->timeOdds;
16932     st  /= cps->timeOdds;
16933
16934     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16935
16936     if (st > 0) {
16937       /* Set exact time per move, normally using st command */
16938       if (cps->stKludge) {
16939         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16940         seconds = st % 60;
16941         if (seconds == 0) {
16942           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16943         } else {
16944           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16945         }
16946       } else {
16947         snprintf(buf, MSG_SIZ, "st %d\n", st);
16948       }
16949     } else {
16950       /* Set conventional or incremental time control, using level command */
16951       if (seconds == 0) {
16952         /* Note old gnuchess bug -- minutes:seconds used to not work.
16953            Fixed in later versions, but still avoid :seconds
16954            when seconds is 0. */
16955         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16956       } else {
16957         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16958                  seconds, inc/1000.);
16959       }
16960     }
16961     SendToProgram(buf, cps);
16962
16963     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16964     /* Orthogonally, limit search to given depth */
16965     if (sd > 0) {
16966       if (cps->sdKludge) {
16967         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16968       } else {
16969         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16970       }
16971       SendToProgram(buf, cps);
16972     }
16973
16974     if(cps->nps >= 0) { /* [HGM] nps */
16975         if(cps->supportsNPS == FALSE)
16976           cps->nps = -1; // don't use if engine explicitly says not supported!
16977         else {
16978           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16979           SendToProgram(buf, cps);
16980         }
16981     }
16982 }
16983
16984 ChessProgramState *
16985 WhitePlayer ()
16986 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16987 {
16988     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16989        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16990         return &second;
16991     return &first;
16992 }
16993
16994 void
16995 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16996 {
16997     char message[MSG_SIZ];
16998     long time, otime;
16999
17000     /* Note: this routine must be called when the clocks are stopped
17001        or when they have *just* been set or switched; otherwise
17002        it will be off by the time since the current tick started.
17003     */
17004     if (machineWhite) {
17005         time = whiteTimeRemaining / 10;
17006         otime = blackTimeRemaining / 10;
17007     } else {
17008         time = blackTimeRemaining / 10;
17009         otime = whiteTimeRemaining / 10;
17010     }
17011     /* [HGM] translate opponent's time by time-odds factor */
17012     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
17013
17014     if (time <= 0) time = 1;
17015     if (otime <= 0) otime = 1;
17016
17017     snprintf(message, MSG_SIZ, "time %ld\n", time);
17018     SendToProgram(message, cps);
17019
17020     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
17021     SendToProgram(message, cps);
17022 }
17023
17024 char *
17025 EngineDefinedVariant (ChessProgramState *cps, int n)
17026 {   // return name of n-th unknown variant that engine supports
17027     static char buf[MSG_SIZ];
17028     char *p, *s = cps->variants;
17029     if(!s) return NULL;
17030     do { // parse string from variants feature
17031       VariantClass v;
17032         p = strchr(s, ',');
17033         if(p) *p = NULLCHAR;
17034       v = StringToVariant(s);
17035       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
17036         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
17037             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
17038                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
17039                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
17040                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
17041             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
17042         }
17043         if(p) *p++ = ',';
17044         if(n < 0) return buf;
17045     } while(s = p);
17046     return NULL;
17047 }
17048
17049 int
17050 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17051 {
17052   char buf[MSG_SIZ];
17053   int len = strlen(name);
17054   int val;
17055
17056   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17057     (*p) += len + 1;
17058     sscanf(*p, "%d", &val);
17059     *loc = (val != 0);
17060     while (**p && **p != ' ')
17061       (*p)++;
17062     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17063     SendToProgram(buf, cps);
17064     return TRUE;
17065   }
17066   return FALSE;
17067 }
17068
17069 int
17070 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
17071 {
17072   char buf[MSG_SIZ];
17073   int len = strlen(name);
17074   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
17075     (*p) += len + 1;
17076     sscanf(*p, "%d", loc);
17077     while (**p && **p != ' ') (*p)++;
17078     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17079     SendToProgram(buf, cps);
17080     return TRUE;
17081   }
17082   return FALSE;
17083 }
17084
17085 int
17086 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17087 {
17088   char buf[MSG_SIZ];
17089   int len = strlen(name);
17090   if (strncmp((*p), name, len) == 0
17091       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17092     (*p) += len + 2;
17093     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17094     sscanf(*p, "%[^\"]", *loc);
17095     while (**p && **p != '\"') (*p)++;
17096     if (**p == '\"') (*p)++;
17097     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17098     SendToProgram(buf, cps);
17099     return TRUE;
17100   }
17101   return FALSE;
17102 }
17103
17104 int
17105 ParseOption (Option *opt, ChessProgramState *cps)
17106 // [HGM] options: process the string that defines an engine option, and determine
17107 // name, type, default value, and allowed value range
17108 {
17109         char *p, *q, buf[MSG_SIZ];
17110         int n, min = (-1)<<31, max = 1<<31, def;
17111
17112         opt->target = &opt->value;   // OK for spin/slider and checkbox
17113         if(p = strstr(opt->name, " -spin ")) {
17114             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17115             if(max < min) max = min; // enforce consistency
17116             if(def < min) def = min;
17117             if(def > max) def = max;
17118             opt->value = def;
17119             opt->min = min;
17120             opt->max = max;
17121             opt->type = Spin;
17122         } else if((p = strstr(opt->name, " -slider "))) {
17123             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17124             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17125             if(max < min) max = min; // enforce consistency
17126             if(def < min) def = min;
17127             if(def > max) def = max;
17128             opt->value = def;
17129             opt->min = min;
17130             opt->max = max;
17131             opt->type = Spin; // Slider;
17132         } else if((p = strstr(opt->name, " -string "))) {
17133             opt->textValue = p+9;
17134             opt->type = TextBox;
17135             opt->target = &opt->textValue;
17136         } else if((p = strstr(opt->name, " -file "))) {
17137             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17138             opt->target = opt->textValue = p+7;
17139             opt->type = FileName; // FileName;
17140             opt->target = &opt->textValue;
17141         } else if((p = strstr(opt->name, " -path "))) {
17142             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17143             opt->target = opt->textValue = p+7;
17144             opt->type = PathName; // PathName;
17145             opt->target = &opt->textValue;
17146         } else if(p = strstr(opt->name, " -check ")) {
17147             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17148             opt->value = (def != 0);
17149             opt->type = CheckBox;
17150         } else if(p = strstr(opt->name, " -combo ")) {
17151             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17152             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17153             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17154             opt->value = n = 0;
17155             while(q = StrStr(q, " /// ")) {
17156                 n++; *q = 0;    // count choices, and null-terminate each of them
17157                 q += 5;
17158                 if(*q == '*') { // remember default, which is marked with * prefix
17159                     q++;
17160                     opt->value = n;
17161                 }
17162                 cps->comboList[cps->comboCnt++] = q;
17163             }
17164             cps->comboList[cps->comboCnt++] = NULL;
17165             opt->max = n + 1;
17166             opt->type = ComboBox;
17167         } else if(p = strstr(opt->name, " -button")) {
17168             opt->type = Button;
17169         } else if(p = strstr(opt->name, " -save")) {
17170             opt->type = SaveButton;
17171         } else return FALSE;
17172         *p = 0; // terminate option name
17173         // now look if the command-line options define a setting for this engine option.
17174         if(cps->optionSettings && cps->optionSettings[0])
17175             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17176         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17177           snprintf(buf, MSG_SIZ, "option %s", p);
17178                 if(p = strstr(buf, ",")) *p = 0;
17179                 if(q = strchr(buf, '=')) switch(opt->type) {
17180                     case ComboBox:
17181                         for(n=0; n<opt->max; n++)
17182                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17183                         break;
17184                     case TextBox:
17185                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17186                         break;
17187                     case Spin:
17188                     case CheckBox:
17189                         opt->value = atoi(q+1);
17190                     default:
17191                         break;
17192                 }
17193                 strcat(buf, "\n");
17194                 SendToProgram(buf, cps);
17195         }
17196         return TRUE;
17197 }
17198
17199 void
17200 FeatureDone (ChessProgramState *cps, int val)
17201 {
17202   DelayedEventCallback cb = GetDelayedEvent();
17203   if ((cb == InitBackEnd3 && cps == &first) ||
17204       (cb == SettingsMenuIfReady && cps == &second) ||
17205       (cb == LoadEngine) ||
17206       (cb == TwoMachinesEventIfReady)) {
17207     CancelDelayedEvent();
17208     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17209   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17210   cps->initDone = val;
17211   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17212 }
17213
17214 /* Parse feature command from engine */
17215 void
17216 ParseFeatures (char *args, ChessProgramState *cps)
17217 {
17218   char *p = args;
17219   char *q = NULL;
17220   int val;
17221   char buf[MSG_SIZ];
17222
17223   for (;;) {
17224     while (*p == ' ') p++;
17225     if (*p == NULLCHAR) return;
17226
17227     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17228     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17229     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17230     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17231     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17232     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17233     if (BoolFeature(&p, "reuse", &val, cps)) {
17234       /* Engine can disable reuse, but can't enable it if user said no */
17235       if (!val) cps->reuse = FALSE;
17236       continue;
17237     }
17238     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17239     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17240       if (gameMode == TwoMachinesPlay) {
17241         DisplayTwoMachinesTitle();
17242       } else {
17243         DisplayTitle("");
17244       }
17245       continue;
17246     }
17247     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17248     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17249     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17250     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17251     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17252     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17253     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17254     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17255     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17256     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17257     if (IntFeature(&p, "done", &val, cps)) {
17258       FeatureDone(cps, val);
17259       continue;
17260     }
17261     /* Added by Tord: */
17262     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17263     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17264     /* End of additions by Tord */
17265
17266     /* [HGM] added features: */
17267     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17268     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17269     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17270     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17271     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17272     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17273     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17274     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17275         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17276         FREE(cps->option[cps->nrOptions].name);
17277         cps->option[cps->nrOptions].name = q; q = NULL;
17278         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17279           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17280             SendToProgram(buf, cps);
17281             continue;
17282         }
17283         if(cps->nrOptions >= MAX_OPTIONS) {
17284             cps->nrOptions--;
17285             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17286             DisplayError(buf, 0);
17287         }
17288         continue;
17289     }
17290     /* End of additions by HGM */
17291
17292     /* unknown feature: complain and skip */
17293     q = p;
17294     while (*q && *q != '=') q++;
17295     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17296     SendToProgram(buf, cps);
17297     p = q;
17298     if (*p == '=') {
17299       p++;
17300       if (*p == '\"') {
17301         p++;
17302         while (*p && *p != '\"') p++;
17303         if (*p == '\"') p++;
17304       } else {
17305         while (*p && *p != ' ') p++;
17306       }
17307     }
17308   }
17309
17310 }
17311
17312 void
17313 PeriodicUpdatesEvent (int newState)
17314 {
17315     if (newState == appData.periodicUpdates)
17316       return;
17317
17318     appData.periodicUpdates=newState;
17319
17320     /* Display type changes, so update it now */
17321 //    DisplayAnalysis();
17322
17323     /* Get the ball rolling again... */
17324     if (newState) {
17325         AnalysisPeriodicEvent(1);
17326         StartAnalysisClock();
17327     }
17328 }
17329
17330 void
17331 PonderNextMoveEvent (int newState)
17332 {
17333     if (newState == appData.ponderNextMove) return;
17334     if (gameMode == EditPosition) EditPositionDone(TRUE);
17335     if (newState) {
17336         SendToProgram("hard\n", &first);
17337         if (gameMode == TwoMachinesPlay) {
17338             SendToProgram("hard\n", &second);
17339         }
17340     } else {
17341         SendToProgram("easy\n", &first);
17342         thinkOutput[0] = NULLCHAR;
17343         if (gameMode == TwoMachinesPlay) {
17344             SendToProgram("easy\n", &second);
17345         }
17346     }
17347     appData.ponderNextMove = newState;
17348 }
17349
17350 void
17351 NewSettingEvent (int option, int *feature, char *command, int value)
17352 {
17353     char buf[MSG_SIZ];
17354
17355     if (gameMode == EditPosition) EditPositionDone(TRUE);
17356     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17357     if(feature == NULL || *feature) SendToProgram(buf, &first);
17358     if (gameMode == TwoMachinesPlay) {
17359         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17360     }
17361 }
17362
17363 void
17364 ShowThinkingEvent ()
17365 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17366 {
17367     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17368     int newState = appData.showThinking
17369         // [HGM] thinking: other features now need thinking output as well
17370         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17371
17372     if (oldState == newState) return;
17373     oldState = newState;
17374     if (gameMode == EditPosition) EditPositionDone(TRUE);
17375     if (oldState) {
17376         SendToProgram("post\n", &first);
17377         if (gameMode == TwoMachinesPlay) {
17378             SendToProgram("post\n", &second);
17379         }
17380     } else {
17381         SendToProgram("nopost\n", &first);
17382         thinkOutput[0] = NULLCHAR;
17383         if (gameMode == TwoMachinesPlay) {
17384             SendToProgram("nopost\n", &second);
17385         }
17386     }
17387 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17388 }
17389
17390 void
17391 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17392 {
17393   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17394   if (pr == NoProc) return;
17395   AskQuestion(title, question, replyPrefix, pr);
17396 }
17397
17398 void
17399 TypeInEvent (char firstChar)
17400 {
17401     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17402         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17403         gameMode == AnalyzeMode || gameMode == EditGame ||
17404         gameMode == EditPosition || gameMode == IcsExamining ||
17405         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17406         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17407                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17408                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17409         gameMode == Training) PopUpMoveDialog(firstChar);
17410 }
17411
17412 void
17413 TypeInDoneEvent (char *move)
17414 {
17415         Board board;
17416         int n, fromX, fromY, toX, toY;
17417         char promoChar;
17418         ChessMove moveType;
17419
17420         // [HGM] FENedit
17421         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17422                 EditPositionPasteFEN(move);
17423                 return;
17424         }
17425         // [HGM] movenum: allow move number to be typed in any mode
17426         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17427           ToNrEvent(2*n-1);
17428           return;
17429         }
17430         // undocumented kludge: allow command-line option to be typed in!
17431         // (potentially fatal, and does not implement the effect of the option.)
17432         // should only be used for options that are values on which future decisions will be made,
17433         // and definitely not on options that would be used during initialization.
17434         if(strstr(move, "!!! -") == move) {
17435             ParseArgsFromString(move+4);
17436             return;
17437         }
17438
17439       if (gameMode != EditGame && currentMove != forwardMostMove &&
17440         gameMode != Training) {
17441         DisplayMoveError(_("Displayed move is not current"));
17442       } else {
17443         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17444           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17445         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17446         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17447           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17448           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17449         } else {
17450           DisplayMoveError(_("Could not parse move"));
17451         }
17452       }
17453 }
17454
17455 void
17456 DisplayMove (int moveNumber)
17457 {
17458     char message[MSG_SIZ];
17459     char res[MSG_SIZ];
17460     char cpThinkOutput[MSG_SIZ];
17461
17462     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17463
17464     if (moveNumber == forwardMostMove - 1 ||
17465         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17466
17467         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17468
17469         if (strchr(cpThinkOutput, '\n')) {
17470             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17471         }
17472     } else {
17473         *cpThinkOutput = NULLCHAR;
17474     }
17475
17476     /* [AS] Hide thinking from human user */
17477     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17478         *cpThinkOutput = NULLCHAR;
17479         if( thinkOutput[0] != NULLCHAR ) {
17480             int i;
17481
17482             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17483                 cpThinkOutput[i] = '.';
17484             }
17485             cpThinkOutput[i] = NULLCHAR;
17486             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17487         }
17488     }
17489
17490     if (moveNumber == forwardMostMove - 1 &&
17491         gameInfo.resultDetails != NULL) {
17492         if (gameInfo.resultDetails[0] == NULLCHAR) {
17493           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17494         } else {
17495           snprintf(res, MSG_SIZ, " {%s} %s",
17496                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17497         }
17498     } else {
17499         res[0] = NULLCHAR;
17500     }
17501
17502     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17503         DisplayMessage(res, cpThinkOutput);
17504     } else {
17505       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17506                 WhiteOnMove(moveNumber) ? " " : ".. ",
17507                 parseList[moveNumber], res);
17508         DisplayMessage(message, cpThinkOutput);
17509     }
17510 }
17511
17512 void
17513 DisplayComment (int moveNumber, char *text)
17514 {
17515     char title[MSG_SIZ];
17516
17517     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17518       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17519     } else {
17520       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17521               WhiteOnMove(moveNumber) ? " " : ".. ",
17522               parseList[moveNumber]);
17523     }
17524     if (text != NULL && (appData.autoDisplayComment || commentUp))
17525         CommentPopUp(title, text);
17526 }
17527
17528 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17529  * might be busy thinking or pondering.  It can be omitted if your
17530  * gnuchess is configured to stop thinking immediately on any user
17531  * input.  However, that gnuchess feature depends on the FIONREAD
17532  * ioctl, which does not work properly on some flavors of Unix.
17533  */
17534 void
17535 Attention (ChessProgramState *cps)
17536 {
17537 #if ATTENTION
17538     if (!cps->useSigint) return;
17539     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17540     switch (gameMode) {
17541       case MachinePlaysWhite:
17542       case MachinePlaysBlack:
17543       case TwoMachinesPlay:
17544       case IcsPlayingWhite:
17545       case IcsPlayingBlack:
17546       case AnalyzeMode:
17547       case AnalyzeFile:
17548         /* Skip if we know it isn't thinking */
17549         if (!cps->maybeThinking) return;
17550         if (appData.debugMode)
17551           fprintf(debugFP, "Interrupting %s\n", cps->which);
17552         InterruptChildProcess(cps->pr);
17553         cps->maybeThinking = FALSE;
17554         break;
17555       default:
17556         break;
17557     }
17558 #endif /*ATTENTION*/
17559 }
17560
17561 int
17562 CheckFlags ()
17563 {
17564     if (whiteTimeRemaining <= 0) {
17565         if (!whiteFlag) {
17566             whiteFlag = TRUE;
17567             if (appData.icsActive) {
17568                 if (appData.autoCallFlag &&
17569                     gameMode == IcsPlayingBlack && !blackFlag) {
17570                   SendToICS(ics_prefix);
17571                   SendToICS("flag\n");
17572                 }
17573             } else {
17574                 if (blackFlag) {
17575                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17576                 } else {
17577                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17578                     if (appData.autoCallFlag) {
17579                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17580                         return TRUE;
17581                     }
17582                 }
17583             }
17584         }
17585     }
17586     if (blackTimeRemaining <= 0) {
17587         if (!blackFlag) {
17588             blackFlag = TRUE;
17589             if (appData.icsActive) {
17590                 if (appData.autoCallFlag &&
17591                     gameMode == IcsPlayingWhite && !whiteFlag) {
17592                   SendToICS(ics_prefix);
17593                   SendToICS("flag\n");
17594                 }
17595             } else {
17596                 if (whiteFlag) {
17597                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17598                 } else {
17599                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17600                     if (appData.autoCallFlag) {
17601                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17602                         return TRUE;
17603                     }
17604                 }
17605             }
17606         }
17607     }
17608     return FALSE;
17609 }
17610
17611 void
17612 CheckTimeControl ()
17613 {
17614     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17615         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17616
17617     /*
17618      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17619      */
17620     if ( !WhiteOnMove(forwardMostMove) ) {
17621         /* White made time control */
17622         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17623         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17624         /* [HGM] time odds: correct new time quota for time odds! */
17625                                             / WhitePlayer()->timeOdds;
17626         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17627     } else {
17628         lastBlack -= blackTimeRemaining;
17629         /* Black made time control */
17630         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17631                                             / WhitePlayer()->other->timeOdds;
17632         lastWhite = whiteTimeRemaining;
17633     }
17634 }
17635
17636 void
17637 DisplayBothClocks ()
17638 {
17639     int wom = gameMode == EditPosition ?
17640       !blackPlaysFirst : WhiteOnMove(currentMove);
17641     DisplayWhiteClock(whiteTimeRemaining, wom);
17642     DisplayBlackClock(blackTimeRemaining, !wom);
17643 }
17644
17645
17646 /* Timekeeping seems to be a portability nightmare.  I think everyone
17647    has ftime(), but I'm really not sure, so I'm including some ifdefs
17648    to use other calls if you don't.  Clocks will be less accurate if
17649    you have neither ftime nor gettimeofday.
17650 */
17651
17652 /* VS 2008 requires the #include outside of the function */
17653 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17654 #include <sys/timeb.h>
17655 #endif
17656
17657 /* Get the current time as a TimeMark */
17658 void
17659 GetTimeMark (TimeMark *tm)
17660 {
17661 #if HAVE_GETTIMEOFDAY
17662
17663     struct timeval timeVal;
17664     struct timezone timeZone;
17665
17666     gettimeofday(&timeVal, &timeZone);
17667     tm->sec = (long) timeVal.tv_sec;
17668     tm->ms = (int) (timeVal.tv_usec / 1000L);
17669
17670 #else /*!HAVE_GETTIMEOFDAY*/
17671 #if HAVE_FTIME
17672
17673 // include <sys/timeb.h> / moved to just above start of function
17674     struct timeb timeB;
17675
17676     ftime(&timeB);
17677     tm->sec = (long) timeB.time;
17678     tm->ms = (int) timeB.millitm;
17679
17680 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17681     tm->sec = (long) time(NULL);
17682     tm->ms = 0;
17683 #endif
17684 #endif
17685 }
17686
17687 /* Return the difference in milliseconds between two
17688    time marks.  We assume the difference will fit in a long!
17689 */
17690 long
17691 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17692 {
17693     return 1000L*(tm2->sec - tm1->sec) +
17694            (long) (tm2->ms - tm1->ms);
17695 }
17696
17697
17698 /*
17699  * Code to manage the game clocks.
17700  *
17701  * In tournament play, black starts the clock and then white makes a move.
17702  * We give the human user a slight advantage if he is playing white---the
17703  * clocks don't run until he makes his first move, so it takes zero time.
17704  * Also, we don't account for network lag, so we could get out of sync
17705  * with GNU Chess's clock -- but then, referees are always right.
17706  */
17707
17708 static TimeMark tickStartTM;
17709 static long intendedTickLength;
17710
17711 long
17712 NextTickLength (long timeRemaining)
17713 {
17714     long nominalTickLength, nextTickLength;
17715
17716     if (timeRemaining > 0L && timeRemaining <= 10000L)
17717       nominalTickLength = 100L;
17718     else
17719       nominalTickLength = 1000L;
17720     nextTickLength = timeRemaining % nominalTickLength;
17721     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17722
17723     return nextTickLength;
17724 }
17725
17726 /* Adjust clock one minute up or down */
17727 void
17728 AdjustClock (Boolean which, int dir)
17729 {
17730     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17731     if(which) blackTimeRemaining += 60000*dir;
17732     else      whiteTimeRemaining += 60000*dir;
17733     DisplayBothClocks();
17734     adjustedClock = TRUE;
17735 }
17736
17737 /* Stop clocks and reset to a fresh time control */
17738 void
17739 ResetClocks ()
17740 {
17741     (void) StopClockTimer();
17742     if (appData.icsActive) {
17743         whiteTimeRemaining = blackTimeRemaining = 0;
17744     } else if (searchTime) {
17745         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17746         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17747     } else { /* [HGM] correct new time quote for time odds */
17748         whiteTC = blackTC = fullTimeControlString;
17749         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17750         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17751     }
17752     if (whiteFlag || blackFlag) {
17753         DisplayTitle("");
17754         whiteFlag = blackFlag = FALSE;
17755     }
17756     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17757     DisplayBothClocks();
17758     adjustedClock = FALSE;
17759 }
17760
17761 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17762
17763 /* Decrement running clock by amount of time that has passed */
17764 void
17765 DecrementClocks ()
17766 {
17767     long timeRemaining;
17768     long lastTickLength, fudge;
17769     TimeMark now;
17770
17771     if (!appData.clockMode) return;
17772     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17773
17774     GetTimeMark(&now);
17775
17776     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17777
17778     /* Fudge if we woke up a little too soon */
17779     fudge = intendedTickLength - lastTickLength;
17780     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17781
17782     if (WhiteOnMove(forwardMostMove)) {
17783         if(whiteNPS >= 0) lastTickLength = 0;
17784         timeRemaining = whiteTimeRemaining -= lastTickLength;
17785         if(timeRemaining < 0 && !appData.icsActive) {
17786             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17787             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17788                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17789                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17790             }
17791         }
17792         DisplayWhiteClock(whiteTimeRemaining - fudge,
17793                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17794     } else {
17795         if(blackNPS >= 0) lastTickLength = 0;
17796         timeRemaining = blackTimeRemaining -= lastTickLength;
17797         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17798             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17799             if(suddenDeath) {
17800                 blackStartMove = forwardMostMove;
17801                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17802             }
17803         }
17804         DisplayBlackClock(blackTimeRemaining - fudge,
17805                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17806     }
17807     if (CheckFlags()) return;
17808
17809     if(twoBoards) { // count down secondary board's clocks as well
17810         activePartnerTime -= lastTickLength;
17811         partnerUp = 1;
17812         if(activePartner == 'W')
17813             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17814         else
17815             DisplayBlackClock(activePartnerTime, TRUE);
17816         partnerUp = 0;
17817     }
17818
17819     tickStartTM = now;
17820     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17821     StartClockTimer(intendedTickLength);
17822
17823     /* if the time remaining has fallen below the alarm threshold, sound the
17824      * alarm. if the alarm has sounded and (due to a takeback or time control
17825      * with increment) the time remaining has increased to a level above the
17826      * threshold, reset the alarm so it can sound again.
17827      */
17828
17829     if (appData.icsActive && appData.icsAlarm) {
17830
17831         /* make sure we are dealing with the user's clock */
17832         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17833                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17834            )) return;
17835
17836         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17837             alarmSounded = FALSE;
17838         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17839             PlayAlarmSound();
17840             alarmSounded = TRUE;
17841         }
17842     }
17843 }
17844
17845
17846 /* A player has just moved, so stop the previously running
17847    clock and (if in clock mode) start the other one.
17848    We redisplay both clocks in case we're in ICS mode, because
17849    ICS gives us an update to both clocks after every move.
17850    Note that this routine is called *after* forwardMostMove
17851    is updated, so the last fractional tick must be subtracted
17852    from the color that is *not* on move now.
17853 */
17854 void
17855 SwitchClocks (int newMoveNr)
17856 {
17857     long lastTickLength;
17858     TimeMark now;
17859     int flagged = FALSE;
17860
17861     GetTimeMark(&now);
17862
17863     if (StopClockTimer() && appData.clockMode) {
17864         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17865         if (!WhiteOnMove(forwardMostMove)) {
17866             if(blackNPS >= 0) lastTickLength = 0;
17867             blackTimeRemaining -= lastTickLength;
17868            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17869 //         if(pvInfoList[forwardMostMove].time == -1)
17870                  pvInfoList[forwardMostMove].time =               // use GUI time
17871                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17872         } else {
17873            if(whiteNPS >= 0) lastTickLength = 0;
17874            whiteTimeRemaining -= lastTickLength;
17875            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17876 //         if(pvInfoList[forwardMostMove].time == -1)
17877                  pvInfoList[forwardMostMove].time =
17878                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17879         }
17880         flagged = CheckFlags();
17881     }
17882     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17883     CheckTimeControl();
17884
17885     if (flagged || !appData.clockMode) return;
17886
17887     switch (gameMode) {
17888       case MachinePlaysBlack:
17889       case MachinePlaysWhite:
17890       case BeginningOfGame:
17891         if (pausing) return;
17892         break;
17893
17894       case EditGame:
17895       case PlayFromGameFile:
17896       case IcsExamining:
17897         return;
17898
17899       default:
17900         break;
17901     }
17902
17903     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17904         if(WhiteOnMove(forwardMostMove))
17905              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17906         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17907     }
17908
17909     tickStartTM = now;
17910     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17911       whiteTimeRemaining : blackTimeRemaining);
17912     StartClockTimer(intendedTickLength);
17913 }
17914
17915
17916 /* Stop both clocks */
17917 void
17918 StopClocks ()
17919 {
17920     long lastTickLength;
17921     TimeMark now;
17922
17923     if (!StopClockTimer()) return;
17924     if (!appData.clockMode) return;
17925
17926     GetTimeMark(&now);
17927
17928     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17929     if (WhiteOnMove(forwardMostMove)) {
17930         if(whiteNPS >= 0) lastTickLength = 0;
17931         whiteTimeRemaining -= lastTickLength;
17932         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17933     } else {
17934         if(blackNPS >= 0) lastTickLength = 0;
17935         blackTimeRemaining -= lastTickLength;
17936         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17937     }
17938     CheckFlags();
17939 }
17940
17941 /* Start clock of player on move.  Time may have been reset, so
17942    if clock is already running, stop and restart it. */
17943 void
17944 StartClocks ()
17945 {
17946     (void) StopClockTimer(); /* in case it was running already */
17947     DisplayBothClocks();
17948     if (CheckFlags()) return;
17949
17950     if (!appData.clockMode) return;
17951     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17952
17953     GetTimeMark(&tickStartTM);
17954     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17955       whiteTimeRemaining : blackTimeRemaining);
17956
17957    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17958     whiteNPS = blackNPS = -1;
17959     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17960        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17961         whiteNPS = first.nps;
17962     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17963        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17964         blackNPS = first.nps;
17965     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17966         whiteNPS = second.nps;
17967     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17968         blackNPS = second.nps;
17969     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17970
17971     StartClockTimer(intendedTickLength);
17972 }
17973
17974 char *
17975 TimeString (long ms)
17976 {
17977     long second, minute, hour, day;
17978     char *sign = "";
17979     static char buf[32];
17980
17981     if (ms > 0 && ms <= 9900) {
17982       /* convert milliseconds to tenths, rounding up */
17983       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17984
17985       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17986       return buf;
17987     }
17988
17989     /* convert milliseconds to seconds, rounding up */
17990     /* use floating point to avoid strangeness of integer division
17991        with negative dividends on many machines */
17992     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17993
17994     if (second < 0) {
17995         sign = "-";
17996         second = -second;
17997     }
17998
17999     day = second / (60 * 60 * 24);
18000     second = second % (60 * 60 * 24);
18001     hour = second / (60 * 60);
18002     second = second % (60 * 60);
18003     minute = second / 60;
18004     second = second % 60;
18005
18006     if (day > 0)
18007       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
18008               sign, day, hour, minute, second);
18009     else if (hour > 0)
18010       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
18011     else
18012       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
18013
18014     return buf;
18015 }
18016
18017
18018 /*
18019  * This is necessary because some C libraries aren't ANSI C compliant yet.
18020  */
18021 char *
18022 StrStr (char *string, char *match)
18023 {
18024     int i, length;
18025
18026     length = strlen(match);
18027
18028     for (i = strlen(string) - length; i >= 0; i--, string++)
18029       if (!strncmp(match, string, length))
18030         return string;
18031
18032     return NULL;
18033 }
18034
18035 char *
18036 StrCaseStr (char *string, char *match)
18037 {
18038     int i, j, length;
18039
18040     length = strlen(match);
18041
18042     for (i = strlen(string) - length; i >= 0; i--, string++) {
18043         for (j = 0; j < length; j++) {
18044             if (ToLower(match[j]) != ToLower(string[j]))
18045               break;
18046         }
18047         if (j == length) return string;
18048     }
18049
18050     return NULL;
18051 }
18052
18053 #ifndef _amigados
18054 int
18055 StrCaseCmp (char *s1, char *s2)
18056 {
18057     char c1, c2;
18058
18059     for (;;) {
18060         c1 = ToLower(*s1++);
18061         c2 = ToLower(*s2++);
18062         if (c1 > c2) return 1;
18063         if (c1 < c2) return -1;
18064         if (c1 == NULLCHAR) return 0;
18065     }
18066 }
18067
18068
18069 int
18070 ToLower (int c)
18071 {
18072     return isupper(c) ? tolower(c) : c;
18073 }
18074
18075
18076 int
18077 ToUpper (int c)
18078 {
18079     return islower(c) ? toupper(c) : c;
18080 }
18081 #endif /* !_amigados    */
18082
18083 char *
18084 StrSave (char *s)
18085 {
18086   char *ret;
18087
18088   if ((ret = (char *) malloc(strlen(s) + 1)))
18089     {
18090       safeStrCpy(ret, s, strlen(s)+1);
18091     }
18092   return ret;
18093 }
18094
18095 char *
18096 StrSavePtr (char *s, char **savePtr)
18097 {
18098     if (*savePtr) {
18099         free(*savePtr);
18100     }
18101     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18102       safeStrCpy(*savePtr, s, strlen(s)+1);
18103     }
18104     return(*savePtr);
18105 }
18106
18107 char *
18108 PGNDate ()
18109 {
18110     time_t clock;
18111     struct tm *tm;
18112     char buf[MSG_SIZ];
18113
18114     clock = time((time_t *)NULL);
18115     tm = localtime(&clock);
18116     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18117             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18118     return StrSave(buf);
18119 }
18120
18121
18122 char *
18123 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18124 {
18125     int i, j, fromX, fromY, toX, toY;
18126     int whiteToPlay, haveRights = nrCastlingRights;
18127     char buf[MSG_SIZ];
18128     char *p, *q;
18129     int emptycount;
18130     ChessSquare piece;
18131
18132     whiteToPlay = (gameMode == EditPosition) ?
18133       !blackPlaysFirst : (move % 2 == 0);
18134     p = buf;
18135
18136     /* Piece placement data */
18137     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18138         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18139         emptycount = 0;
18140         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18141             if (boards[move][i][j] == EmptySquare) {
18142                 emptycount++;
18143             } else { ChessSquare piece = boards[move][i][j];
18144                 if (emptycount > 0) {
18145                     if(emptycount<10) /* [HGM] can be >= 10 */
18146                         *p++ = '0' + emptycount;
18147                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18148                     emptycount = 0;
18149                 }
18150                 if(PieceToChar(piece) == '+') {
18151                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18152                     *p++ = '+';
18153                     piece = (ChessSquare)(CHUDEMOTED(piece));
18154                 }
18155                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18156                 if(*p = PieceSuffix(piece)) p++;
18157                 if(p[-1] == '~') {
18158                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18159                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18160                     *p++ = '~';
18161                 }
18162             }
18163         }
18164         if (emptycount > 0) {
18165             if(emptycount<10) /* [HGM] can be >= 10 */
18166                 *p++ = '0' + emptycount;
18167             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18168             emptycount = 0;
18169         }
18170         *p++ = '/';
18171     }
18172     *(p - 1) = ' ';
18173
18174     /* [HGM] print Crazyhouse or Shogi holdings */
18175     if( gameInfo.holdingsWidth ) {
18176         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18177         q = p;
18178         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18179             piece = boards[move][i][BOARD_WIDTH-1];
18180             if( piece != EmptySquare )
18181               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18182                   *p++ = PieceToChar(piece);
18183         }
18184         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18185             piece = boards[move][BOARD_HEIGHT-i-1][0];
18186             if( piece != EmptySquare )
18187               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18188                   *p++ = PieceToChar(piece);
18189         }
18190
18191         if( q == p ) *p++ = '-';
18192         *p++ = ']';
18193         *p++ = ' ';
18194     }
18195
18196     /* Active color */
18197     *p++ = whiteToPlay ? 'w' : 'b';
18198     *p++ = ' ';
18199
18200   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18201     haveRights = 0; q = p;
18202     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18203       piece = boards[move][0][i];
18204       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18205         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18206       }
18207     }
18208     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18209       piece = boards[move][BOARD_HEIGHT-1][i];
18210       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18211         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18212       }
18213     }
18214     if(p == q) *p++ = '-';
18215     *p++ = ' ';
18216   }
18217
18218   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18219     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18220   } else {
18221   if(haveRights) {
18222      int handW=0, handB=0;
18223      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18224         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18225         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18226      }
18227      q = p;
18228      if(appData.fischerCastling) {
18229         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18230            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18231                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18232         } else {
18233        /* [HGM] write directly from rights */
18234            if(boards[move][CASTLING][2] != NoRights &&
18235               boards[move][CASTLING][0] != NoRights   )
18236                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18237            if(boards[move][CASTLING][2] != NoRights &&
18238               boards[move][CASTLING][1] != NoRights   )
18239                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18240         }
18241         if(handB) {
18242            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18243                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18244         } else {
18245            if(boards[move][CASTLING][5] != NoRights &&
18246               boards[move][CASTLING][3] != NoRights   )
18247                 *p++ = boards[move][CASTLING][3] + AAA;
18248            if(boards[move][CASTLING][5] != NoRights &&
18249               boards[move][CASTLING][4] != NoRights   )
18250                 *p++ = boards[move][CASTLING][4] + AAA;
18251         }
18252      } else {
18253
18254         /* [HGM] write true castling rights */
18255         if( nrCastlingRights == 6 ) {
18256             int q, k=0;
18257             if(boards[move][CASTLING][0] != NoRights &&
18258                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18259             q = (boards[move][CASTLING][1] != NoRights &&
18260                  boards[move][CASTLING][2] != NoRights  );
18261             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18262                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18263                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18264                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18265             }
18266             if(q) *p++ = 'Q';
18267             k = 0;
18268             if(boards[move][CASTLING][3] != NoRights &&
18269                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18270             q = (boards[move][CASTLING][4] != NoRights &&
18271                  boards[move][CASTLING][5] != NoRights  );
18272             if(handB) {
18273                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18274                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18275                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18276             }
18277             if(q) *p++ = 'q';
18278         }
18279      }
18280      if (q == p) *p++ = '-'; /* No castling rights */
18281      *p++ = ' ';
18282   }
18283
18284   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18285      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18286      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18287     /* En passant target square */
18288     if (move > backwardMostMove) {
18289         fromX = moveList[move - 1][0] - AAA;
18290         fromY = moveList[move - 1][1] - ONE;
18291         toX = moveList[move - 1][2] - AAA;
18292         toY = moveList[move - 1][3] - ONE;
18293         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18294             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18295             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18296             fromX == toX) {
18297             /* 2-square pawn move just happened */
18298             *p++ = toX + AAA;
18299             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18300         } else {
18301             *p++ = '-';
18302         }
18303     } else if(move == backwardMostMove) {
18304         // [HGM] perhaps we should always do it like this, and forget the above?
18305         if((signed char)boards[move][EP_STATUS] >= 0) {
18306             *p++ = boards[move][EP_STATUS] + AAA;
18307             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18308         } else {
18309             *p++ = '-';
18310         }
18311     } else {
18312         *p++ = '-';
18313     }
18314     *p++ = ' ';
18315   }
18316   }
18317
18318     if(moveCounts)
18319     {   int i = 0, j=move;
18320
18321         /* [HGM] find reversible plies */
18322         if (appData.debugMode) { int k;
18323             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18324             for(k=backwardMostMove; k<=forwardMostMove; k++)
18325                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18326
18327         }
18328
18329         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18330         if( j == backwardMostMove ) i += initialRulePlies;
18331         sprintf(p, "%d ", i);
18332         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18333
18334         /* Fullmove number */
18335         sprintf(p, "%d", (move / 2) + 1);
18336     } else *--p = NULLCHAR;
18337
18338     return StrSave(buf);
18339 }
18340
18341 Boolean
18342 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18343 {
18344     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18345     char *p, c;
18346     int emptycount, virgin[BOARD_FILES];
18347     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18348
18349     p = fen;
18350
18351     /* Piece placement data */
18352     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18353         j = 0;
18354         for (;;) {
18355             if (*p == '/' || *p == ' ' || *p == '[' ) {
18356                 if(j > w) w = j;
18357                 emptycount = gameInfo.boardWidth - j;
18358                 while (emptycount--)
18359                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18360                 if (*p == '/') p++;
18361                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18362                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18363                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18364                     }
18365                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18366                 }
18367                 break;
18368 #if(BOARD_FILES >= 10)*0
18369             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18370                 p++; emptycount=10;
18371                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18372                 while (emptycount--)
18373                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18374 #endif
18375             } else if (*p == '*') {
18376                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18377             } else if (isdigit(*p)) {
18378                 emptycount = *p++ - '0';
18379                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18380                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18381                 while (emptycount--)
18382                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18383             } else if (*p == '<') {
18384                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18385                 else if (i != 0 || !shuffle) return FALSE;
18386                 p++;
18387             } else if (shuffle && *p == '>') {
18388                 p++; // for now ignore closing shuffle range, and assume rank-end
18389             } else if (*p == '?') {
18390                 if (j >= gameInfo.boardWidth) return FALSE;
18391                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18392                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18393             } else if (*p == '+' || isalpha(*p)) {
18394                 char *q, *s = SUFFIXES;
18395                 if (j >= gameInfo.boardWidth) return FALSE;
18396                 if(*p=='+') {
18397                     char c = *++p;
18398                     if(q = strchr(s, p[1])) p++;
18399                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18400                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18401                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18402                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18403                 } else {
18404                     char c = *p++;
18405                     if(q = strchr(s, *p)) p++;
18406                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18407                 }
18408
18409                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18410                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18411                     piece = (ChessSquare) (PROMOTED(piece));
18412                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18413                     p++;
18414                 }
18415                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18416                 if(piece == king) wKingRank = i;
18417                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18418             } else {
18419                 return FALSE;
18420             }
18421         }
18422     }
18423     while (*p == '/' || *p == ' ') p++;
18424
18425     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18426
18427     /* [HGM] by default clear Crazyhouse holdings, if present */
18428     if(gameInfo.holdingsWidth) {
18429        for(i=0; i<BOARD_HEIGHT; i++) {
18430            board[i][0]             = EmptySquare; /* black holdings */
18431            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18432            board[i][1]             = (ChessSquare) 0; /* black counts */
18433            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18434        }
18435     }
18436
18437     /* [HGM] look for Crazyhouse holdings here */
18438     while(*p==' ') p++;
18439     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18440         int swap=0, wcnt=0, bcnt=0;
18441         if(*p == '[') p++;
18442         if(*p == '<') swap++, p++;
18443         if(*p == '-' ) p++; /* empty holdings */ else {
18444             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18445             /* if we would allow FEN reading to set board size, we would   */
18446             /* have to add holdings and shift the board read so far here   */
18447             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18448                 p++;
18449                 if((int) piece >= (int) BlackPawn ) {
18450                     i = (int)piece - (int)BlackPawn;
18451                     i = PieceToNumber((ChessSquare)i);
18452                     if( i >= gameInfo.holdingsSize ) return FALSE;
18453                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18454                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18455                     bcnt++;
18456                 } else {
18457                     i = (int)piece - (int)WhitePawn;
18458                     i = PieceToNumber((ChessSquare)i);
18459                     if( i >= gameInfo.holdingsSize ) return FALSE;
18460                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18461                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18462                     wcnt++;
18463                 }
18464             }
18465             if(subst) { // substitute back-rank question marks by holdings pieces
18466                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18467                     int k, m, n = bcnt + 1;
18468                     if(board[0][j] == ClearBoard) {
18469                         if(!wcnt) return FALSE;
18470                         n = rand() % wcnt;
18471                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18472                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18473                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18474                             break;
18475                         }
18476                     }
18477                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18478                         if(!bcnt) return FALSE;
18479                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18480                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18481                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18482                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18483                             break;
18484                         }
18485                     }
18486                 }
18487                 subst = 0;
18488             }
18489         }
18490         if(*p == ']') p++;
18491     }
18492
18493     if(subst) return FALSE; // substitution requested, but no holdings
18494
18495     while(*p == ' ') p++;
18496
18497     /* Active color */
18498     c = *p++;
18499     if(appData.colorNickNames) {
18500       if( c == appData.colorNickNames[0] ) c = 'w'; else
18501       if( c == appData.colorNickNames[1] ) c = 'b';
18502     }
18503     switch (c) {
18504       case 'w':
18505         *blackPlaysFirst = FALSE;
18506         break;
18507       case 'b':
18508         *blackPlaysFirst = TRUE;
18509         break;
18510       default:
18511         return FALSE;
18512     }
18513
18514     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18515     /* return the extra info in global variiables             */
18516
18517     while(*p==' ') p++;
18518
18519     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18520         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18521         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18522     }
18523
18524     /* set defaults in case FEN is incomplete */
18525     board[EP_STATUS] = EP_UNKNOWN;
18526     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18527     for(i=0; i<nrCastlingRights; i++ ) {
18528         board[CASTLING][i] =
18529             appData.fischerCastling ? NoRights : initialRights[i];
18530     }   /* assume possible unless obviously impossible */
18531     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18532     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18533     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18534                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18535     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18536     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18537     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18538                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18539     FENrulePlies = 0;
18540
18541     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18542       char *q = p;
18543       int w=0, b=0;
18544       while(isalpha(*p)) {
18545         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18546         if(islower(*p)) b |= 1 << (*p++ - 'a');
18547       }
18548       if(*p == '-') p++;
18549       if(p != q) {
18550         board[TOUCHED_W] = ~w;
18551         board[TOUCHED_B] = ~b;
18552         while(*p == ' ') p++;
18553       }
18554     } else
18555
18556     if(nrCastlingRights) {
18557       int fischer = 0;
18558       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18559       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18560           /* castling indicator present, so default becomes no castlings */
18561           for(i=0; i<nrCastlingRights; i++ ) {
18562                  board[CASTLING][i] = NoRights;
18563           }
18564       }
18565       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18566              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18567              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18568              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18569         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18570
18571         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18572             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18573             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18574         }
18575         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18576             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18577         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18578                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18579         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18580                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18581         switch(c) {
18582           case'K':
18583               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18584               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18585               board[CASTLING][2] = whiteKingFile;
18586               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18587               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18588               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18589               break;
18590           case'Q':
18591               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18592               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18593               board[CASTLING][2] = whiteKingFile;
18594               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18595               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18596               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18597               break;
18598           case'k':
18599               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18600               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18601               board[CASTLING][5] = blackKingFile;
18602               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18603               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18604               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18605               break;
18606           case'q':
18607               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18608               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18609               board[CASTLING][5] = blackKingFile;
18610               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18611               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18612               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18613           case '-':
18614               break;
18615           default: /* FRC castlings */
18616               if(c >= 'a') { /* black rights */
18617                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18618                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18619                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18620                   if(i == BOARD_RGHT) break;
18621                   board[CASTLING][5] = i;
18622                   c -= AAA;
18623                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18624                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18625                   if(c > i)
18626                       board[CASTLING][3] = c;
18627                   else
18628                       board[CASTLING][4] = c;
18629               } else { /* white rights */
18630                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18631                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18632                     if(board[0][i] == WhiteKing) break;
18633                   if(i == BOARD_RGHT) break;
18634                   board[CASTLING][2] = i;
18635                   c -= AAA - 'a' + 'A';
18636                   if(board[0][c] >= WhiteKing) break;
18637                   if(c > i)
18638                       board[CASTLING][0] = c;
18639                   else
18640                       board[CASTLING][1] = c;
18641               }
18642         }
18643       }
18644       for(i=0; i<nrCastlingRights; i++)
18645         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18646       if(gameInfo.variant == VariantSChess)
18647         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18648       if(fischer && shuffle) appData.fischerCastling = TRUE;
18649     if (appData.debugMode) {
18650         fprintf(debugFP, "FEN castling rights:");
18651         for(i=0; i<nrCastlingRights; i++)
18652         fprintf(debugFP, " %d", board[CASTLING][i]);
18653         fprintf(debugFP, "\n");
18654     }
18655
18656       while(*p==' ') p++;
18657     }
18658
18659     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18660
18661     /* read e.p. field in games that know e.p. capture */
18662     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18663        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18664        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18665       if(*p=='-') {
18666         p++; board[EP_STATUS] = EP_NONE;
18667       } else {
18668          char c = *p++ - AAA;
18669
18670          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18671          if(*p >= '0' && *p <='9') p++;
18672          board[EP_STATUS] = c;
18673       }
18674     }
18675
18676
18677     if(sscanf(p, "%d", &i) == 1) {
18678         FENrulePlies = i; /* 50-move ply counter */
18679         /* (The move number is still ignored)    */
18680     }
18681
18682     return TRUE;
18683 }
18684
18685 void
18686 EditPositionPasteFEN (char *fen)
18687 {
18688   if (fen != NULL) {
18689     Board initial_position;
18690
18691     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18692       DisplayError(_("Bad FEN position in clipboard"), 0);
18693       return ;
18694     } else {
18695       int savedBlackPlaysFirst = blackPlaysFirst;
18696       EditPositionEvent();
18697       blackPlaysFirst = savedBlackPlaysFirst;
18698       CopyBoard(boards[0], initial_position);
18699       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18700       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18701       DisplayBothClocks();
18702       DrawPosition(FALSE, boards[currentMove]);
18703     }
18704   }
18705 }
18706
18707 static char cseq[12] = "\\   ";
18708
18709 Boolean
18710 set_cont_sequence (char *new_seq)
18711 {
18712     int len;
18713     Boolean ret;
18714
18715     // handle bad attempts to set the sequence
18716         if (!new_seq)
18717                 return 0; // acceptable error - no debug
18718
18719     len = strlen(new_seq);
18720     ret = (len > 0) && (len < sizeof(cseq));
18721     if (ret)
18722       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18723     else if (appData.debugMode)
18724       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18725     return ret;
18726 }
18727
18728 /*
18729     reformat a source message so words don't cross the width boundary.  internal
18730     newlines are not removed.  returns the wrapped size (no null character unless
18731     included in source message).  If dest is NULL, only calculate the size required
18732     for the dest buffer.  lp argument indicats line position upon entry, and it's
18733     passed back upon exit.
18734 */
18735 int
18736 wrap (char *dest, char *src, int count, int width, int *lp)
18737 {
18738     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18739
18740     cseq_len = strlen(cseq);
18741     old_line = line = *lp;
18742     ansi = len = clen = 0;
18743
18744     for (i=0; i < count; i++)
18745     {
18746         if (src[i] == '\033')
18747             ansi = 1;
18748
18749         // if we hit the width, back up
18750         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18751         {
18752             // store i & len in case the word is too long
18753             old_i = i, old_len = len;
18754
18755             // find the end of the last word
18756             while (i && src[i] != ' ' && src[i] != '\n')
18757             {
18758                 i--;
18759                 len--;
18760             }
18761
18762             // word too long?  restore i & len before splitting it
18763             if ((old_i-i+clen) >= width)
18764             {
18765                 i = old_i;
18766                 len = old_len;
18767             }
18768
18769             // extra space?
18770             if (i && src[i-1] == ' ')
18771                 len--;
18772
18773             if (src[i] != ' ' && src[i] != '\n')
18774             {
18775                 i--;
18776                 if (len)
18777                     len--;
18778             }
18779
18780             // now append the newline and continuation sequence
18781             if (dest)
18782                 dest[len] = '\n';
18783             len++;
18784             if (dest)
18785                 strncpy(dest+len, cseq, cseq_len);
18786             len += cseq_len;
18787             line = cseq_len;
18788             clen = cseq_len;
18789             continue;
18790         }
18791
18792         if (dest)
18793             dest[len] = src[i];
18794         len++;
18795         if (!ansi)
18796             line++;
18797         if (src[i] == '\n')
18798             line = 0;
18799         if (src[i] == 'm')
18800             ansi = 0;
18801     }
18802     if (dest && appData.debugMode)
18803     {
18804         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18805             count, width, line, len, *lp);
18806         show_bytes(debugFP, src, count);
18807         fprintf(debugFP, "\ndest: ");
18808         show_bytes(debugFP, dest, len);
18809         fprintf(debugFP, "\n");
18810     }
18811     *lp = dest ? line : old_line;
18812
18813     return len;
18814 }
18815
18816 // [HGM] vari: routines for shelving variations
18817 Boolean modeRestore = FALSE;
18818
18819 void
18820 PushInner (int firstMove, int lastMove)
18821 {
18822         int i, j, nrMoves = lastMove - firstMove;
18823
18824         // push current tail of game on stack
18825         savedResult[storedGames] = gameInfo.result;
18826         savedDetails[storedGames] = gameInfo.resultDetails;
18827         gameInfo.resultDetails = NULL;
18828         savedFirst[storedGames] = firstMove;
18829         savedLast [storedGames] = lastMove;
18830         savedFramePtr[storedGames] = framePtr;
18831         framePtr -= nrMoves; // reserve space for the boards
18832         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18833             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18834             for(j=0; j<MOVE_LEN; j++)
18835                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18836             for(j=0; j<2*MOVE_LEN; j++)
18837                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18838             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18839             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18840             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18841             pvInfoList[firstMove+i-1].depth = 0;
18842             commentList[framePtr+i] = commentList[firstMove+i];
18843             commentList[firstMove+i] = NULL;
18844         }
18845
18846         storedGames++;
18847         forwardMostMove = firstMove; // truncate game so we can start variation
18848 }
18849
18850 void
18851 PushTail (int firstMove, int lastMove)
18852 {
18853         if(appData.icsActive) { // only in local mode
18854                 forwardMostMove = currentMove; // mimic old ICS behavior
18855                 return;
18856         }
18857         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18858
18859         PushInner(firstMove, lastMove);
18860         if(storedGames == 1) GreyRevert(FALSE);
18861         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18862 }
18863
18864 void
18865 PopInner (Boolean annotate)
18866 {
18867         int i, j, nrMoves;
18868         char buf[8000], moveBuf[20];
18869
18870         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18871         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18872         nrMoves = savedLast[storedGames] - currentMove;
18873         if(annotate) {
18874                 int cnt = 10;
18875                 if(!WhiteOnMove(currentMove))
18876                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18877                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18878                 for(i=currentMove; i<forwardMostMove; i++) {
18879                         if(WhiteOnMove(i))
18880                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18881                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18882                         strcat(buf, moveBuf);
18883                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18884                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18885                 }
18886                 strcat(buf, ")");
18887         }
18888         for(i=1; i<=nrMoves; i++) { // copy last variation back
18889             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18890             for(j=0; j<MOVE_LEN; j++)
18891                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18892             for(j=0; j<2*MOVE_LEN; j++)
18893                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18894             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18895             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18896             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18897             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18898             commentList[currentMove+i] = commentList[framePtr+i];
18899             commentList[framePtr+i] = NULL;
18900         }
18901         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18902         framePtr = savedFramePtr[storedGames];
18903         gameInfo.result = savedResult[storedGames];
18904         if(gameInfo.resultDetails != NULL) {
18905             free(gameInfo.resultDetails);
18906       }
18907         gameInfo.resultDetails = savedDetails[storedGames];
18908         forwardMostMove = currentMove + nrMoves;
18909 }
18910
18911 Boolean
18912 PopTail (Boolean annotate)
18913 {
18914         if(appData.icsActive) return FALSE; // only in local mode
18915         if(!storedGames) return FALSE; // sanity
18916         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18917
18918         PopInner(annotate);
18919         if(currentMove < forwardMostMove) ForwardEvent(); else
18920         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18921
18922         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18923         return TRUE;
18924 }
18925
18926 void
18927 CleanupTail ()
18928 {       // remove all shelved variations
18929         int i;
18930         for(i=0; i<storedGames; i++) {
18931             if(savedDetails[i])
18932                 free(savedDetails[i]);
18933             savedDetails[i] = NULL;
18934         }
18935         for(i=framePtr; i<MAX_MOVES; i++) {
18936                 if(commentList[i]) free(commentList[i]);
18937                 commentList[i] = NULL;
18938         }
18939         framePtr = MAX_MOVES-1;
18940         storedGames = 0;
18941 }
18942
18943 void
18944 LoadVariation (int index, char *text)
18945 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18946         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18947         int level = 0, move;
18948
18949         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18950         // first find outermost bracketing variation
18951         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18952             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18953                 if(*p == '{') wait = '}'; else
18954                 if(*p == '[') wait = ']'; else
18955                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18956                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18957             }
18958             if(*p == wait) wait = NULLCHAR; // closing ]} found
18959             p++;
18960         }
18961         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18962         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18963         end[1] = NULLCHAR; // clip off comment beyond variation
18964         ToNrEvent(currentMove-1);
18965         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18966         // kludge: use ParsePV() to append variation to game
18967         move = currentMove;
18968         ParsePV(start, TRUE, TRUE);
18969         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18970         ClearPremoveHighlights();
18971         CommentPopDown();
18972         ToNrEvent(currentMove+1);
18973 }
18974
18975 void
18976 LoadTheme ()
18977 {
18978     char *p, *q, buf[MSG_SIZ];
18979     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18980         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18981         ParseArgsFromString(buf);
18982         ActivateTheme(TRUE); // also redo colors
18983         return;
18984     }
18985     p = nickName;
18986     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18987     {
18988         int len;
18989         q = appData.themeNames;
18990         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18991       if(appData.useBitmaps) {
18992         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18993                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18994                 appData.liteBackTextureMode,
18995                 appData.darkBackTextureMode );
18996       } else {
18997         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18998                 Col2Text(2),   // lightSquareColor
18999                 Col2Text(3) ); // darkSquareColor
19000       }
19001       if(appData.useBorder) {
19002         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
19003                 appData.border);
19004       } else {
19005         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
19006       }
19007       if(appData.useFont) {
19008         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
19009                 appData.renderPiecesWithFont,
19010                 appData.fontToPieceTable,
19011                 Col2Text(9),    // appData.fontBackColorWhite
19012                 Col2Text(10) ); // appData.fontForeColorBlack
19013       } else {
19014         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
19015                 appData.pieceDirectory);
19016         if(!appData.pieceDirectory[0])
19017           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
19018                 Col2Text(0),   // whitePieceColor
19019                 Col2Text(1) ); // blackPieceColor
19020       }
19021       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
19022                 Col2Text(4),   // highlightSquareColor
19023                 Col2Text(5) ); // premoveHighlightColor
19024         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
19025         if(insert != q) insert[-1] = NULLCHAR;
19026         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
19027         if(q)   free(q);
19028     }
19029     ActivateTheme(FALSE);
19030 }