Print score with same sign in message and engine output
[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 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void SendToProgram P((char *message, ChessProgramState *cps));
196 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
197 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
198                            char *buf, int count, int error));
199 void SendTimeControl P((ChessProgramState *cps,
200                         int mps, long tc, int inc, int sd, int st));
201 char *TimeControlTagValue P((void));
202 void Attention P((ChessProgramState *cps));
203 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
204 int ResurrectChessProgram P((void));
205 void DisplayComment P((int moveNumber, char *text));
206 void DisplayMove P((int moveNumber));
207
208 void ParseGameHistory P((char *game));
209 void ParseBoard12 P((char *string));
210 void KeepAlive P((void));
211 void StartClocks P((void));
212 void SwitchClocks P((int nr));
213 void StopClocks P((void));
214 void ResetClocks P((void));
215 char *PGNDate P((void));
216 void SetGameInfo P((void));
217 int RegisterMove P((void));
218 void MakeRegisteredMove P((void));
219 void TruncateGame P((void));
220 int looking_at P((char *, int *, char *));
221 void CopyPlayerNameIntoFileName P((char **, char *));
222 char *SavePart P((char *));
223 int SaveGameOldStyle P((FILE *));
224 int SaveGamePGN P((FILE *));
225 int CheckFlags P((void));
226 long NextTickLength P((long));
227 void CheckTimeControl P((void));
228 void show_bytes P((FILE *, char *, int));
229 int string_to_rating P((char *str));
230 void ParseFeatures P((char* args, ChessProgramState *cps));
231 void InitBackEnd3 P((void));
232 void FeatureDone P((ChessProgramState* cps, int val));
233 void InitChessProgram P((ChessProgramState *cps, int setup));
234 void OutputKibitz(int window, char *text);
235 int PerpetualChase(int first, int last);
236 int EngineOutputIsUp();
237 void InitDrawingSizes(int x, int y);
238 void NextMatchGame P((void));
239 int NextTourneyGame P((int nr, int *swap));
240 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
241 FILE *WriteTourneyFile P((char *results, FILE *f));
242 void DisplayTwoMachinesTitle P(());
243 static void ExcludeClick P((int index));
244 void ToggleSecond P((void));
245 void PauseEngine P((ChessProgramState *cps));
246 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
247
248 #ifdef WIN32
249        extern void ConsoleCreate();
250 #endif
251
252 ChessProgramState *WhitePlayer();
253 int VerifyDisplayMode P(());
254
255 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
256 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
257 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
258 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
259 void ics_update_width P((int new_width));
260 extern char installDir[MSG_SIZ];
261 VariantClass startVariant; /* [HGM] nicks: initial variant */
262 Boolean abortMatch;
263
264 extern int tinyLayout, smallLayout;
265 ChessProgramStats programStats;
266 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
267 int endPV = -1;
268 static int exiting = 0; /* [HGM] moved to top */
269 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
270 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
271 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
272 int partnerHighlight[2];
273 Boolean partnerBoardValid = 0;
274 char partnerStatus[MSG_SIZ];
275 Boolean partnerUp;
276 Boolean originalFlip;
277 Boolean twoBoards = 0;
278 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
279 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
280 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
281 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
282 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
283 int opponentKibitzes;
284 int lastSavedGame; /* [HGM] save: ID of game */
285 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
286 extern int chatCount;
287 int chattingPartner;
288 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
289 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
290 char lastMsg[MSG_SIZ];
291 char lastTalker[MSG_SIZ];
292 ChessSquare pieceSweep = EmptySquare;
293 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
294 int promoDefaultAltered;
295 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
296 static int initPing = -1;
297 int border;       /* [HGM] width of board rim, needed to size seek graph  */
298 char bestMove[MSG_SIZ];
299 int solvingTime, totalTime;
300
301 /* States for ics_getting_history */
302 #define H_FALSE 0
303 #define H_REQUESTED 1
304 #define H_GOT_REQ_HEADER 2
305 #define H_GOT_UNREQ_HEADER 3
306 #define H_GETTING_MOVES 4
307 #define H_GOT_UNWANTED_HEADER 5
308
309 /* whosays values for GameEnds */
310 #define GE_ICS 0
311 #define GE_ENGINE 1
312 #define GE_PLAYER 2
313 #define GE_FILE 3
314 #define GE_XBOARD 4
315 #define GE_ENGINE1 5
316 #define GE_ENGINE2 6
317
318 /* Maximum number of games in a cmail message */
319 #define CMAIL_MAX_GAMES 20
320
321 /* Different types of move when calling RegisterMove */
322 #define CMAIL_MOVE   0
323 #define CMAIL_RESIGN 1
324 #define CMAIL_DRAW   2
325 #define CMAIL_ACCEPT 3
326
327 /* Different types of result to remember for each game */
328 #define CMAIL_NOT_RESULT 0
329 #define CMAIL_OLD_RESULT 1
330 #define CMAIL_NEW_RESULT 2
331
332 /* Telnet protocol constants */
333 #define TN_WILL 0373
334 #define TN_WONT 0374
335 #define TN_DO   0375
336 #define TN_DONT 0376
337 #define TN_IAC  0377
338 #define TN_ECHO 0001
339 #define TN_SGA  0003
340 #define TN_PORT 23
341
342 char*
343 safeStrCpy (char *dst, const char *src, size_t count)
344 { // [HGM] made safe
345   int i;
346   assert( dst != NULL );
347   assert( src != NULL );
348   assert( count > 0 );
349
350   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
351   if(  i == count && dst[count-1] != NULLCHAR)
352     {
353       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
354       if(appData.debugMode)
355         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
356     }
357
358   return dst;
359 }
360
361 /* Some compiler can't cast u64 to double
362  * This function do the job for us:
363
364  * We use the highest bit for cast, this only
365  * works if the highest bit is not
366  * in use (This should not happen)
367  *
368  * We used this for all compiler
369  */
370 double
371 u64ToDouble (u64 value)
372 {
373   double r;
374   u64 tmp = value & u64Const(0x7fffffffffffffff);
375   r = (double)(s64)tmp;
376   if (value & u64Const(0x8000000000000000))
377        r +=  9.2233720368547758080e18; /* 2^63 */
378  return r;
379 }
380
381 /* Fake up flags for now, as we aren't keeping track of castling
382    availability yet. [HGM] Change of logic: the flag now only
383    indicates the type of castlings allowed by the rule of the game.
384    The actual rights themselves are maintained in the array
385    castlingRights, as part of the game history, and are not probed
386    by this function.
387  */
388 int
389 PosFlags (index)
390 {
391   int flags = F_ALL_CASTLE_OK;
392   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
393   switch (gameInfo.variant) {
394   case VariantSuicide:
395     flags &= ~F_ALL_CASTLE_OK;
396   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
397     flags |= F_IGNORE_CHECK;
398   case VariantLosers:
399     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
400     break;
401   case VariantAtomic:
402     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
403     break;
404   case VariantKriegspiel:
405     flags |= F_KRIEGSPIEL_CAPTURE;
406     break;
407   case VariantCapaRandom:
408   case VariantFischeRandom:
409     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
410   case VariantNoCastle:
411   case VariantShatranj:
412   case VariantCourier:
413   case VariantMakruk:
414   case VariantASEAN:
415   case VariantGrand:
416     flags &= ~F_ALL_CASTLE_OK;
417     break;
418   case VariantChu:
419   case VariantChuChess:
420   case VariantLion:
421     flags |= F_NULL_MOVE;
422     break;
423   default:
424     break;
425   }
426   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
427   return flags;
428 }
429
430 FILE *gameFileFP, *debugFP, *serverFP;
431 char *currentDebugFile; // [HGM] debug split: to remember name
432
433 /*
434     [AS] Note: sometimes, the sscanf() function is used to parse the input
435     into a fixed-size buffer. Because of this, we must be prepared to
436     receive strings as long as the size of the input buffer, which is currently
437     set to 4K for Windows and 8K for the rest.
438     So, we must either allocate sufficiently large buffers here, or
439     reduce the size of the input buffer in the input reading part.
440 */
441
442 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
443 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
444 char thinkOutput1[MSG_SIZ*10];
445 char promoRestrict[MSG_SIZ];
446
447 ChessProgramState first, second, pairing;
448
449 /* premove variables */
450 int premoveToX = 0;
451 int premoveToY = 0;
452 int premoveFromX = 0;
453 int premoveFromY = 0;
454 int premovePromoChar = 0;
455 int gotPremove = 0;
456 Boolean alarmSounded;
457 /* end premove variables */
458
459 char *ics_prefix = "$";
460 enum ICS_TYPE ics_type = ICS_GENERIC;
461
462 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
463 int pauseExamForwardMostMove = 0;
464 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
465 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
466 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
467 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
468 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
469 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
470 int whiteFlag = FALSE, blackFlag = FALSE;
471 int userOfferedDraw = FALSE;
472 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
473 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
474 int cmailMoveType[CMAIL_MAX_GAMES];
475 long ics_clock_paused = 0;
476 ProcRef icsPR = NoProc, cmailPR = NoProc;
477 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
478 GameMode gameMode = BeginningOfGame;
479 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
480 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
481 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
482 int hiddenThinkOutputState = 0; /* [AS] */
483 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
484 int adjudicateLossPlies = 6;
485 char white_holding[64], black_holding[64];
486 TimeMark lastNodeCountTime;
487 long lastNodeCount=0;
488 int shiftKey, controlKey; // [HGM] set by mouse handler
489
490 int have_sent_ICS_logon = 0;
491 int movesPerSession;
492 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
493 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
494 Boolean adjustedClock;
495 long timeControl_2; /* [AS] Allow separate time controls */
496 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
497 long timeRemaining[2][MAX_MOVES];
498 int matchGame = 0, nextGame = 0, roundNr = 0;
499 Boolean waitingForGame = FALSE, startingEngine = FALSE;
500 TimeMark programStartTime, pauseStart;
501 char ics_handle[MSG_SIZ];
502 int have_set_title = 0;
503
504 /* animateTraining preserves the state of appData.animate
505  * when Training mode is activated. This allows the
506  * response to be animated when appData.animate == TRUE and
507  * appData.animateDragging == TRUE.
508  */
509 Boolean animateTraining;
510
511 GameInfo gameInfo;
512
513 AppData appData;
514
515 Board boards[MAX_MOVES];
516 /* [HGM] Following 7 needed for accurate legality tests: */
517 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
518 unsigned char initialRights[BOARD_FILES];
519 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
520 int   initialRulePlies, FENrulePlies;
521 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
522 int loadFlag = 0;
523 Boolean shuffleOpenings;
524 int mute; // mute all sounds
525
526 // [HGM] vari: next 12 to save and restore variations
527 #define MAX_VARIATIONS 10
528 int framePtr = MAX_MOVES-1; // points to free stack entry
529 int storedGames = 0;
530 int savedFirst[MAX_VARIATIONS];
531 int savedLast[MAX_VARIATIONS];
532 int savedFramePtr[MAX_VARIATIONS];
533 char *savedDetails[MAX_VARIATIONS];
534 ChessMove savedResult[MAX_VARIATIONS];
535
536 void PushTail P((int firstMove, int lastMove));
537 Boolean PopTail P((Boolean annotate));
538 void PushInner P((int firstMove, int lastMove));
539 void PopInner P((Boolean annotate));
540 void CleanupTail P((void));
541
542 ChessSquare  FIDEArray[2][BOARD_FILES] = {
543     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
546         BlackKing, BlackBishop, BlackKnight, BlackRook }
547 };
548
549 ChessSquare twoKingsArray[2][BOARD_FILES] = {
550     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
551         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
553         BlackKing, BlackKing, BlackKnight, BlackRook }
554 };
555
556 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
558         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
559     { BlackRook, BlackMan, BlackBishop, BlackQueen,
560         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
561 };
562
563 ChessSquare SpartanArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
566     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
567         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
568 };
569
570 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
571     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
574         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
575 };
576
577 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
578     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
579         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
580     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
581         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
582 };
583
584 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
585     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
586         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackMan, BlackFerz,
588         BlackKing, BlackMan, BlackKnight, BlackRook }
589 };
590
591 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
592     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
593         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
594     { BlackRook, BlackKnight, BlackMan, BlackFerz,
595         BlackKing, BlackMan, BlackKnight, BlackRook }
596 };
597
598 ChessSquare  lionArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
600         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
601     { BlackRook, BlackLion, BlackBishop, BlackQueen,
602         BlackKing, BlackBishop, BlackKnight, BlackRook }
603 };
604
605
606 #if (BOARD_FILES>=10)
607 ChessSquare ShogiArray[2][BOARD_FILES] = {
608     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
609         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
610     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
611         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
612 };
613
614 ChessSquare XiangqiArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
616         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
618         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
619 };
620
621 ChessSquare CapablancaArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
623         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
625         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
626 };
627
628 ChessSquare GreatArray[2][BOARD_FILES] = {
629     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
630         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
631     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
632         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
633 };
634
635 ChessSquare JanusArray[2][BOARD_FILES] = {
636     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
637         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
638     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
639         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
640 };
641
642 ChessSquare GrandArray[2][BOARD_FILES] = {
643     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
644         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
645     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
646         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
647 };
648
649 ChessSquare ChuChessArray[2][BOARD_FILES] = {
650     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
651         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
652     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
653         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
654 };
655
656 #ifdef GOTHIC
657 ChessSquare GothicArray[2][BOARD_FILES] = {
658     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
659         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
660     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
661         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
662 };
663 #else // !GOTHIC
664 #define GothicArray CapablancaArray
665 #endif // !GOTHIC
666
667 #ifdef FALCON
668 ChessSquare FalconArray[2][BOARD_FILES] = {
669     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
670         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
671     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
672         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
673 };
674 #else // !FALCON
675 #define FalconArray CapablancaArray
676 #endif // !FALCON
677
678 #else // !(BOARD_FILES>=10)
679 #define XiangqiPosition FIDEArray
680 #define CapablancaArray FIDEArray
681 #define GothicArray FIDEArray
682 #define GreatArray FIDEArray
683 #endif // !(BOARD_FILES>=10)
684
685 #if (BOARD_FILES>=12)
686 ChessSquare CourierArray[2][BOARD_FILES] = {
687     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
688         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
689     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
690         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
691 };
692 ChessSquare ChuArray[6][BOARD_FILES] = {
693     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
694       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
695     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
696       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
697     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
698       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
699     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
700       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
701     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
702       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
703     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
704       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
705 };
706 #else // !(BOARD_FILES>=12)
707 #define CourierArray CapablancaArray
708 #define ChuArray CapablancaArray
709 #endif // !(BOARD_FILES>=12)
710
711
712 Board initialPosition;
713
714
715 /* Convert str to a rating. Checks for special cases of "----",
716
717    "++++", etc. Also strips ()'s */
718 int
719 string_to_rating (char *str)
720 {
721   while(*str && !isdigit(*str)) ++str;
722   if (!*str)
723     return 0;   /* One of the special "no rating" cases */
724   else
725     return atoi(str);
726 }
727
728 void
729 ClearProgramStats ()
730 {
731     /* Init programStats */
732     programStats.movelist[0] = 0;
733     programStats.depth = 0;
734     programStats.nr_moves = 0;
735     programStats.moves_left = 0;
736     programStats.nodes = 0;
737     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
738     programStats.score = 0;
739     programStats.got_only_move = 0;
740     programStats.got_fail = 0;
741     programStats.line_is_book = 0;
742 }
743
744 void
745 CommonEngineInit ()
746 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754
755     first.other = &second;
756     second.other = &first;
757
758     { float norm = 1;
759         if(appData.timeOddsMode) {
760             norm = appData.timeOdds[0];
761             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
762         }
763         first.timeOdds  = appData.timeOdds[0]/norm;
764         second.timeOdds = appData.timeOdds[1]/norm;
765     }
766
767     if(programVersion) free(programVersion);
768     if (appData.noChessProgram) {
769         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
770         sprintf(programVersion, "%s", PACKAGE_STRING);
771     } else {
772       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
773       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
774       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
775     }
776 }
777
778 void
779 UnloadEngine (ChessProgramState *cps)
780 {
781         /* Kill off first chess program */
782         if (cps->isr != NULL)
783           RemoveInputSource(cps->isr);
784         cps->isr = NULL;
785
786         if (cps->pr != NoProc) {
787             ExitAnalyzeMode();
788             DoSleep( appData.delayBeforeQuit );
789             SendToProgram("quit\n", cps);
790             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
791         }
792         cps->pr = NoProc;
793         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
794 }
795
796 void
797 ClearOptions (ChessProgramState *cps)
798 {
799     int i;
800     cps->nrOptions = cps->comboCnt = 0;
801     for(i=0; i<MAX_OPTIONS; i++) {
802         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
803         cps->option[i].textValue = 0;
804     }
805 }
806
807 char *engineNames[] = {
808   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
809      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
810 N_("first"),
811   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
812      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
813 N_("second")
814 };
815
816 void
817 InitEngine (ChessProgramState *cps, int n)
818 {   // [HGM] all engine initialiation put in a function that does one engine
819
820     ClearOptions(cps);
821
822     cps->which = engineNames[n];
823     cps->maybeThinking = FALSE;
824     cps->pr = NoProc;
825     cps->isr = NULL;
826     cps->sendTime = 2;
827     cps->sendDrawOffers = 1;
828
829     cps->program = appData.chessProgram[n];
830     cps->host = appData.host[n];
831     cps->dir = appData.directory[n];
832     cps->initString = appData.engInitString[n];
833     cps->computerString = appData.computerString[n];
834     cps->useSigint  = TRUE;
835     cps->useSigterm = TRUE;
836     cps->reuse = appData.reuse[n];
837     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
838     cps->useSetboard = FALSE;
839     cps->useSAN = FALSE;
840     cps->usePing = FALSE;
841     cps->lastPing = 0;
842     cps->lastPong = 0;
843     cps->usePlayother = FALSE;
844     cps->useColors = TRUE;
845     cps->useUsermove = FALSE;
846     cps->sendICS = FALSE;
847     cps->sendName = appData.icsActive;
848     cps->sdKludge = FALSE;
849     cps->stKludge = FALSE;
850     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
851     TidyProgramName(cps->program, cps->host, cps->tidy);
852     cps->matchWins = 0;
853     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
854     cps->analysisSupport = 2; /* detect */
855     cps->analyzing = FALSE;
856     cps->initDone = FALSE;
857     cps->reload = FALSE;
858     cps->pseudo = appData.pseudo[n];
859
860     /* New features added by Tord: */
861     cps->useFEN960 = FALSE;
862     cps->useOOCastle = TRUE;
863     /* End of new features added by Tord. */
864     cps->fenOverride  = appData.fenOverride[n];
865
866     /* [HGM] time odds: set factor for each machine */
867     cps->timeOdds  = appData.timeOdds[n];
868
869     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
870     cps->accumulateTC = appData.accumulateTC[n];
871     cps->maxNrOfSessions = 1;
872
873     /* [HGM] debug */
874     cps->debug = FALSE;
875
876     cps->drawDepth = appData.drawDepth[n];
877     cps->supportsNPS = UNKNOWN;
878     cps->memSize = FALSE;
879     cps->maxCores = FALSE;
880     ASSIGN(cps->egtFormats, "");
881
882     /* [HGM] options */
883     cps->optionSettings  = appData.engOptions[n];
884
885     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
886     cps->isUCI = appData.isUCI[n]; /* [AS] */
887     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
888     cps->highlight = 0;
889
890     if (appData.protocolVersion[n] > PROTOVER
891         || appData.protocolVersion[n] < 1)
892       {
893         char buf[MSG_SIZ];
894         int len;
895
896         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
897                        appData.protocolVersion[n]);
898         if( (len >= MSG_SIZ) && appData.debugMode )
899           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
900
901         DisplayFatalError(buf, 0, 2);
902       }
903     else
904       {
905         cps->protocolVersion = appData.protocolVersion[n];
906       }
907
908     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
909     ParseFeatures(appData.featureDefaults, cps);
910 }
911
912 ChessProgramState *savCps;
913
914 GameMode oldMode;
915
916 void
917 LoadEngine ()
918 {
919     int i;
920     if(WaitForEngine(savCps, LoadEngine)) return;
921     CommonEngineInit(); // recalculate time odds
922     if(gameInfo.variant != StringToVariant(appData.variant)) {
923         // we changed variant when loading the engine; this forces us to reset
924         Reset(TRUE, savCps != &first);
925         oldMode = BeginningOfGame; // to prevent restoring old mode
926     }
927     InitChessProgram(savCps, FALSE);
928     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
929     DisplayMessage("", "");
930     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
931     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
932     ThawUI();
933     SetGNUMode();
934     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
935 }
936
937 void
938 ReplaceEngine (ChessProgramState *cps, int n)
939 {
940     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
941     keepInfo = 1;
942     if(oldMode != BeginningOfGame) EditGameEvent();
943     keepInfo = 0;
944     UnloadEngine(cps);
945     appData.noChessProgram = FALSE;
946     appData.clockMode = TRUE;
947     InitEngine(cps, n);
948     UpdateLogos(TRUE);
949     if(n) return; // only startup first engine immediately; second can wait
950     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
951     LoadEngine();
952 }
953
954 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
955 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
956
957 static char resetOptions[] =
958         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
959         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
960         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
961         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
962
963 void
964 FloatToFront(char **list, char *engineLine)
965 {
966     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
967     int i=0;
968     if(appData.recentEngines <= 0) return;
969     TidyProgramName(engineLine, "localhost", tidy+1);
970     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
971     strncpy(buf+1, *list, MSG_SIZ-50);
972     if(p = strstr(buf, tidy)) { // tidy name appears in list
973         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
974         while(*p++ = *++q); // squeeze out
975     }
976     strcat(tidy, buf+1); // put list behind tidy name
977     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
978     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
979     ASSIGN(*list, tidy+1);
980 }
981
982 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
983
984 void
985 Load (ChessProgramState *cps, int i)
986 {
987     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
988     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
989         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
990         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
991         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
992         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
993         appData.firstProtocolVersion = PROTOVER;
994         ParseArgsFromString(buf);
995         SwapEngines(i);
996         ReplaceEngine(cps, i);
997         FloatToFront(&appData.recentEngineList, engineLine);
998         return;
999     }
1000     p = engineName;
1001     while(q = strchr(p, SLASH)) p = q+1;
1002     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1003     if(engineDir[0] != NULLCHAR) {
1004         ASSIGN(appData.directory[i], engineDir); p = engineName;
1005     } else if(p != engineName) { // derive directory from engine path, when not given
1006         p[-1] = 0;
1007         ASSIGN(appData.directory[i], engineName);
1008         p[-1] = SLASH;
1009         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1010     } else { ASSIGN(appData.directory[i], "."); }
1011     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1012     if(params[0]) {
1013         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1014         snprintf(command, MSG_SIZ, "%s %s", p, params);
1015         p = command;
1016     }
1017     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1018     ASSIGN(appData.chessProgram[i], p);
1019     appData.isUCI[i] = isUCI;
1020     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1021     appData.hasOwnBookUCI[i] = hasBook;
1022     if(!nickName[0]) useNick = FALSE;
1023     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1024     if(addToList) {
1025         int len;
1026         char quote;
1027         q = firstChessProgramNames;
1028         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1029         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1030         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1031                         quote, p, quote, appData.directory[i],
1032                         useNick ? " -fn \"" : "",
1033                         useNick ? nickName : "",
1034                         useNick ? "\"" : "",
1035                         v1 ? " -firstProtocolVersion 1" : "",
1036                         hasBook ? "" : " -fNoOwnBookUCI",
1037                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1038                         storeVariant ? " -variant " : "",
1039                         storeVariant ? VariantName(gameInfo.variant) : "");
1040         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1041         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1042         if(insert != q) insert[-1] = NULLCHAR;
1043         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1044         if(q)   free(q);
1045         FloatToFront(&appData.recentEngineList, buf);
1046     }
1047     ReplaceEngine(cps, i);
1048 }
1049
1050 void
1051 InitTimeControls ()
1052 {
1053     int matched, min, sec;
1054     /*
1055      * Parse timeControl resource
1056      */
1057     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1058                           appData.movesPerSession)) {
1059         char buf[MSG_SIZ];
1060         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1061         DisplayFatalError(buf, 0, 2);
1062     }
1063
1064     /*
1065      * Parse searchTime resource
1066      */
1067     if (*appData.searchTime != NULLCHAR) {
1068         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1069         if (matched == 1) {
1070             searchTime = min * 60;
1071         } else if (matched == 2) {
1072             searchTime = min * 60 + sec;
1073         } else {
1074             char buf[MSG_SIZ];
1075             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1076             DisplayFatalError(buf, 0, 2);
1077         }
1078     }
1079 }
1080
1081 void
1082 InitBackEnd1 ()
1083 {
1084
1085     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1086     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1087
1088     GetTimeMark(&programStartTime);
1089     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1090     appData.seedBase = random() + (random()<<15);
1091     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1092
1093     ClearProgramStats();
1094     programStats.ok_to_send = 1;
1095     programStats.seen_stat = 0;
1096
1097     /*
1098      * Initialize game list
1099      */
1100     ListNew(&gameList);
1101
1102
1103     /*
1104      * Internet chess server status
1105      */
1106     if (appData.icsActive) {
1107         appData.matchMode = FALSE;
1108         appData.matchGames = 0;
1109 #if ZIPPY
1110         appData.noChessProgram = !appData.zippyPlay;
1111 #else
1112         appData.zippyPlay = FALSE;
1113         appData.zippyTalk = FALSE;
1114         appData.noChessProgram = TRUE;
1115 #endif
1116         if (*appData.icsHelper != NULLCHAR) {
1117             appData.useTelnet = TRUE;
1118             appData.telnetProgram = appData.icsHelper;
1119         }
1120     } else {
1121         appData.zippyTalk = appData.zippyPlay = FALSE;
1122     }
1123
1124     /* [AS] Initialize pv info list [HGM] and game state */
1125     {
1126         int i, j;
1127
1128         for( i=0; i<=framePtr; i++ ) {
1129             pvInfoList[i].depth = -1;
1130             boards[i][EP_STATUS] = EP_NONE;
1131             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1132         }
1133     }
1134
1135     InitTimeControls();
1136
1137     /* [AS] Adjudication threshold */
1138     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1139
1140     InitEngine(&first, 0);
1141     InitEngine(&second, 1);
1142     CommonEngineInit();
1143
1144     pairing.which = "pairing"; // pairing engine
1145     pairing.pr = NoProc;
1146     pairing.isr = NULL;
1147     pairing.program = appData.pairingEngine;
1148     pairing.host = "localhost";
1149     pairing.dir = ".";
1150
1151     if (appData.icsActive) {
1152         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1153     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1154         appData.clockMode = FALSE;
1155         first.sendTime = second.sendTime = 0;
1156     }
1157
1158 #if ZIPPY
1159     /* Override some settings from environment variables, for backward
1160        compatibility.  Unfortunately it's not feasible to have the env
1161        vars just set defaults, at least in xboard.  Ugh.
1162     */
1163     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1164       ZippyInit();
1165     }
1166 #endif
1167
1168     if (!appData.icsActive) {
1169       char buf[MSG_SIZ];
1170       int len;
1171
1172       /* Check for variants that are supported only in ICS mode,
1173          or not at all.  Some that are accepted here nevertheless
1174          have bugs; see comments below.
1175       */
1176       VariantClass variant = StringToVariant(appData.variant);
1177       switch (variant) {
1178       case VariantBughouse:     /* need four players and two boards */
1179       case VariantKriegspiel:   /* need to hide pieces and move details */
1180         /* case VariantFischeRandom: (Fabien: moved below) */
1181         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1182         if( (len >= MSG_SIZ) && appData.debugMode )
1183           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1184
1185         DisplayFatalError(buf, 0, 2);
1186         return;
1187
1188       case VariantUnknown:
1189       case VariantLoadable:
1190       case Variant29:
1191       case Variant30:
1192       case Variant31:
1193       case Variant32:
1194       case Variant33:
1195       case Variant34:
1196       case Variant35:
1197       case Variant36:
1198       default:
1199         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1200         if( (len >= MSG_SIZ) && appData.debugMode )
1201           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1202
1203         DisplayFatalError(buf, 0, 2);
1204         return;
1205
1206       case VariantNormal:     /* definitely works! */
1207         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1208           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1209           return;
1210         }
1211       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1212       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1213       case VariantGothic:     /* [HGM] should work */
1214       case VariantCapablanca: /* [HGM] should work */
1215       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1216       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1217       case VariantChu:        /* [HGM] experimental */
1218       case VariantKnightmate: /* [HGM] should work */
1219       case VariantCylinder:   /* [HGM] untested */
1220       case VariantFalcon:     /* [HGM] untested */
1221       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1222                                  offboard interposition not understood */
1223       case VariantWildCastle: /* pieces not automatically shuffled */
1224       case VariantNoCastle:   /* pieces not automatically shuffled */
1225       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1226       case VariantLosers:     /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantSuicide:    /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantGiveaway:   /* should work except for win condition,
1231                                  and doesn't know captures are mandatory */
1232       case VariantTwoKings:   /* should work */
1233       case VariantAtomic:     /* should work except for win condition */
1234       case Variant3Check:     /* should work except for win condition */
1235       case VariantShatranj:   /* should work except for all win conditions */
1236       case VariantMakruk:     /* should work except for draw countdown */
1237       case VariantASEAN :     /* should work except for draw countdown */
1238       case VariantBerolina:   /* might work if TestLegality is off */
1239       case VariantCapaRandom: /* should work */
1240       case VariantJanus:      /* should work */
1241       case VariantSuper:      /* experimental */
1242       case VariantGreat:      /* experimental, requires legality testing to be off */
1243       case VariantSChess:     /* S-Chess, should work */
1244       case VariantGrand:      /* should work */
1245       case VariantSpartan:    /* should work */
1246       case VariantLion:       /* should work */
1247       case VariantChuChess:   /* should work */
1248         break;
1249       }
1250     }
1251
1252 }
1253
1254 int
1255 NextIntegerFromString (char ** str, long * value)
1256 {
1257     int result = -1;
1258     char * s = *str;
1259
1260     while( *s == ' ' || *s == '\t' ) {
1261         s++;
1262     }
1263
1264     *value = 0;
1265
1266     if( *s >= '0' && *s <= '9' ) {
1267         while( *s >= '0' && *s <= '9' ) {
1268             *value = *value * 10 + (*s - '0');
1269             s++;
1270         }
1271
1272         result = 0;
1273     }
1274
1275     *str = s;
1276
1277     return result;
1278 }
1279
1280 int
1281 NextTimeControlFromString (char ** str, long * value)
1282 {
1283     long temp;
1284     int result = NextIntegerFromString( str, &temp );
1285
1286     if( result == 0 ) {
1287         *value = temp * 60; /* Minutes */
1288         if( **str == ':' ) {
1289             (*str)++;
1290             result = NextIntegerFromString( str, &temp );
1291             *value += temp; /* Seconds */
1292         }
1293     }
1294
1295     return result;
1296 }
1297
1298 int
1299 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1300 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1301     int result = -1, type = 0; long temp, temp2;
1302
1303     if(**str != ':') return -1; // old params remain in force!
1304     (*str)++;
1305     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1306     if( NextIntegerFromString( str, &temp ) ) return -1;
1307     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1308
1309     if(**str != '/') {
1310         /* time only: incremental or sudden-death time control */
1311         if(**str == '+') { /* increment follows; read it */
1312             (*str)++;
1313             if(**str == '!') type = *(*str)++; // Bronstein TC
1314             if(result = NextIntegerFromString( str, &temp2)) return -1;
1315             *inc = temp2 * 1000;
1316             if(**str == '.') { // read fraction of increment
1317                 char *start = ++(*str);
1318                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1319                 temp2 *= 1000;
1320                 while(start++ < *str) temp2 /= 10;
1321                 *inc += temp2;
1322             }
1323         } else *inc = 0;
1324         *moves = 0; *tc = temp * 1000; *incType = type;
1325         return 0;
1326     }
1327
1328     (*str)++; /* classical time control */
1329     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1330
1331     if(result == 0) {
1332         *moves = temp;
1333         *tc    = temp2 * 1000;
1334         *inc   = 0;
1335         *incType = type;
1336     }
1337     return result;
1338 }
1339
1340 int
1341 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1342 {   /* [HGM] get time to add from the multi-session time-control string */
1343     int incType, moves=1; /* kludge to force reading of first session */
1344     long time, increment;
1345     char *s = tcString;
1346
1347     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1348     do {
1349         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1350         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1351         if(movenr == -1) return time;    /* last move before new session     */
1352         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1353         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1354         if(!moves) return increment;     /* current session is incremental   */
1355         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1356     } while(movenr >= -1);               /* try again for next session       */
1357
1358     return 0; // no new time quota on this move
1359 }
1360
1361 int
1362 ParseTimeControl (char *tc, float ti, int mps)
1363 {
1364   long tc1;
1365   long tc2;
1366   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1367   int min, sec=0;
1368
1369   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1370   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1371       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1372   if(ti > 0) {
1373
1374     if(mps)
1375       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1376     else
1377       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1378   } else {
1379     if(mps)
1380       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1381     else
1382       snprintf(buf, MSG_SIZ, ":%s", mytc);
1383   }
1384   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1385
1386   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1387     return FALSE;
1388   }
1389
1390   if( *tc == '/' ) {
1391     /* Parse second time control */
1392     tc++;
1393
1394     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1395       return FALSE;
1396     }
1397
1398     if( tc2 == 0 ) {
1399       return FALSE;
1400     }
1401
1402     timeControl_2 = tc2 * 1000;
1403   }
1404   else {
1405     timeControl_2 = 0;
1406   }
1407
1408   if( tc1 == 0 ) {
1409     return FALSE;
1410   }
1411
1412   timeControl = tc1 * 1000;
1413
1414   if (ti >= 0) {
1415     timeIncrement = ti * 1000;  /* convert to ms */
1416     movesPerSession = 0;
1417   } else {
1418     timeIncrement = 0;
1419     movesPerSession = mps;
1420   }
1421   return TRUE;
1422 }
1423
1424 void
1425 InitBackEnd2 ()
1426 {
1427     if (appData.debugMode) {
1428 #    ifdef __GIT_VERSION
1429       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1430 #    else
1431       fprintf(debugFP, "Version: %s\n", programVersion);
1432 #    endif
1433     }
1434     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1435
1436     set_cont_sequence(appData.wrapContSeq);
1437     if (appData.matchGames > 0) {
1438         appData.matchMode = TRUE;
1439     } else if (appData.matchMode) {
1440         appData.matchGames = 1;
1441     }
1442     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1443         appData.matchGames = appData.sameColorGames;
1444     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1445         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1446         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1447     }
1448     Reset(TRUE, FALSE);
1449     if (appData.noChessProgram || first.protocolVersion == 1) {
1450       InitBackEnd3();
1451     } else {
1452       /* kludge: allow timeout for initial "feature" commands */
1453       FreezeUI();
1454       DisplayMessage("", _("Starting chess program"));
1455       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1456     }
1457 }
1458
1459 int
1460 CalculateIndex (int index, int gameNr)
1461 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1462     int res;
1463     if(index > 0) return index; // fixed nmber
1464     if(index == 0) return 1;
1465     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1466     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1467     return res;
1468 }
1469
1470 int
1471 LoadGameOrPosition (int gameNr)
1472 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1473     if (*appData.loadGameFile != NULLCHAR) {
1474         if (!LoadGameFromFile(appData.loadGameFile,
1475                 CalculateIndex(appData.loadGameIndex, gameNr),
1476                               appData.loadGameFile, FALSE)) {
1477             DisplayFatalError(_("Bad game file"), 0, 1);
1478             return 0;
1479         }
1480     } else if (*appData.loadPositionFile != NULLCHAR) {
1481         if (!LoadPositionFromFile(appData.loadPositionFile,
1482                 CalculateIndex(appData.loadPositionIndex, gameNr),
1483                                   appData.loadPositionFile)) {
1484             DisplayFatalError(_("Bad position file"), 0, 1);
1485             return 0;
1486         }
1487     }
1488     return 1;
1489 }
1490
1491 void
1492 ReserveGame (int gameNr, char resChar)
1493 {
1494     FILE *tf = fopen(appData.tourneyFile, "r+");
1495     char *p, *q, c, buf[MSG_SIZ];
1496     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1497     safeStrCpy(buf, lastMsg, MSG_SIZ);
1498     DisplayMessage(_("Pick new game"), "");
1499     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1500     ParseArgsFromFile(tf);
1501     p = q = appData.results;
1502     if(appData.debugMode) {
1503       char *r = appData.participants;
1504       fprintf(debugFP, "results = '%s'\n", p);
1505       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1506       fprintf(debugFP, "\n");
1507     }
1508     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1509     nextGame = q - p;
1510     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1511     safeStrCpy(q, p, strlen(p) + 2);
1512     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1513     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1514     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1515         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1516         q[nextGame] = '*';
1517     }
1518     fseek(tf, -(strlen(p)+4), SEEK_END);
1519     c = fgetc(tf);
1520     if(c != '"') // depending on DOS or Unix line endings we can be one off
1521          fseek(tf, -(strlen(p)+2), SEEK_END);
1522     else fseek(tf, -(strlen(p)+3), SEEK_END);
1523     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1524     DisplayMessage(buf, "");
1525     free(p); appData.results = q;
1526     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1527        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1528       int round = appData.defaultMatchGames * appData.tourneyType;
1529       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1530          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1531         UnloadEngine(&first);  // next game belongs to other pairing;
1532         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1533     }
1534     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1535 }
1536
1537 void
1538 MatchEvent (int mode)
1539 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1540         int dummy;
1541         if(matchMode) { // already in match mode: switch it off
1542             abortMatch = TRUE;
1543             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1544             return;
1545         }
1546 //      if(gameMode != BeginningOfGame) {
1547 //          DisplayError(_("You can only start a match from the initial position."), 0);
1548 //          return;
1549 //      }
1550         abortMatch = FALSE;
1551         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1552         /* Set up machine vs. machine match */
1553         nextGame = 0;
1554         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1555         if(appData.tourneyFile[0]) {
1556             ReserveGame(-1, 0);
1557             if(nextGame > appData.matchGames) {
1558                 char buf[MSG_SIZ];
1559                 if(strchr(appData.results, '*') == NULL) {
1560                     FILE *f;
1561                     appData.tourneyCycles++;
1562                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1563                         fclose(f);
1564                         NextTourneyGame(-1, &dummy);
1565                         ReserveGame(-1, 0);
1566                         if(nextGame <= appData.matchGames) {
1567                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1568                             matchMode = mode;
1569                             ScheduleDelayedEvent(NextMatchGame, 10000);
1570                             return;
1571                         }
1572                     }
1573                 }
1574                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1575                 DisplayError(buf, 0);
1576                 appData.tourneyFile[0] = 0;
1577                 return;
1578             }
1579         } else
1580         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1581             DisplayFatalError(_("Can't have a match with no chess programs"),
1582                               0, 2);
1583             return;
1584         }
1585         matchMode = mode;
1586         matchGame = roundNr = 1;
1587         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1588         NextMatchGame();
1589 }
1590
1591 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1592
1593 void
1594 InitBackEnd3 P((void))
1595 {
1596     GameMode initialMode;
1597     char buf[MSG_SIZ];
1598     int err, len;
1599
1600     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1601        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1602         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1603        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1604        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1605         char c, *q = first.variants, *p = strchr(q, ',');
1606         if(p) *p = NULLCHAR;
1607         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1608             int w, h, s;
1609             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1610                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1611             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1612             Reset(TRUE, FALSE);         // and re-initialize
1613         }
1614         if(p) *p = ',';
1615     }
1616
1617     InitChessProgram(&first, startedFromSetupPosition);
1618
1619     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1620         free(programVersion);
1621         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1622         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1623         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1624     }
1625
1626     if (appData.icsActive) {
1627 #ifdef WIN32
1628         /* [DM] Make a console window if needed [HGM] merged ifs */
1629         ConsoleCreate();
1630 #endif
1631         err = establish();
1632         if (err != 0)
1633           {
1634             if (*appData.icsCommPort != NULLCHAR)
1635               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1636                              appData.icsCommPort);
1637             else
1638               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1639                         appData.icsHost, appData.icsPort);
1640
1641             if( (len >= MSG_SIZ) && appData.debugMode )
1642               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1643
1644             DisplayFatalError(buf, err, 1);
1645             return;
1646         }
1647         SetICSMode();
1648         telnetISR =
1649           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1650         fromUserISR =
1651           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1652         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1653             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1654     } else if (appData.noChessProgram) {
1655         SetNCPMode();
1656     } else {
1657         SetGNUMode();
1658     }
1659
1660     if (*appData.cmailGameName != NULLCHAR) {
1661         SetCmailMode();
1662         OpenLoopback(&cmailPR);
1663         cmailISR =
1664           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1665     }
1666
1667     ThawUI();
1668     DisplayMessage("", "");
1669     if (StrCaseCmp(appData.initialMode, "") == 0) {
1670       initialMode = BeginningOfGame;
1671       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1672         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1673         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1674         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1675         ModeHighlight();
1676       }
1677     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1678       initialMode = TwoMachinesPlay;
1679     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1680       initialMode = AnalyzeFile;
1681     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1682       initialMode = AnalyzeMode;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1684       initialMode = MachinePlaysWhite;
1685     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1686       initialMode = MachinePlaysBlack;
1687     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1688       initialMode = EditGame;
1689     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1690       initialMode = EditPosition;
1691     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1692       initialMode = Training;
1693     } else {
1694       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1695       if( (len >= MSG_SIZ) && appData.debugMode )
1696         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1697
1698       DisplayFatalError(buf, 0, 2);
1699       return;
1700     }
1701
1702     if (appData.matchMode) {
1703         if(appData.tourneyFile[0]) { // start tourney from command line
1704             FILE *f;
1705             if(f = fopen(appData.tourneyFile, "r")) {
1706                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1707                 fclose(f);
1708                 appData.clockMode = TRUE;
1709                 SetGNUMode();
1710             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1711         }
1712         MatchEvent(TRUE);
1713     } else if (*appData.cmailGameName != NULLCHAR) {
1714         /* Set up cmail mode */
1715         ReloadCmailMsgEvent(TRUE);
1716     } else {
1717         /* Set up other modes */
1718         if (initialMode == AnalyzeFile) {
1719           if (*appData.loadGameFile == NULLCHAR) {
1720             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1721             return;
1722           }
1723         }
1724         if (*appData.loadGameFile != NULLCHAR) {
1725             (void) LoadGameFromFile(appData.loadGameFile,
1726                                     appData.loadGameIndex,
1727                                     appData.loadGameFile, TRUE);
1728         } else if (*appData.loadPositionFile != NULLCHAR) {
1729             (void) LoadPositionFromFile(appData.loadPositionFile,
1730                                         appData.loadPositionIndex,
1731                                         appData.loadPositionFile);
1732             /* [HGM] try to make self-starting even after FEN load */
1733             /* to allow automatic setup of fairy variants with wtm */
1734             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1735                 gameMode = BeginningOfGame;
1736                 setboardSpoiledMachineBlack = 1;
1737             }
1738             /* [HGM] loadPos: make that every new game uses the setup */
1739             /* from file as long as we do not switch variant          */
1740             if(!blackPlaysFirst) {
1741                 startedFromPositionFile = TRUE;
1742                 CopyBoard(filePosition, boards[0]);
1743                 CopyBoard(initialPosition, boards[0]);
1744             }
1745         }
1746         if (initialMode == AnalyzeMode) {
1747           if (appData.noChessProgram) {
1748             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1749             return;
1750           }
1751           if (appData.icsActive) {
1752             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1753             return;
1754           }
1755           AnalyzeModeEvent();
1756         } else if (initialMode == AnalyzeFile) {
1757           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1758           ShowThinkingEvent();
1759           AnalyzeFileEvent();
1760           AnalysisPeriodicEvent(1);
1761         } else if (initialMode == MachinePlaysWhite) {
1762           if (appData.noChessProgram) {
1763             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1764                               0, 2);
1765             return;
1766           }
1767           if (appData.icsActive) {
1768             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1769                               0, 2);
1770             return;
1771           }
1772           MachineWhiteEvent();
1773         } else if (initialMode == MachinePlaysBlack) {
1774           if (appData.noChessProgram) {
1775             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1776                               0, 2);
1777             return;
1778           }
1779           if (appData.icsActive) {
1780             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1781                               0, 2);
1782             return;
1783           }
1784           MachineBlackEvent();
1785         } else if (initialMode == TwoMachinesPlay) {
1786           if (appData.noChessProgram) {
1787             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1788                               0, 2);
1789             return;
1790           }
1791           if (appData.icsActive) {
1792             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1793                               0, 2);
1794             return;
1795           }
1796           TwoMachinesEvent();
1797         } else if (initialMode == EditGame) {
1798           EditGameEvent();
1799         } else if (initialMode == EditPosition) {
1800           EditPositionEvent();
1801         } else if (initialMode == Training) {
1802           if (*appData.loadGameFile == NULLCHAR) {
1803             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1804             return;
1805           }
1806           TrainingEvent();
1807         }
1808     }
1809 }
1810
1811 void
1812 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1813 {
1814     DisplayBook(current+1);
1815
1816     MoveHistorySet( movelist, first, last, current, pvInfoList );
1817
1818     EvalGraphSet( first, last, current, pvInfoList );
1819
1820     MakeEngineOutputTitle();
1821 }
1822
1823 /*
1824  * Establish will establish a contact to a remote host.port.
1825  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1826  *  used to talk to the host.
1827  * Returns 0 if okay, error code if not.
1828  */
1829 int
1830 establish ()
1831 {
1832     char buf[MSG_SIZ];
1833
1834     if (*appData.icsCommPort != NULLCHAR) {
1835         /* Talk to the host through a serial comm port */
1836         return OpenCommPort(appData.icsCommPort, &icsPR);
1837
1838     } else if (*appData.gateway != NULLCHAR) {
1839         if (*appData.remoteShell == NULLCHAR) {
1840             /* Use the rcmd protocol to run telnet program on a gateway host */
1841             snprintf(buf, sizeof(buf), "%s %s %s",
1842                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1843             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1844
1845         } else {
1846             /* Use the rsh program to run telnet program on a gateway host */
1847             if (*appData.remoteUser == NULLCHAR) {
1848                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1849                         appData.gateway, appData.telnetProgram,
1850                         appData.icsHost, appData.icsPort);
1851             } else {
1852                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1853                         appData.remoteShell, appData.gateway,
1854                         appData.remoteUser, appData.telnetProgram,
1855                         appData.icsHost, appData.icsPort);
1856             }
1857             return StartChildProcess(buf, "", &icsPR);
1858
1859         }
1860     } else if (appData.useTelnet) {
1861         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1862
1863     } else {
1864         /* TCP socket interface differs somewhat between
1865            Unix and NT; handle details in the front end.
1866            */
1867         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1868     }
1869 }
1870
1871 void
1872 EscapeExpand (char *p, char *q)
1873 {       // [HGM] initstring: routine to shape up string arguments
1874         while(*p++ = *q++) if(p[-1] == '\\')
1875             switch(*q++) {
1876                 case 'n': p[-1] = '\n'; break;
1877                 case 'r': p[-1] = '\r'; break;
1878                 case 't': p[-1] = '\t'; break;
1879                 case '\\': p[-1] = '\\'; break;
1880                 case 0: *p = 0; return;
1881                 default: p[-1] = q[-1]; break;
1882             }
1883 }
1884
1885 void
1886 show_bytes (FILE *fp, char *buf, int count)
1887 {
1888     while (count--) {
1889         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1890             fprintf(fp, "\\%03o", *buf & 0xff);
1891         } else {
1892             putc(*buf, fp);
1893         }
1894         buf++;
1895     }
1896     fflush(fp);
1897 }
1898
1899 /* Returns an errno value */
1900 int
1901 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1902 {
1903     char buf[8192], *p, *q, *buflim;
1904     int left, newcount, outcount;
1905
1906     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1907         *appData.gateway != NULLCHAR) {
1908         if (appData.debugMode) {
1909             fprintf(debugFP, ">ICS: ");
1910             show_bytes(debugFP, message, count);
1911             fprintf(debugFP, "\n");
1912         }
1913         return OutputToProcess(pr, message, count, outError);
1914     }
1915
1916     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1917     p = message;
1918     q = buf;
1919     left = count;
1920     newcount = 0;
1921     while (left) {
1922         if (q >= buflim) {
1923             if (appData.debugMode) {
1924                 fprintf(debugFP, ">ICS: ");
1925                 show_bytes(debugFP, buf, newcount);
1926                 fprintf(debugFP, "\n");
1927             }
1928             outcount = OutputToProcess(pr, buf, newcount, outError);
1929             if (outcount < newcount) return -1; /* to be sure */
1930             q = buf;
1931             newcount = 0;
1932         }
1933         if (*p == '\n') {
1934             *q++ = '\r';
1935             newcount++;
1936         } else if (((unsigned char) *p) == TN_IAC) {
1937             *q++ = (char) TN_IAC;
1938             newcount ++;
1939         }
1940         *q++ = *p++;
1941         newcount++;
1942         left--;
1943     }
1944     if (appData.debugMode) {
1945         fprintf(debugFP, ">ICS: ");
1946         show_bytes(debugFP, buf, newcount);
1947         fprintf(debugFP, "\n");
1948     }
1949     outcount = OutputToProcess(pr, buf, newcount, outError);
1950     if (outcount < newcount) return -1; /* to be sure */
1951     return count;
1952 }
1953
1954 void
1955 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1956 {
1957     int outError, outCount;
1958     static int gotEof = 0;
1959     static FILE *ini;
1960
1961     /* Pass data read from player on to ICS */
1962     if (count > 0) {
1963         gotEof = 0;
1964         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1965         if (outCount < count) {
1966             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1967         }
1968         if(have_sent_ICS_logon == 2) {
1969           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1970             fprintf(ini, "%s", message);
1971             have_sent_ICS_logon = 3;
1972           } else
1973             have_sent_ICS_logon = 1;
1974         } else if(have_sent_ICS_logon == 3) {
1975             fprintf(ini, "%s", message);
1976             fclose(ini);
1977           have_sent_ICS_logon = 1;
1978         }
1979     } else if (count < 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1982     } else if (gotEof++ > 0) {
1983         RemoveInputSource(isr);
1984         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1985     }
1986 }
1987
1988 void
1989 KeepAlive ()
1990 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1991     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1992     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1993     SendToICS("date\n");
1994     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1995 }
1996
1997 /* added routine for printf style output to ics */
1998 void
1999 ics_printf (char *format, ...)
2000 {
2001     char buffer[MSG_SIZ];
2002     va_list args;
2003
2004     va_start(args, format);
2005     vsnprintf(buffer, sizeof(buffer), format, args);
2006     buffer[sizeof(buffer)-1] = '\0';
2007     SendToICS(buffer);
2008     va_end(args);
2009 }
2010
2011 void
2012 SendToICS (char *s)
2013 {
2014     int count, outCount, outError;
2015
2016     if (icsPR == NoProc) return;
2017
2018     count = strlen(s);
2019     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2020     if (outCount < count) {
2021         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2022     }
2023 }
2024
2025 /* This is used for sending logon scripts to the ICS. Sending
2026    without a delay causes problems when using timestamp on ICC
2027    (at least on my machine). */
2028 void
2029 SendToICSDelayed (char *s, long msdelay)
2030 {
2031     int count, outCount, outError;
2032
2033     if (icsPR == NoProc) return;
2034
2035     count = strlen(s);
2036     if (appData.debugMode) {
2037         fprintf(debugFP, ">ICS: ");
2038         show_bytes(debugFP, s, count);
2039         fprintf(debugFP, "\n");
2040     }
2041     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2042                                       msdelay);
2043     if (outCount < count) {
2044         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2045     }
2046 }
2047
2048
2049 /* Remove all highlighting escape sequences in s
2050    Also deletes any suffix starting with '('
2051    */
2052 char *
2053 StripHighlightAndTitle (char *s)
2054 {
2055     static char retbuf[MSG_SIZ];
2056     char *p = retbuf;
2057
2058     while (*s != NULLCHAR) {
2059         while (*s == '\033') {
2060             while (*s != NULLCHAR && !isalpha(*s)) s++;
2061             if (*s != NULLCHAR) s++;
2062         }
2063         while (*s != NULLCHAR && *s != '\033') {
2064             if (*s == '(' || *s == '[') {
2065                 *p = NULLCHAR;
2066                 return retbuf;
2067             }
2068             *p++ = *s++;
2069         }
2070     }
2071     *p = NULLCHAR;
2072     return retbuf;
2073 }
2074
2075 /* Remove all highlighting escape sequences in s */
2076 char *
2077 StripHighlight (char *s)
2078 {
2079     static char retbuf[MSG_SIZ];
2080     char *p = retbuf;
2081
2082     while (*s != NULLCHAR) {
2083         while (*s == '\033') {
2084             while (*s != NULLCHAR && !isalpha(*s)) s++;
2085             if (*s != NULLCHAR) s++;
2086         }
2087         while (*s != NULLCHAR && *s != '\033') {
2088             *p++ = *s++;
2089         }
2090     }
2091     *p = NULLCHAR;
2092     return retbuf;
2093 }
2094
2095 char engineVariant[MSG_SIZ];
2096 char *variantNames[] = VARIANT_NAMES;
2097 char *
2098 VariantName (VariantClass v)
2099 {
2100     if(v == VariantUnknown || *engineVariant) return engineVariant;
2101     return variantNames[v];
2102 }
2103
2104
2105 /* Identify a variant from the strings the chess servers use or the
2106    PGN Variant tag names we use. */
2107 VariantClass
2108 StringToVariant (char *e)
2109 {
2110     char *p;
2111     int wnum = -1;
2112     VariantClass v = VariantNormal;
2113     int i, found = FALSE;
2114     char buf[MSG_SIZ], c;
2115     int len;
2116
2117     if (!e) return v;
2118
2119     /* [HGM] skip over optional board-size prefixes */
2120     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2121         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2122         while( *e++ != '_');
2123     }
2124
2125     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2126         v = VariantNormal;
2127         found = TRUE;
2128     } else
2129     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2130       if (p = StrCaseStr(e, variantNames[i])) {
2131         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2132         v = (VariantClass) i;
2133         found = TRUE;
2134         break;
2135       }
2136     }
2137
2138     if (!found) {
2139       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2140           || StrCaseStr(e, "wild/fr")
2141           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2142         v = VariantFischeRandom;
2143       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2144                  (i = 1, p = StrCaseStr(e, "w"))) {
2145         p += i;
2146         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2147         if (isdigit(*p)) {
2148           wnum = atoi(p);
2149         } else {
2150           wnum = -1;
2151         }
2152         switch (wnum) {
2153         case 0: /* FICS only, actually */
2154         case 1:
2155           /* Castling legal even if K starts on d-file */
2156           v = VariantWildCastle;
2157           break;
2158         case 2:
2159         case 3:
2160         case 4:
2161           /* Castling illegal even if K & R happen to start in
2162              normal positions. */
2163           v = VariantNoCastle;
2164           break;
2165         case 5:
2166         case 7:
2167         case 8:
2168         case 10:
2169         case 11:
2170         case 12:
2171         case 13:
2172         case 14:
2173         case 15:
2174         case 18:
2175         case 19:
2176           /* Castling legal iff K & R start in normal positions */
2177           v = VariantNormal;
2178           break;
2179         case 6:
2180         case 20:
2181         case 21:
2182           /* Special wilds for position setup; unclear what to do here */
2183           v = VariantLoadable;
2184           break;
2185         case 9:
2186           /* Bizarre ICC game */
2187           v = VariantTwoKings;
2188           break;
2189         case 16:
2190           v = VariantKriegspiel;
2191           break;
2192         case 17:
2193           v = VariantLosers;
2194           break;
2195         case 22:
2196           v = VariantFischeRandom;
2197           break;
2198         case 23:
2199           v = VariantCrazyhouse;
2200           break;
2201         case 24:
2202           v = VariantBughouse;
2203           break;
2204         case 25:
2205           v = Variant3Check;
2206           break;
2207         case 26:
2208           /* Not quite the same as FICS suicide! */
2209           v = VariantGiveaway;
2210           break;
2211         case 27:
2212           v = VariantAtomic;
2213           break;
2214         case 28:
2215           v = VariantShatranj;
2216           break;
2217
2218         /* Temporary names for future ICC types.  The name *will* change in
2219            the next xboard/WinBoard release after ICC defines it. */
2220         case 29:
2221           v = Variant29;
2222           break;
2223         case 30:
2224           v = Variant30;
2225           break;
2226         case 31:
2227           v = Variant31;
2228           break;
2229         case 32:
2230           v = Variant32;
2231           break;
2232         case 33:
2233           v = Variant33;
2234           break;
2235         case 34:
2236           v = Variant34;
2237           break;
2238         case 35:
2239           v = Variant35;
2240           break;
2241         case 36:
2242           v = Variant36;
2243           break;
2244         case 37:
2245           v = VariantShogi;
2246           break;
2247         case 38:
2248           v = VariantXiangqi;
2249           break;
2250         case 39:
2251           v = VariantCourier;
2252           break;
2253         case 40:
2254           v = VariantGothic;
2255           break;
2256         case 41:
2257           v = VariantCapablanca;
2258           break;
2259         case 42:
2260           v = VariantKnightmate;
2261           break;
2262         case 43:
2263           v = VariantFairy;
2264           break;
2265         case 44:
2266           v = VariantCylinder;
2267           break;
2268         case 45:
2269           v = VariantFalcon;
2270           break;
2271         case 46:
2272           v = VariantCapaRandom;
2273           break;
2274         case 47:
2275           v = VariantBerolina;
2276           break;
2277         case 48:
2278           v = VariantJanus;
2279           break;
2280         case 49:
2281           v = VariantSuper;
2282           break;
2283         case 50:
2284           v = VariantGreat;
2285           break;
2286         case -1:
2287           /* Found "wild" or "w" in the string but no number;
2288              must assume it's normal chess. */
2289           v = VariantNormal;
2290           break;
2291         default:
2292           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2293           if( (len >= MSG_SIZ) && appData.debugMode )
2294             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2295
2296           DisplayError(buf, 0);
2297           v = VariantUnknown;
2298           break;
2299         }
2300       }
2301     }
2302     if (appData.debugMode) {
2303       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2304               e, wnum, VariantName(v));
2305     }
2306     return v;
2307 }
2308
2309 static int leftover_start = 0, leftover_len = 0;
2310 char star_match[STAR_MATCH_N][MSG_SIZ];
2311
2312 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2313    advance *index beyond it, and set leftover_start to the new value of
2314    *index; else return FALSE.  If pattern contains the character '*', it
2315    matches any sequence of characters not containing '\r', '\n', or the
2316    character following the '*' (if any), and the matched sequence(s) are
2317    copied into star_match.
2318    */
2319 int
2320 looking_at ( char *buf, int *index, char *pattern)
2321 {
2322     char *bufp = &buf[*index], *patternp = pattern;
2323     int star_count = 0;
2324     char *matchp = star_match[0];
2325
2326     for (;;) {
2327         if (*patternp == NULLCHAR) {
2328             *index = leftover_start = bufp - buf;
2329             *matchp = NULLCHAR;
2330             return TRUE;
2331         }
2332         if (*bufp == NULLCHAR) return FALSE;
2333         if (*patternp == '*') {
2334             if (*bufp == *(patternp + 1)) {
2335                 *matchp = NULLCHAR;
2336                 matchp = star_match[++star_count];
2337                 patternp += 2;
2338                 bufp++;
2339                 continue;
2340             } else if (*bufp == '\n' || *bufp == '\r') {
2341                 patternp++;
2342                 if (*patternp == NULLCHAR)
2343                   continue;
2344                 else
2345                   return FALSE;
2346             } else {
2347                 *matchp++ = *bufp++;
2348                 continue;
2349             }
2350         }
2351         if (*patternp != *bufp) return FALSE;
2352         patternp++;
2353         bufp++;
2354     }
2355 }
2356
2357 void
2358 SendToPlayer (char *data, int length)
2359 {
2360     int error, outCount;
2361     outCount = OutputToProcess(NoProc, data, length, &error);
2362     if (outCount < length) {
2363         DisplayFatalError(_("Error writing to display"), error, 1);
2364     }
2365 }
2366
2367 void
2368 PackHolding (char packed[], char *holding)
2369 {
2370     char *p = holding;
2371     char *q = packed;
2372     int runlength = 0;
2373     int curr = 9999;
2374     do {
2375         if (*p == curr) {
2376             runlength++;
2377         } else {
2378             switch (runlength) {
2379               case 0:
2380                 break;
2381               case 1:
2382                 *q++ = curr;
2383                 break;
2384               case 2:
2385                 *q++ = curr;
2386                 *q++ = curr;
2387                 break;
2388               default:
2389                 sprintf(q, "%d", runlength);
2390                 while (*q) q++;
2391                 *q++ = curr;
2392                 break;
2393             }
2394             runlength = 1;
2395             curr = *p;
2396         }
2397     } while (*p++);
2398     *q = NULLCHAR;
2399 }
2400
2401 /* Telnet protocol requests from the front end */
2402 void
2403 TelnetRequest (unsigned char ddww, unsigned char option)
2404 {
2405     unsigned char msg[3];
2406     int outCount, outError;
2407
2408     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2409
2410     if (appData.debugMode) {
2411         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2412         switch (ddww) {
2413           case TN_DO:
2414             ddwwStr = "DO";
2415             break;
2416           case TN_DONT:
2417             ddwwStr = "DONT";
2418             break;
2419           case TN_WILL:
2420             ddwwStr = "WILL";
2421             break;
2422           case TN_WONT:
2423             ddwwStr = "WONT";
2424             break;
2425           default:
2426             ddwwStr = buf1;
2427             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2428             break;
2429         }
2430         switch (option) {
2431           case TN_ECHO:
2432             optionStr = "ECHO";
2433             break;
2434           default:
2435             optionStr = buf2;
2436             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2437             break;
2438         }
2439         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2440     }
2441     msg[0] = TN_IAC;
2442     msg[1] = ddww;
2443     msg[2] = option;
2444     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2445     if (outCount < 3) {
2446         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2447     }
2448 }
2449
2450 void
2451 DoEcho ()
2452 {
2453     if (!appData.icsActive) return;
2454     TelnetRequest(TN_DO, TN_ECHO);
2455 }
2456
2457 void
2458 DontEcho ()
2459 {
2460     if (!appData.icsActive) return;
2461     TelnetRequest(TN_DONT, TN_ECHO);
2462 }
2463
2464 void
2465 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2466 {
2467     /* put the holdings sent to us by the server on the board holdings area */
2468     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2469     char p;
2470     ChessSquare piece;
2471
2472     if(gameInfo.holdingsWidth < 2)  return;
2473     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2474         return; // prevent overwriting by pre-board holdings
2475
2476     if( (int)lowestPiece >= BlackPawn ) {
2477         holdingsColumn = 0;
2478         countsColumn = 1;
2479         holdingsStartRow = BOARD_HEIGHT-1;
2480         direction = -1;
2481     } else {
2482         holdingsColumn = BOARD_WIDTH-1;
2483         countsColumn = BOARD_WIDTH-2;
2484         holdingsStartRow = 0;
2485         direction = 1;
2486     }
2487
2488     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2489         board[i][holdingsColumn] = EmptySquare;
2490         board[i][countsColumn]   = (ChessSquare) 0;
2491     }
2492     while( (p=*holdings++) != NULLCHAR ) {
2493         piece = CharToPiece( ToUpper(p) );
2494         if(piece == EmptySquare) continue;
2495         /*j = (int) piece - (int) WhitePawn;*/
2496         j = PieceToNumber(piece);
2497         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2498         if(j < 0) continue;               /* should not happen */
2499         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2500         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2501         board[holdingsStartRow+j*direction][countsColumn]++;
2502     }
2503 }
2504
2505
2506 void
2507 VariantSwitch (Board board, VariantClass newVariant)
2508 {
2509    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2510    static Board oldBoard;
2511
2512    startedFromPositionFile = FALSE;
2513    if(gameInfo.variant == newVariant) return;
2514
2515    /* [HGM] This routine is called each time an assignment is made to
2516     * gameInfo.variant during a game, to make sure the board sizes
2517     * are set to match the new variant. If that means adding or deleting
2518     * holdings, we shift the playing board accordingly
2519     * This kludge is needed because in ICS observe mode, we get boards
2520     * of an ongoing game without knowing the variant, and learn about the
2521     * latter only later. This can be because of the move list we requested,
2522     * in which case the game history is refilled from the beginning anyway,
2523     * but also when receiving holdings of a crazyhouse game. In the latter
2524     * case we want to add those holdings to the already received position.
2525     */
2526
2527
2528    if (appData.debugMode) {
2529      fprintf(debugFP, "Switch board from %s to %s\n",
2530              VariantName(gameInfo.variant), VariantName(newVariant));
2531      setbuf(debugFP, NULL);
2532    }
2533    shuffleOpenings = 0;       /* [HGM] shuffle */
2534    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2535    switch(newVariant)
2536      {
2537      case VariantShogi:
2538        newWidth = 9;  newHeight = 9;
2539        gameInfo.holdingsSize = 7;
2540      case VariantBughouse:
2541      case VariantCrazyhouse:
2542        newHoldingsWidth = 2; break;
2543      case VariantGreat:
2544        newWidth = 10;
2545      case VariantSuper:
2546        newHoldingsWidth = 2;
2547        gameInfo.holdingsSize = 8;
2548        break;
2549      case VariantGothic:
2550      case VariantCapablanca:
2551      case VariantCapaRandom:
2552        newWidth = 10;
2553      default:
2554        newHoldingsWidth = gameInfo.holdingsSize = 0;
2555      };
2556
2557    if(newWidth  != gameInfo.boardWidth  ||
2558       newHeight != gameInfo.boardHeight ||
2559       newHoldingsWidth != gameInfo.holdingsWidth ) {
2560
2561      /* shift position to new playing area, if needed */
2562      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2563        for(i=0; i<BOARD_HEIGHT; i++)
2564          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2565            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2566              board[i][j];
2567        for(i=0; i<newHeight; i++) {
2568          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2569          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2570        }
2571      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2572        for(i=0; i<BOARD_HEIGHT; i++)
2573          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2574            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2575              board[i][j];
2576      }
2577      board[HOLDINGS_SET] = 0;
2578      gameInfo.boardWidth  = newWidth;
2579      gameInfo.boardHeight = newHeight;
2580      gameInfo.holdingsWidth = newHoldingsWidth;
2581      gameInfo.variant = newVariant;
2582      InitDrawingSizes(-2, 0);
2583    } else gameInfo.variant = newVariant;
2584    CopyBoard(oldBoard, board);   // remember correctly formatted board
2585      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2586    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2587 }
2588
2589 static int loggedOn = FALSE;
2590
2591 /*-- Game start info cache: --*/
2592 int gs_gamenum;
2593 char gs_kind[MSG_SIZ];
2594 static char player1Name[128] = "";
2595 static char player2Name[128] = "";
2596 static char cont_seq[] = "\n\\   ";
2597 static int player1Rating = -1;
2598 static int player2Rating = -1;
2599 /*----------------------------*/
2600
2601 ColorClass curColor = ColorNormal;
2602 int suppressKibitz = 0;
2603
2604 // [HGM] seekgraph
2605 Boolean soughtPending = FALSE;
2606 Boolean seekGraphUp;
2607 #define MAX_SEEK_ADS 200
2608 #define SQUARE 0x80
2609 char *seekAdList[MAX_SEEK_ADS];
2610 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2611 float tcList[MAX_SEEK_ADS];
2612 char colorList[MAX_SEEK_ADS];
2613 int nrOfSeekAds = 0;
2614 int minRating = 1010, maxRating = 2800;
2615 int hMargin = 10, vMargin = 20, h, w;
2616 extern int squareSize, lineGap;
2617
2618 void
2619 PlotSeekAd (int i)
2620 {
2621         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2622         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2623         if(r < minRating+100 && r >=0 ) r = minRating+100;
2624         if(r > maxRating) r = maxRating;
2625         if(tc < 1.f) tc = 1.f;
2626         if(tc > 95.f) tc = 95.f;
2627         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2628         y = ((double)r - minRating)/(maxRating - minRating)
2629             * (h-vMargin-squareSize/8-1) + vMargin;
2630         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2631         if(strstr(seekAdList[i], " u ")) color = 1;
2632         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2633            !strstr(seekAdList[i], "bullet") &&
2634            !strstr(seekAdList[i], "blitz") &&
2635            !strstr(seekAdList[i], "standard") ) color = 2;
2636         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2637         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2638 }
2639
2640 void
2641 PlotSingleSeekAd (int i)
2642 {
2643         PlotSeekAd(i);
2644 }
2645
2646 void
2647 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2648 {
2649         char buf[MSG_SIZ], *ext = "";
2650         VariantClass v = StringToVariant(type);
2651         if(strstr(type, "wild")) {
2652             ext = type + 4; // append wild number
2653             if(v == VariantFischeRandom) type = "chess960"; else
2654             if(v == VariantLoadable) type = "setup"; else
2655             type = VariantName(v);
2656         }
2657         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2658         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2659             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2660             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2661             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2662             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2663             seekNrList[nrOfSeekAds] = nr;
2664             zList[nrOfSeekAds] = 0;
2665             seekAdList[nrOfSeekAds++] = StrSave(buf);
2666             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2667         }
2668 }
2669
2670 void
2671 EraseSeekDot (int i)
2672 {
2673     int x = xList[i], y = yList[i], d=squareSize/4, k;
2674     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2675     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2676     // now replot every dot that overlapped
2677     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2678         int xx = xList[k], yy = yList[k];
2679         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2680             DrawSeekDot(xx, yy, colorList[k]);
2681     }
2682 }
2683
2684 void
2685 RemoveSeekAd (int nr)
2686 {
2687         int i;
2688         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2689             EraseSeekDot(i);
2690             if(seekAdList[i]) free(seekAdList[i]);
2691             seekAdList[i] = seekAdList[--nrOfSeekAds];
2692             seekNrList[i] = seekNrList[nrOfSeekAds];
2693             ratingList[i] = ratingList[nrOfSeekAds];
2694             colorList[i]  = colorList[nrOfSeekAds];
2695             tcList[i] = tcList[nrOfSeekAds];
2696             xList[i]  = xList[nrOfSeekAds];
2697             yList[i]  = yList[nrOfSeekAds];
2698             zList[i]  = zList[nrOfSeekAds];
2699             seekAdList[nrOfSeekAds] = NULL;
2700             break;
2701         }
2702 }
2703
2704 Boolean
2705 MatchSoughtLine (char *line)
2706 {
2707     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2708     int nr, base, inc, u=0; char dummy;
2709
2710     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2712        (u=1) &&
2713        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2714         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2715         // match: compact and save the line
2716         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2717         return TRUE;
2718     }
2719     return FALSE;
2720 }
2721
2722 int
2723 DrawSeekGraph ()
2724 {
2725     int i;
2726     if(!seekGraphUp) return FALSE;
2727     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2728     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2729
2730     DrawSeekBackground(0, 0, w, h);
2731     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2732     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2733     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2734         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2735         yy = h-1-yy;
2736         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2737         if(i%500 == 0) {
2738             char buf[MSG_SIZ];
2739             snprintf(buf, MSG_SIZ, "%d", i);
2740             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2741         }
2742     }
2743     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2744     for(i=1; i<100; i+=(i<10?1:5)) {
2745         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2746         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2747         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2748             char buf[MSG_SIZ];
2749             snprintf(buf, MSG_SIZ, "%d", i);
2750             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2751         }
2752     }
2753     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2754     return TRUE;
2755 }
2756
2757 int
2758 SeekGraphClick (ClickType click, int x, int y, int moving)
2759 {
2760     static int lastDown = 0, displayed = 0, lastSecond;
2761     if(y < 0) return FALSE;
2762     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2763         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2764         if(!seekGraphUp) return FALSE;
2765         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2766         DrawPosition(TRUE, NULL);
2767         return TRUE;
2768     }
2769     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2770         if(click == Release || moving) return FALSE;
2771         nrOfSeekAds = 0;
2772         soughtPending = TRUE;
2773         SendToICS(ics_prefix);
2774         SendToICS("sought\n"); // should this be "sought all"?
2775     } else { // issue challenge based on clicked ad
2776         int dist = 10000; int i, closest = 0, second = 0;
2777         for(i=0; i<nrOfSeekAds; i++) {
2778             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2779             if(d < dist) { dist = d; closest = i; }
2780             second += (d - zList[i] < 120); // count in-range ads
2781             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2782         }
2783         if(dist < 120) {
2784             char buf[MSG_SIZ];
2785             second = (second > 1);
2786             if(displayed != closest || second != lastSecond) {
2787                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2788                 lastSecond = second; displayed = closest;
2789             }
2790             if(click == Press) {
2791                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2792                 lastDown = closest;
2793                 return TRUE;
2794             } // on press 'hit', only show info
2795             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2796             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2797             SendToICS(ics_prefix);
2798             SendToICS(buf);
2799             return TRUE; // let incoming board of started game pop down the graph
2800         } else if(click == Release) { // release 'miss' is ignored
2801             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2802             if(moving == 2) { // right up-click
2803                 nrOfSeekAds = 0; // refresh graph
2804                 soughtPending = TRUE;
2805                 SendToICS(ics_prefix);
2806                 SendToICS("sought\n"); // should this be "sought all"?
2807             }
2808             return TRUE;
2809         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2810         // press miss or release hit 'pop down' seek graph
2811         seekGraphUp = FALSE;
2812         DrawPosition(TRUE, NULL);
2813     }
2814     return TRUE;
2815 }
2816
2817 void
2818 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2819 {
2820 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2821 #define STARTED_NONE 0
2822 #define STARTED_MOVES 1
2823 #define STARTED_BOARD 2
2824 #define STARTED_OBSERVE 3
2825 #define STARTED_HOLDINGS 4
2826 #define STARTED_CHATTER 5
2827 #define STARTED_COMMENT 6
2828 #define STARTED_MOVES_NOHIDE 7
2829
2830     static int started = STARTED_NONE;
2831     static char parse[20000];
2832     static int parse_pos = 0;
2833     static char buf[BUF_SIZE + 1];
2834     static int firstTime = TRUE, intfSet = FALSE;
2835     static ColorClass prevColor = ColorNormal;
2836     static int savingComment = FALSE;
2837     static int cmatch = 0; // continuation sequence match
2838     char *bp;
2839     char str[MSG_SIZ];
2840     int i, oldi;
2841     int buf_len;
2842     int next_out;
2843     int tkind;
2844     int backup;    /* [DM] For zippy color lines */
2845     char *p;
2846     char talker[MSG_SIZ]; // [HGM] chat
2847     int channel, collective=0;
2848
2849     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2850
2851     if (appData.debugMode) {
2852       if (!error) {
2853         fprintf(debugFP, "<ICS: ");
2854         show_bytes(debugFP, data, count);
2855         fprintf(debugFP, "\n");
2856       }
2857     }
2858
2859     if (appData.debugMode) { int f = forwardMostMove;
2860         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2861                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2862                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2863     }
2864     if (count > 0) {
2865         /* If last read ended with a partial line that we couldn't parse,
2866            prepend it to the new read and try again. */
2867         if (leftover_len > 0) {
2868             for (i=0; i<leftover_len; i++)
2869               buf[i] = buf[leftover_start + i];
2870         }
2871
2872     /* copy new characters into the buffer */
2873     bp = buf + leftover_len;
2874     buf_len=leftover_len;
2875     for (i=0; i<count; i++)
2876     {
2877         // ignore these
2878         if (data[i] == '\r')
2879             continue;
2880
2881         // join lines split by ICS?
2882         if (!appData.noJoin)
2883         {
2884             /*
2885                 Joining just consists of finding matches against the
2886                 continuation sequence, and discarding that sequence
2887                 if found instead of copying it.  So, until a match
2888                 fails, there's nothing to do since it might be the
2889                 complete sequence, and thus, something we don't want
2890                 copied.
2891             */
2892             if (data[i] == cont_seq[cmatch])
2893             {
2894                 cmatch++;
2895                 if (cmatch == strlen(cont_seq))
2896                 {
2897                     cmatch = 0; // complete match.  just reset the counter
2898
2899                     /*
2900                         it's possible for the ICS to not include the space
2901                         at the end of the last word, making our [correct]
2902                         join operation fuse two separate words.  the server
2903                         does this when the space occurs at the width setting.
2904                     */
2905                     if (!buf_len || buf[buf_len-1] != ' ')
2906                     {
2907                         *bp++ = ' ';
2908                         buf_len++;
2909                     }
2910                 }
2911                 continue;
2912             }
2913             else if (cmatch)
2914             {
2915                 /*
2916                     match failed, so we have to copy what matched before
2917                     falling through and copying this character.  In reality,
2918                     this will only ever be just the newline character, but
2919                     it doesn't hurt to be precise.
2920                 */
2921                 strncpy(bp, cont_seq, cmatch);
2922                 bp += cmatch;
2923                 buf_len += cmatch;
2924                 cmatch = 0;
2925             }
2926         }
2927
2928         // copy this char
2929         *bp++ = data[i];
2930         buf_len++;
2931     }
2932
2933         buf[buf_len] = NULLCHAR;
2934 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2935         next_out = 0;
2936         leftover_start = 0;
2937
2938         i = 0;
2939         while (i < buf_len) {
2940             /* Deal with part of the TELNET option negotiation
2941                protocol.  We refuse to do anything beyond the
2942                defaults, except that we allow the WILL ECHO option,
2943                which ICS uses to turn off password echoing when we are
2944                directly connected to it.  We reject this option
2945                if localLineEditing mode is on (always on in xboard)
2946                and we are talking to port 23, which might be a real
2947                telnet server that will try to keep WILL ECHO on permanently.
2948              */
2949             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2950                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2951                 unsigned char option;
2952                 oldi = i;
2953                 switch ((unsigned char) buf[++i]) {
2954                   case TN_WILL:
2955                     if (appData.debugMode)
2956                       fprintf(debugFP, "\n<WILL ");
2957                     switch (option = (unsigned char) buf[++i]) {
2958                       case TN_ECHO:
2959                         if (appData.debugMode)
2960                           fprintf(debugFP, "ECHO ");
2961                         /* Reply only if this is a change, according
2962                            to the protocol rules. */
2963                         if (remoteEchoOption) break;
2964                         if (appData.localLineEditing &&
2965                             atoi(appData.icsPort) == TN_PORT) {
2966                             TelnetRequest(TN_DONT, TN_ECHO);
2967                         } else {
2968                             EchoOff();
2969                             TelnetRequest(TN_DO, TN_ECHO);
2970                             remoteEchoOption = TRUE;
2971                         }
2972                         break;
2973                       default:
2974                         if (appData.debugMode)
2975                           fprintf(debugFP, "%d ", option);
2976                         /* Whatever this is, we don't want it. */
2977                         TelnetRequest(TN_DONT, option);
2978                         break;
2979                     }
2980                     break;
2981                   case TN_WONT:
2982                     if (appData.debugMode)
2983                       fprintf(debugFP, "\n<WONT ");
2984                     switch (option = (unsigned char) buf[++i]) {
2985                       case TN_ECHO:
2986                         if (appData.debugMode)
2987                           fprintf(debugFP, "ECHO ");
2988                         /* Reply only if this is a change, according
2989                            to the protocol rules. */
2990                         if (!remoteEchoOption) break;
2991                         EchoOn();
2992                         TelnetRequest(TN_DONT, TN_ECHO);
2993                         remoteEchoOption = FALSE;
2994                         break;
2995                       default:
2996                         if (appData.debugMode)
2997                           fprintf(debugFP, "%d ", (unsigned char) option);
2998                         /* Whatever this is, it must already be turned
2999                            off, because we never agree to turn on
3000                            anything non-default, so according to the
3001                            protocol rules, we don't reply. */
3002                         break;
3003                     }
3004                     break;
3005                   case TN_DO:
3006                     if (appData.debugMode)
3007                       fprintf(debugFP, "\n<DO ");
3008                     switch (option = (unsigned char) buf[++i]) {
3009                       default:
3010                         /* Whatever this is, we refuse to do it. */
3011                         if (appData.debugMode)
3012                           fprintf(debugFP, "%d ", option);
3013                         TelnetRequest(TN_WONT, option);
3014                         break;
3015                     }
3016                     break;
3017                   case TN_DONT:
3018                     if (appData.debugMode)
3019                       fprintf(debugFP, "\n<DONT ");
3020                     switch (option = (unsigned char) buf[++i]) {
3021                       default:
3022                         if (appData.debugMode)
3023                           fprintf(debugFP, "%d ", option);
3024                         /* Whatever this is, we are already not doing
3025                            it, because we never agree to do anything
3026                            non-default, so according to the protocol
3027                            rules, we don't reply. */
3028                         break;
3029                     }
3030                     break;
3031                   case TN_IAC:
3032                     if (appData.debugMode)
3033                       fprintf(debugFP, "\n<IAC ");
3034                     /* Doubled IAC; pass it through */
3035                     i--;
3036                     break;
3037                   default:
3038                     if (appData.debugMode)
3039                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3040                     /* Drop all other telnet commands on the floor */
3041                     break;
3042                 }
3043                 if (oldi > next_out)
3044                   SendToPlayer(&buf[next_out], oldi - next_out);
3045                 if (++i > next_out)
3046                   next_out = i;
3047                 continue;
3048             }
3049
3050             /* OK, this at least will *usually* work */
3051             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3052                 loggedOn = TRUE;
3053             }
3054
3055             if (loggedOn && !intfSet) {
3056                 if (ics_type == ICS_ICC) {
3057                   snprintf(str, MSG_SIZ,
3058                           "/set-quietly interface %s\n/set-quietly style 12\n",
3059                           programVersion);
3060                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3061                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3062                 } else if (ics_type == ICS_CHESSNET) {
3063                   snprintf(str, MSG_SIZ, "/style 12\n");
3064                 } else {
3065                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3066                   strcat(str, programVersion);
3067                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3068                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3069                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3070 #ifdef WIN32
3071                   strcat(str, "$iset nohighlight 1\n");
3072 #endif
3073                   strcat(str, "$iset lock 1\n$style 12\n");
3074                 }
3075                 SendToICS(str);
3076                 NotifyFrontendLogin();
3077                 intfSet = TRUE;
3078             }
3079
3080             if (started == STARTED_COMMENT) {
3081                 /* Accumulate characters in comment */
3082                 parse[parse_pos++] = buf[i];
3083                 if (buf[i] == '\n') {
3084                     parse[parse_pos] = NULLCHAR;
3085                     if(chattingPartner>=0) {
3086                         char mess[MSG_SIZ];
3087                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3088                         OutputChatMessage(chattingPartner, mess);
3089                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3090                             int p;
3091                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3092                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3093                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3094                                 OutputChatMessage(p, mess);
3095                                 break;
3096                             }
3097                         }
3098                         chattingPartner = -1;
3099                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3100                         collective = 0;
3101                     } else
3102                     if(!suppressKibitz) // [HGM] kibitz
3103                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3104                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3105                         int nrDigit = 0, nrAlph = 0, j;
3106                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3107                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3108                         parse[parse_pos] = NULLCHAR;
3109                         // try to be smart: if it does not look like search info, it should go to
3110                         // ICS interaction window after all, not to engine-output window.
3111                         for(j=0; j<parse_pos; j++) { // count letters and digits
3112                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3113                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3114                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3115                         }
3116                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3117                             int depth=0; float score;
3118                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3119                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3120                                 pvInfoList[forwardMostMove-1].depth = depth;
3121                                 pvInfoList[forwardMostMove-1].score = 100*score;
3122                             }
3123                             OutputKibitz(suppressKibitz, parse);
3124                         } else {
3125                             char tmp[MSG_SIZ];
3126                             if(gameMode == IcsObserving) // restore original ICS messages
3127                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3129                             else
3130                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3131                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3132                             SendToPlayer(tmp, strlen(tmp));
3133                         }
3134                         next_out = i+1; // [HGM] suppress printing in ICS window
3135                     }
3136                     started = STARTED_NONE;
3137                 } else {
3138                     /* Don't match patterns against characters in comment */
3139                     i++;
3140                     continue;
3141                 }
3142             }
3143             if (started == STARTED_CHATTER) {
3144                 if (buf[i] != '\n') {
3145                     /* Don't match patterns against characters in chatter */
3146                     i++;
3147                     continue;
3148                 }
3149                 started = STARTED_NONE;
3150                 if(suppressKibitz) next_out = i+1;
3151             }
3152
3153             /* Kludge to deal with rcmd protocol */
3154             if (firstTime && looking_at(buf, &i, "\001*")) {
3155                 DisplayFatalError(&buf[1], 0, 1);
3156                 continue;
3157             } else {
3158                 firstTime = FALSE;
3159             }
3160
3161             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3162                 ics_type = ICS_ICC;
3163                 ics_prefix = "/";
3164                 if (appData.debugMode)
3165                   fprintf(debugFP, "ics_type %d\n", ics_type);
3166                 continue;
3167             }
3168             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3169                 ics_type = ICS_FICS;
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, "chess.net")) {
3176                 ics_type = ICS_CHESSNET;
3177                 ics_prefix = "/";
3178                 if (appData.debugMode)
3179                   fprintf(debugFP, "ics_type %d\n", ics_type);
3180                 continue;
3181             }
3182
3183             if (!loggedOn &&
3184                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3185                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3186                  looking_at(buf, &i, "will be \"*\""))) {
3187               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3188               continue;
3189             }
3190
3191             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3192               char buf[MSG_SIZ];
3193               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3194               DisplayIcsInteractionTitle(buf);
3195               have_set_title = TRUE;
3196             }
3197
3198             /* skip finger notes */
3199             if (started == STARTED_NONE &&
3200                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3201                  (buf[i] == '1' && buf[i+1] == '0')) &&
3202                 buf[i+2] == ':' && buf[i+3] == ' ') {
3203               started = STARTED_CHATTER;
3204               i += 3;
3205               continue;
3206             }
3207
3208             oldi = i;
3209             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3210             if(appData.seekGraph) {
3211                 if(soughtPending && MatchSoughtLine(buf+i)) {
3212                     i = strstr(buf+i, "rated") - buf;
3213                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3214                     next_out = leftover_start = i;
3215                     started = STARTED_CHATTER;
3216                     suppressKibitz = TRUE;
3217                     continue;
3218                 }
3219                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3220                         && looking_at(buf, &i, "* ads displayed")) {
3221                     soughtPending = FALSE;
3222                     seekGraphUp = TRUE;
3223                     DrawSeekGraph();
3224                     continue;
3225                 }
3226                 if(appData.autoRefresh) {
3227                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3228                         int s = (ics_type == ICS_ICC); // ICC format differs
3229                         if(seekGraphUp)
3230                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3231                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3232                         looking_at(buf, &i, "*% "); // eat prompt
3233                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3234                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3235                         next_out = i; // suppress
3236                         continue;
3237                     }
3238                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3239                         char *p = star_match[0];
3240                         while(*p) {
3241                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3242                             while(*p && *p++ != ' '); // next
3243                         }
3244                         looking_at(buf, &i, "*% "); // eat prompt
3245                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3246                         next_out = i;
3247                         continue;
3248                     }
3249                 }
3250             }
3251
3252             /* skip formula vars */
3253             if (started == STARTED_NONE &&
3254                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3255               started = STARTED_CHATTER;
3256               i += 3;
3257               continue;
3258             }
3259
3260             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3261             if (appData.autoKibitz && started == STARTED_NONE &&
3262                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3263                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3264                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3265                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3266                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3267                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3268                         suppressKibitz = TRUE;
3269                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3270                         next_out = i;
3271                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3272                                 && (gameMode == IcsPlayingWhite)) ||
3273                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3274                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3275                             started = STARTED_CHATTER; // own kibitz we simply discard
3276                         else {
3277                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3278                             parse_pos = 0; parse[0] = NULLCHAR;
3279                             savingComment = TRUE;
3280                             suppressKibitz = gameMode != IcsObserving ? 2 :
3281                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3282                         }
3283                         continue;
3284                 } else
3285                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3286                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3287                          && atoi(star_match[0])) {
3288                     // suppress the acknowledgements of our own autoKibitz
3289                     char *p;
3290                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3291                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3292                     SendToPlayer(star_match[0], strlen(star_match[0]));
3293                     if(looking_at(buf, &i, "*% ")) // eat prompt
3294                         suppressKibitz = FALSE;
3295                     next_out = i;
3296                     continue;
3297                 }
3298             } // [HGM] kibitz: end of patch
3299
3300             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3301
3302             // [HGM] chat: intercept tells by users for which we have an open chat window
3303             channel = -1;
3304             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3305                                            looking_at(buf, &i, "* whispers:") ||
3306                                            looking_at(buf, &i, "* kibitzes:") ||
3307                                            looking_at(buf, &i, "* shouts:") ||
3308                                            looking_at(buf, &i, "* c-shouts:") ||
3309                                            looking_at(buf, &i, "--> * ") ||
3310                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3311                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3314                 int p;
3315                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3316                 chattingPartner = -1; collective = 0;
3317
3318                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3319                 for(p=0; p<MAX_CHAT; p++) {
3320                     collective = 1;
3321                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3322                     talker[0] = '['; strcat(talker, "] ");
3323                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3324                     chattingPartner = p; break;
3325                     }
3326                 } else
3327                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3328                 for(p=0; p<MAX_CHAT; p++) {
3329                     collective = 1;
3330                     if(!strcmp("kibitzes", chatPartner[p])) {
3331                         talker[0] = '['; strcat(talker, "] ");
3332                         chattingPartner = p; break;
3333                     }
3334                 } else
3335                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3336                 for(p=0; p<MAX_CHAT; p++) {
3337                     collective = 1;
3338                     if(!strcmp("whispers", chatPartner[p])) {
3339                         talker[0] = '['; strcat(talker, "] ");
3340                         chattingPartner = p; break;
3341                     }
3342                 } else
3343                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3344                   if(buf[i-8] == '-' && buf[i-3] == 't')
3345                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3346                     collective = 1;
3347                     if(!strcmp("c-shouts", chatPartner[p])) {
3348                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3349                         chattingPartner = p; break;
3350                     }
3351                   }
3352                   if(chattingPartner < 0)
3353                   for(p=0; p<MAX_CHAT; p++) {
3354                     collective = 1;
3355                     if(!strcmp("shouts", chatPartner[p])) {
3356                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3357                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3358                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3359                         chattingPartner = p; break;
3360                     }
3361                   }
3362                 }
3363                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3364                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3365                     talker[0] = 0;
3366                     Colorize(ColorTell, FALSE);
3367                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3368                     collective |= 2;
3369                     chattingPartner = p; break;
3370                 }
3371                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3372                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3373                     started = STARTED_COMMENT;
3374                     parse_pos = 0; parse[0] = NULLCHAR;
3375                     savingComment = 3 + chattingPartner; // counts as TRUE
3376                     if(collective == 3) i = oldi; else {
3377                         suppressKibitz = TRUE;
3378                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3379                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3380                         continue;
3381                     }
3382                 }
3383             } // [HGM] chat: end of patch
3384
3385           backup = i;
3386             if (appData.zippyTalk || appData.zippyPlay) {
3387                 /* [DM] Backup address for color zippy lines */
3388 #if ZIPPY
3389                if (loggedOn == TRUE)
3390                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3391                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3392 #endif
3393             } // [DM] 'else { ' deleted
3394                 if (
3395                     /* Regular tells and says */
3396                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3397                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3398                     looking_at(buf, &i, "* says: ") ||
3399                     /* Don't color "message" or "messages" output */
3400                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3401                     looking_at(buf, &i, "*. * at *:*: ") ||
3402                     looking_at(buf, &i, "--* (*:*): ") ||
3403                     /* Message notifications (same color as tells) */
3404                     looking_at(buf, &i, "* has left a message ") ||
3405                     looking_at(buf, &i, "* just sent you a message:\n") ||
3406                     /* Whispers and kibitzes */
3407                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3408                     looking_at(buf, &i, "* kibitzes: ") ||
3409                     /* Channel tells */
3410                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3411
3412                   if (tkind == 1 && strchr(star_match[0], ':')) {
3413                       /* Avoid "tells you:" spoofs in channels */
3414                      tkind = 3;
3415                   }
3416                   if (star_match[0][0] == NULLCHAR ||
3417                       strchr(star_match[0], ' ') ||
3418                       (tkind == 3 && strchr(star_match[1], ' '))) {
3419                     /* Reject bogus matches */
3420                     i = oldi;
3421                   } else {
3422                     if (appData.colorize) {
3423                       if (oldi > next_out) {
3424                         SendToPlayer(&buf[next_out], oldi - next_out);
3425                         next_out = oldi;
3426                       }
3427                       switch (tkind) {
3428                       case 1:
3429                         Colorize(ColorTell, FALSE);
3430                         curColor = ColorTell;
3431                         break;
3432                       case 2:
3433                         Colorize(ColorKibitz, FALSE);
3434                         curColor = ColorKibitz;
3435                         break;
3436                       case 3:
3437                         p = strrchr(star_match[1], '(');
3438                         if (p == NULL) {
3439                           p = star_match[1];
3440                         } else {
3441                           p++;
3442                         }
3443                         if (atoi(p) == 1) {
3444                           Colorize(ColorChannel1, FALSE);
3445                           curColor = ColorChannel1;
3446                         } else {
3447                           Colorize(ColorChannel, FALSE);
3448                           curColor = ColorChannel;
3449                         }
3450                         break;
3451                       case 5:
3452                         curColor = ColorNormal;
3453                         break;
3454                       }
3455                     }
3456                     if (started == STARTED_NONE && appData.autoComment &&
3457                         (gameMode == IcsObserving ||
3458                          gameMode == IcsPlayingWhite ||
3459                          gameMode == IcsPlayingBlack)) {
3460                       parse_pos = i - oldi;
3461                       memcpy(parse, &buf[oldi], parse_pos);
3462                       parse[parse_pos] = NULLCHAR;
3463                       started = STARTED_COMMENT;
3464                       savingComment = TRUE;
3465                     } else if(collective != 3) {
3466                       started = STARTED_CHATTER;
3467                       savingComment = FALSE;
3468                     }
3469                     loggedOn = TRUE;
3470                     continue;
3471                   }
3472                 }
3473
3474                 if (looking_at(buf, &i, "* s-shouts: ") ||
3475                     looking_at(buf, &i, "* c-shouts: ")) {
3476                     if (appData.colorize) {
3477                         if (oldi > next_out) {
3478                             SendToPlayer(&buf[next_out], oldi - next_out);
3479                             next_out = oldi;
3480                         }
3481                         Colorize(ColorSShout, FALSE);
3482                         curColor = ColorSShout;
3483                     }
3484                     loggedOn = TRUE;
3485                     started = STARTED_CHATTER;
3486                     continue;
3487                 }
3488
3489                 if (looking_at(buf, &i, "--->")) {
3490                     loggedOn = TRUE;
3491                     continue;
3492                 }
3493
3494                 if (looking_at(buf, &i, "* shouts: ") ||
3495                     looking_at(buf, &i, "--> ")) {
3496                     if (appData.colorize) {
3497                         if (oldi > next_out) {
3498                             SendToPlayer(&buf[next_out], oldi - next_out);
3499                             next_out = oldi;
3500                         }
3501                         Colorize(ColorShout, FALSE);
3502                         curColor = ColorShout;
3503                     }
3504                     loggedOn = TRUE;
3505                     started = STARTED_CHATTER;
3506                     continue;
3507                 }
3508
3509                 if (looking_at( buf, &i, "Challenge:")) {
3510                     if (appData.colorize) {
3511                         if (oldi > next_out) {
3512                             SendToPlayer(&buf[next_out], oldi - next_out);
3513                             next_out = oldi;
3514                         }
3515                         Colorize(ColorChallenge, FALSE);
3516                         curColor = ColorChallenge;
3517                     }
3518                     loggedOn = TRUE;
3519                     continue;
3520                 }
3521
3522                 if (looking_at(buf, &i, "* offers you") ||
3523                     looking_at(buf, &i, "* offers to be") ||
3524                     looking_at(buf, &i, "* would like to") ||
3525                     looking_at(buf, &i, "* requests to") ||
3526                     looking_at(buf, &i, "Your opponent offers") ||
3527                     looking_at(buf, &i, "Your opponent requests")) {
3528
3529                     if (appData.colorize) {
3530                         if (oldi > next_out) {
3531                             SendToPlayer(&buf[next_out], oldi - next_out);
3532                             next_out = oldi;
3533                         }
3534                         Colorize(ColorRequest, FALSE);
3535                         curColor = ColorRequest;
3536                     }
3537                     continue;
3538                 }
3539
3540                 if (looking_at(buf, &i, "* (*) seeking")) {
3541                     if (appData.colorize) {
3542                         if (oldi > next_out) {
3543                             SendToPlayer(&buf[next_out], oldi - next_out);
3544                             next_out = oldi;
3545                         }
3546                         Colorize(ColorSeek, FALSE);
3547                         curColor = ColorSeek;
3548                     }
3549                     continue;
3550             }
3551
3552           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3553
3554             if (looking_at(buf, &i, "\\   ")) {
3555                 if (prevColor != ColorNormal) {
3556                     if (oldi > next_out) {
3557                         SendToPlayer(&buf[next_out], oldi - next_out);
3558                         next_out = oldi;
3559                     }
3560                     Colorize(prevColor, TRUE);
3561                     curColor = prevColor;
3562                 }
3563                 if (savingComment) {
3564                     parse_pos = i - oldi;
3565                     memcpy(parse, &buf[oldi], parse_pos);
3566                     parse[parse_pos] = NULLCHAR;
3567                     started = STARTED_COMMENT;
3568                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3569                         chattingPartner = savingComment - 3; // kludge to remember the box
3570                 } else {
3571                     started = STARTED_CHATTER;
3572                 }
3573                 continue;
3574             }
3575
3576             if (looking_at(buf, &i, "Black Strength :") ||
3577                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3578                 looking_at(buf, &i, "<10>") ||
3579                 looking_at(buf, &i, "#@#")) {
3580                 /* Wrong board style */
3581                 loggedOn = TRUE;
3582                 SendToICS(ics_prefix);
3583                 SendToICS("set style 12\n");
3584                 SendToICS(ics_prefix);
3585                 SendToICS("refresh\n");
3586                 continue;
3587             }
3588
3589             if (looking_at(buf, &i, "login:")) {
3590               if (!have_sent_ICS_logon) {
3591                 if(ICSInitScript())
3592                   have_sent_ICS_logon = 1;
3593                 else // no init script was found
3594                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3595               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3596                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3597               }
3598                 continue;
3599             }
3600
3601             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3602                 (looking_at(buf, &i, "\n<12> ") ||
3603                  looking_at(buf, &i, "<12> "))) {
3604                 loggedOn = TRUE;
3605                 if (oldi > next_out) {
3606                     SendToPlayer(&buf[next_out], oldi - next_out);
3607                 }
3608                 next_out = i;
3609                 started = STARTED_BOARD;
3610                 parse_pos = 0;
3611                 continue;
3612             }
3613
3614             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3615                 looking_at(buf, &i, "<b1> ")) {
3616                 if (oldi > next_out) {
3617                     SendToPlayer(&buf[next_out], oldi - next_out);
3618                 }
3619                 next_out = i;
3620                 started = STARTED_HOLDINGS;
3621                 parse_pos = 0;
3622                 continue;
3623             }
3624
3625             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3626                 loggedOn = TRUE;
3627                 /* Header for a move list -- first line */
3628
3629                 switch (ics_getting_history) {
3630                   case H_FALSE:
3631                     switch (gameMode) {
3632                       case IcsIdle:
3633                       case BeginningOfGame:
3634                         /* User typed "moves" or "oldmoves" while we
3635                            were idle.  Pretend we asked for these
3636                            moves and soak them up so user can step
3637                            through them and/or save them.
3638                            */
3639                         Reset(FALSE, TRUE);
3640                         gameMode = IcsObserving;
3641                         ModeHighlight();
3642                         ics_gamenum = -1;
3643                         ics_getting_history = H_GOT_UNREQ_HEADER;
3644                         break;
3645                       case EditGame: /*?*/
3646                       case EditPosition: /*?*/
3647                         /* Should above feature work in these modes too? */
3648                         /* For now it doesn't */
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                       default:
3652                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3653                         break;
3654                     }
3655                     break;
3656                   case H_REQUESTED:
3657                     /* Is this the right one? */
3658                     if (gameInfo.white && gameInfo.black &&
3659                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3660                         strcmp(gameInfo.black, star_match[2]) == 0) {
3661                         /* All is well */
3662                         ics_getting_history = H_GOT_REQ_HEADER;
3663                     }
3664                     break;
3665                   case H_GOT_REQ_HEADER:
3666                   case H_GOT_UNREQ_HEADER:
3667                   case H_GOT_UNWANTED_HEADER:
3668                   case H_GETTING_MOVES:
3669                     /* Should not happen */
3670                     DisplayError(_("Error gathering move list: two headers"), 0);
3671                     ics_getting_history = H_FALSE;
3672                     break;
3673                 }
3674
3675                 /* Save player ratings into gameInfo if needed */
3676                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3677                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3678                     (gameInfo.whiteRating == -1 ||
3679                      gameInfo.blackRating == -1)) {
3680
3681                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3682                     gameInfo.blackRating = string_to_rating(star_match[3]);
3683                     if (appData.debugMode)
3684                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3685                               gameInfo.whiteRating, gameInfo.blackRating);
3686                 }
3687                 continue;
3688             }
3689
3690             if (looking_at(buf, &i,
3691               "* * match, initial time: * minute*, increment: * second")) {
3692                 /* Header for a move list -- second line */
3693                 /* Initial board will follow if this is a wild game */
3694                 if (gameInfo.event != NULL) free(gameInfo.event);
3695                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3696                 gameInfo.event = StrSave(str);
3697                 /* [HGM] we switched variant. Translate boards if needed. */
3698                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3699                 continue;
3700             }
3701
3702             if (looking_at(buf, &i, "Move  ")) {
3703                 /* Beginning of a move list */
3704                 switch (ics_getting_history) {
3705                   case H_FALSE:
3706                     /* Normally should not happen */
3707                     /* Maybe user hit reset while we were parsing */
3708                     break;
3709                   case H_REQUESTED:
3710                     /* Happens if we are ignoring a move list that is not
3711                      * the one we just requested.  Common if the user
3712                      * tries to observe two games without turning off
3713                      * getMoveList */
3714                     break;
3715                   case H_GETTING_MOVES:
3716                     /* Should not happen */
3717                     DisplayError(_("Error gathering move list: nested"), 0);
3718                     ics_getting_history = H_FALSE;
3719                     break;
3720                   case H_GOT_REQ_HEADER:
3721                     ics_getting_history = H_GETTING_MOVES;
3722                     started = STARTED_MOVES;
3723                     parse_pos = 0;
3724                     if (oldi > next_out) {
3725                         SendToPlayer(&buf[next_out], oldi - next_out);
3726                     }
3727                     break;
3728                   case H_GOT_UNREQ_HEADER:
3729                     ics_getting_history = H_GETTING_MOVES;
3730                     started = STARTED_MOVES_NOHIDE;
3731                     parse_pos = 0;
3732                     break;
3733                   case H_GOT_UNWANTED_HEADER:
3734                     ics_getting_history = H_FALSE;
3735                     break;
3736                 }
3737                 continue;
3738             }
3739
3740             if (looking_at(buf, &i, "% ") ||
3741                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3742                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3743                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3744                     soughtPending = FALSE;
3745                     seekGraphUp = TRUE;
3746                     DrawSeekGraph();
3747                 }
3748                 if(suppressKibitz) next_out = i;
3749                 savingComment = FALSE;
3750                 suppressKibitz = 0;
3751                 switch (started) {
3752                   case STARTED_MOVES:
3753                   case STARTED_MOVES_NOHIDE:
3754                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3755                     parse[parse_pos + i - oldi] = NULLCHAR;
3756                     ParseGameHistory(parse);
3757 #if ZIPPY
3758                     if (appData.zippyPlay && first.initDone) {
3759                         FeedMovesToProgram(&first, forwardMostMove);
3760                         if (gameMode == IcsPlayingWhite) {
3761                             if (WhiteOnMove(forwardMostMove)) {
3762                                 if (first.sendTime) {
3763                                   if (first.useColors) {
3764                                     SendToProgram("black\n", &first);
3765                                   }
3766                                   SendTimeRemaining(&first, TRUE);
3767                                 }
3768                                 if (first.useColors) {
3769                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3770                                 }
3771                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3772                                 first.maybeThinking = TRUE;
3773                             } else {
3774                                 if (first.usePlayother) {
3775                                   if (first.sendTime) {
3776                                     SendTimeRemaining(&first, TRUE);
3777                                   }
3778                                   SendToProgram("playother\n", &first);
3779                                   firstMove = FALSE;
3780                                 } else {
3781                                   firstMove = TRUE;
3782                                 }
3783                             }
3784                         } else if (gameMode == IcsPlayingBlack) {
3785                             if (!WhiteOnMove(forwardMostMove)) {
3786                                 if (first.sendTime) {
3787                                   if (first.useColors) {
3788                                     SendToProgram("white\n", &first);
3789                                   }
3790                                   SendTimeRemaining(&first, FALSE);
3791                                 }
3792                                 if (first.useColors) {
3793                                   SendToProgram("black\n", &first);
3794                                 }
3795                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3796                                 first.maybeThinking = TRUE;
3797                             } else {
3798                                 if (first.usePlayother) {
3799                                   if (first.sendTime) {
3800                                     SendTimeRemaining(&first, FALSE);
3801                                   }
3802                                   SendToProgram("playother\n", &first);
3803                                   firstMove = FALSE;
3804                                 } else {
3805                                   firstMove = TRUE;
3806                                 }
3807                             }
3808                         }
3809                     }
3810 #endif
3811                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3812                         /* Moves came from oldmoves or moves command
3813                            while we weren't doing anything else.
3814                            */
3815                         currentMove = forwardMostMove;
3816                         ClearHighlights();/*!!could figure this out*/
3817                         flipView = appData.flipView;
3818                         DrawPosition(TRUE, boards[currentMove]);
3819                         DisplayBothClocks();
3820                         snprintf(str, MSG_SIZ, "%s %s %s",
3821                                 gameInfo.white, _("vs."),  gameInfo.black);
3822                         DisplayTitle(str);
3823                         gameMode = IcsIdle;
3824                     } else {
3825                         /* Moves were history of an active game */
3826                         if (gameInfo.resultDetails != NULL) {
3827                             free(gameInfo.resultDetails);
3828                             gameInfo.resultDetails = NULL;
3829                         }
3830                     }
3831                     HistorySet(parseList, backwardMostMove,
3832                                forwardMostMove, currentMove-1);
3833                     DisplayMove(currentMove - 1);
3834                     if (started == STARTED_MOVES) next_out = i;
3835                     started = STARTED_NONE;
3836                     ics_getting_history = H_FALSE;
3837                     break;
3838
3839                   case STARTED_OBSERVE:
3840                     started = STARTED_NONE;
3841                     SendToICS(ics_prefix);
3842                     SendToICS("refresh\n");
3843                     break;
3844
3845                   default:
3846                     break;
3847                 }
3848                 if(bookHit) { // [HGM] book: simulate book reply
3849                     static char bookMove[MSG_SIZ]; // a bit generous?
3850
3851                     programStats.nodes = programStats.depth = programStats.time =
3852                     programStats.score = programStats.got_only_move = 0;
3853                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3854
3855                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3856                     strcat(bookMove, bookHit);
3857                     HandleMachineMove(bookMove, &first);
3858                 }
3859                 continue;
3860             }
3861
3862             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3863                  started == STARTED_HOLDINGS ||
3864                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3865                 /* Accumulate characters in move list or board */
3866                 parse[parse_pos++] = buf[i];
3867             }
3868
3869             /* Start of game messages.  Mostly we detect start of game
3870                when the first board image arrives.  On some versions
3871                of the ICS, though, we need to do a "refresh" after starting
3872                to observe in order to get the current board right away. */
3873             if (looking_at(buf, &i, "Adding game * to observation list")) {
3874                 started = STARTED_OBSERVE;
3875                 continue;
3876             }
3877
3878             /* Handle auto-observe */
3879             if (appData.autoObserve &&
3880                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3881                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3882                 char *player;
3883                 /* Choose the player that was highlighted, if any. */
3884                 if (star_match[0][0] == '\033' ||
3885                     star_match[1][0] != '\033') {
3886                     player = star_match[0];
3887                 } else {
3888                     player = star_match[2];
3889                 }
3890                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3891                         ics_prefix, StripHighlightAndTitle(player));
3892                 SendToICS(str);
3893
3894                 /* Save ratings from notify string */
3895                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3896                 player1Rating = string_to_rating(star_match[1]);
3897                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3898                 player2Rating = string_to_rating(star_match[3]);
3899
3900                 if (appData.debugMode)
3901                   fprintf(debugFP,
3902                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3903                           player1Name, player1Rating,
3904                           player2Name, player2Rating);
3905
3906                 continue;
3907             }
3908
3909             /* Deal with automatic examine mode after a game,
3910                and with IcsObserving -> IcsExamining transition */
3911             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3912                 looking_at(buf, &i, "has made you an examiner of game *")) {
3913
3914                 int gamenum = atoi(star_match[0]);
3915                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3916                     gamenum == ics_gamenum) {
3917                     /* We were already playing or observing this game;
3918                        no need to refetch history */
3919                     gameMode = IcsExamining;
3920                     if (pausing) {
3921                         pauseExamForwardMostMove = forwardMostMove;
3922                     } else if (currentMove < forwardMostMove) {
3923                         ForwardInner(forwardMostMove);
3924                     }
3925                 } else {
3926                     /* I don't think this case really can happen */
3927                     SendToICS(ics_prefix);
3928                     SendToICS("refresh\n");
3929                 }
3930                 continue;
3931             }
3932
3933             /* Error messages */
3934 //          if (ics_user_moved) {
3935             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3936                 if (looking_at(buf, &i, "Illegal move") ||
3937                     looking_at(buf, &i, "Not a legal move") ||
3938                     looking_at(buf, &i, "Your king is in check") ||
3939                     looking_at(buf, &i, "It isn't your turn") ||
3940                     looking_at(buf, &i, "It is not your move")) {
3941                     /* Illegal move */
3942                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3943                         currentMove = forwardMostMove-1;
3944                         DisplayMove(currentMove - 1); /* before DMError */
3945                         DrawPosition(FALSE, boards[currentMove]);
3946                         SwitchClocks(forwardMostMove-1); // [HGM] race
3947                         DisplayBothClocks();
3948                     }
3949                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3950                     ics_user_moved = 0;
3951                     continue;
3952                 }
3953             }
3954
3955             if (looking_at(buf, &i, "still have time") ||
3956                 looking_at(buf, &i, "not out of time") ||
3957                 looking_at(buf, &i, "either player is out of time") ||
3958                 looking_at(buf, &i, "has timeseal; checking")) {
3959                 /* We must have called his flag a little too soon */
3960                 whiteFlag = blackFlag = FALSE;
3961                 continue;
3962             }
3963
3964             if (looking_at(buf, &i, "added * seconds to") ||
3965                 looking_at(buf, &i, "seconds were added to")) {
3966                 /* Update the clocks */
3967                 SendToICS(ics_prefix);
3968                 SendToICS("refresh\n");
3969                 continue;
3970             }
3971
3972             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3973                 ics_clock_paused = TRUE;
3974                 StopClocks();
3975                 continue;
3976             }
3977
3978             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3979                 ics_clock_paused = FALSE;
3980                 StartClocks();
3981                 continue;
3982             }
3983
3984             /* Grab player ratings from the Creating: message.
3985                Note we have to check for the special case when
3986                the ICS inserts things like [white] or [black]. */
3987             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3988                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3989                 /* star_matches:
3990                    0    player 1 name (not necessarily white)
3991                    1    player 1 rating
3992                    2    empty, white, or black (IGNORED)
3993                    3    player 2 name (not necessarily black)
3994                    4    player 2 rating
3995
3996                    The names/ratings are sorted out when the game
3997                    actually starts (below).
3998                 */
3999                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4000                 player1Rating = string_to_rating(star_match[1]);
4001                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4002                 player2Rating = string_to_rating(star_match[4]);
4003
4004                 if (appData.debugMode)
4005                   fprintf(debugFP,
4006                           "Ratings from 'Creating:' %s %d, %s %d\n",
4007                           player1Name, player1Rating,
4008                           player2Name, player2Rating);
4009
4010                 continue;
4011             }
4012
4013             /* Improved generic start/end-of-game messages */
4014             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4015                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4016                 /* If tkind == 0: */
4017                 /* star_match[0] is the game number */
4018                 /*           [1] is the white player's name */
4019                 /*           [2] is the black player's name */
4020                 /* For end-of-game: */
4021                 /*           [3] is the reason for the game end */
4022                 /*           [4] is a PGN end game-token, preceded by " " */
4023                 /* For start-of-game: */
4024                 /*           [3] begins with "Creating" or "Continuing" */
4025                 /*           [4] is " *" or empty (don't care). */
4026                 int gamenum = atoi(star_match[0]);
4027                 char *whitename, *blackname, *why, *endtoken;
4028                 ChessMove endtype = EndOfFile;
4029
4030                 if (tkind == 0) {
4031                   whitename = star_match[1];
4032                   blackname = star_match[2];
4033                   why = star_match[3];
4034                   endtoken = star_match[4];
4035                 } else {
4036                   whitename = star_match[1];
4037                   blackname = star_match[3];
4038                   why = star_match[5];
4039                   endtoken = star_match[6];
4040                 }
4041
4042                 /* Game start messages */
4043                 if (strncmp(why, "Creating ", 9) == 0 ||
4044                     strncmp(why, "Continuing ", 11) == 0) {
4045                     gs_gamenum = gamenum;
4046                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4047                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4048                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4049 #if ZIPPY
4050                     if (appData.zippyPlay) {
4051                         ZippyGameStart(whitename, blackname);
4052                     }
4053 #endif /*ZIPPY*/
4054                     partnerBoardValid = FALSE; // [HGM] bughouse
4055                     continue;
4056                 }
4057
4058                 /* Game end messages */
4059                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4060                     ics_gamenum != gamenum) {
4061                     continue;
4062                 }
4063                 while (endtoken[0] == ' ') endtoken++;
4064                 switch (endtoken[0]) {
4065                   case '*':
4066                   default:
4067                     endtype = GameUnfinished;
4068                     break;
4069                   case '0':
4070                     endtype = BlackWins;
4071                     break;
4072                   case '1':
4073                     if (endtoken[1] == '/')
4074                       endtype = GameIsDrawn;
4075                     else
4076                       endtype = WhiteWins;
4077                     break;
4078                 }
4079                 GameEnds(endtype, why, GE_ICS);
4080 #if ZIPPY
4081                 if (appData.zippyPlay && first.initDone) {
4082                     ZippyGameEnd(endtype, why);
4083                     if (first.pr == NoProc) {
4084                       /* Start the next process early so that we'll
4085                          be ready for the next challenge */
4086                       StartChessProgram(&first);
4087                     }
4088                     /* Send "new" early, in case this command takes
4089                        a long time to finish, so that we'll be ready
4090                        for the next challenge. */
4091                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4092                     Reset(TRUE, TRUE);
4093                 }
4094 #endif /*ZIPPY*/
4095                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4096                 continue;
4097             }
4098
4099             if (looking_at(buf, &i, "Removing game * from observation") ||
4100                 looking_at(buf, &i, "no longer observing game *") ||
4101                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4102                 if (gameMode == IcsObserving &&
4103                     atoi(star_match[0]) == ics_gamenum)
4104                   {
4105                       /* icsEngineAnalyze */
4106                       if (appData.icsEngineAnalyze) {
4107                             ExitAnalyzeMode();
4108                             ModeHighlight();
4109                       }
4110                       StopClocks();
4111                       gameMode = IcsIdle;
4112                       ics_gamenum = -1;
4113                       ics_user_moved = FALSE;
4114                   }
4115                 continue;
4116             }
4117
4118             if (looking_at(buf, &i, "no longer examining game *")) {
4119                 if (gameMode == IcsExamining &&
4120                     atoi(star_match[0]) == ics_gamenum)
4121                   {
4122                       gameMode = IcsIdle;
4123                       ics_gamenum = -1;
4124                       ics_user_moved = FALSE;
4125                   }
4126                 continue;
4127             }
4128
4129             /* Advance leftover_start past any newlines we find,
4130                so only partial lines can get reparsed */
4131             if (looking_at(buf, &i, "\n")) {
4132                 prevColor = curColor;
4133                 if (curColor != ColorNormal) {
4134                     if (oldi > next_out) {
4135                         SendToPlayer(&buf[next_out], oldi - next_out);
4136                         next_out = oldi;
4137                     }
4138                     Colorize(ColorNormal, FALSE);
4139                     curColor = ColorNormal;
4140                 }
4141                 if (started == STARTED_BOARD) {
4142                     started = STARTED_NONE;
4143                     parse[parse_pos] = NULLCHAR;
4144                     ParseBoard12(parse);
4145                     ics_user_moved = 0;
4146
4147                     /* Send premove here */
4148                     if (appData.premove) {
4149                       char str[MSG_SIZ];
4150                       if (currentMove == 0 &&
4151                           gameMode == IcsPlayingWhite &&
4152                           appData.premoveWhite) {
4153                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4154                         if (appData.debugMode)
4155                           fprintf(debugFP, "Sending premove:\n");
4156                         SendToICS(str);
4157                       } else if (currentMove == 1 &&
4158                                  gameMode == IcsPlayingBlack &&
4159                                  appData.premoveBlack) {
4160                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4161                         if (appData.debugMode)
4162                           fprintf(debugFP, "Sending premove:\n");
4163                         SendToICS(str);
4164                       } else if (gotPremove) {
4165                         int oldFMM = forwardMostMove;
4166                         gotPremove = 0;
4167                         ClearPremoveHighlights();
4168                         if (appData.debugMode)
4169                           fprintf(debugFP, "Sending premove:\n");
4170                           UserMoveEvent(premoveFromX, premoveFromY,
4171                                         premoveToX, premoveToY,
4172                                         premovePromoChar);
4173                         if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
4174                           if(moveList[oldFMM-1][1] != '@')
4175                             SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
4176                                           moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4177                           else // (drop)
4178                             SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
4179                         }
4180                       }
4181                     }
4182
4183                     /* Usually suppress following prompt */
4184                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4185                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4186                         if (looking_at(buf, &i, "*% ")) {
4187                             savingComment = FALSE;
4188                             suppressKibitz = 0;
4189                         }
4190                     }
4191                     next_out = i;
4192                 } else if (started == STARTED_HOLDINGS) {
4193                     int gamenum;
4194                     char new_piece[MSG_SIZ];
4195                     started = STARTED_NONE;
4196                     parse[parse_pos] = NULLCHAR;
4197                     if (appData.debugMode)
4198                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4199                                                         parse, currentMove);
4200                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4201                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4202                         if (gameInfo.variant == VariantNormal) {
4203                           /* [HGM] We seem to switch variant during a game!
4204                            * Presumably no holdings were displayed, so we have
4205                            * to move the position two files to the right to
4206                            * create room for them!
4207                            */
4208                           VariantClass newVariant;
4209                           switch(gameInfo.boardWidth) { // base guess on board width
4210                                 case 9:  newVariant = VariantShogi; break;
4211                                 case 10: newVariant = VariantGreat; break;
4212                                 default: newVariant = VariantCrazyhouse; break;
4213                           }
4214                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4215                           /* Get a move list just to see the header, which
4216                              will tell us whether this is really bug or zh */
4217                           if (ics_getting_history == H_FALSE) {
4218                             ics_getting_history = H_REQUESTED;
4219                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4220                             SendToICS(str);
4221                           }
4222                         }
4223                         new_piece[0] = NULLCHAR;
4224                         sscanf(parse, "game %d white [%s black [%s <- %s",
4225                                &gamenum, white_holding, black_holding,
4226                                new_piece);
4227                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4228                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4229                         /* [HGM] copy holdings to board holdings area */
4230                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4231                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4232                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4233 #if ZIPPY
4234                         if (appData.zippyPlay && first.initDone) {
4235                             ZippyHoldings(white_holding, black_holding,
4236                                           new_piece);
4237                         }
4238 #endif /*ZIPPY*/
4239                         if (tinyLayout || smallLayout) {
4240                             char wh[16], bh[16];
4241                             PackHolding(wh, white_holding);
4242                             PackHolding(bh, black_holding);
4243                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4244                                     gameInfo.white, gameInfo.black);
4245                         } else {
4246                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4247                                     gameInfo.white, white_holding, _("vs."),
4248                                     gameInfo.black, black_holding);
4249                         }
4250                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4251                         DrawPosition(FALSE, boards[currentMove]);
4252                         DisplayTitle(str);
4253                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4254                         sscanf(parse, "game %d white [%s black [%s <- %s",
4255                                &gamenum, white_holding, black_holding,
4256                                new_piece);
4257                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4258                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4259                         /* [HGM] copy holdings to partner-board holdings area */
4260                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4261                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4262                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4263                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4264                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4265                       }
4266                     }
4267                     /* Suppress following prompt */
4268                     if (looking_at(buf, &i, "*% ")) {
4269                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4270                         savingComment = FALSE;
4271                         suppressKibitz = 0;
4272                     }
4273                     next_out = i;
4274                 }
4275                 continue;
4276             }
4277
4278             i++;                /* skip unparsed character and loop back */
4279         }
4280
4281         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4282 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4283 //          SendToPlayer(&buf[next_out], i - next_out);
4284             started != STARTED_HOLDINGS && leftover_start > next_out) {
4285             SendToPlayer(&buf[next_out], leftover_start - next_out);
4286             next_out = i;
4287         }
4288
4289         leftover_len = buf_len - leftover_start;
4290         /* if buffer ends with something we couldn't parse,
4291            reparse it after appending the next read */
4292
4293     } else if (count == 0) {
4294         RemoveInputSource(isr);
4295         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4296     } else {
4297         DisplayFatalError(_("Error reading from ICS"), error, 1);
4298     }
4299 }
4300
4301
4302 /* Board style 12 looks like this:
4303
4304    <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
4305
4306  * The "<12> " is stripped before it gets to this routine.  The two
4307  * trailing 0's (flip state and clock ticking) are later addition, and
4308  * some chess servers may not have them, or may have only the first.
4309  * Additional trailing fields may be added in the future.
4310  */
4311
4312 #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"
4313
4314 #define RELATION_OBSERVING_PLAYED    0
4315 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4316 #define RELATION_PLAYING_MYMOVE      1
4317 #define RELATION_PLAYING_NOTMYMOVE  -1
4318 #define RELATION_EXAMINING           2
4319 #define RELATION_ISOLATED_BOARD     -3
4320 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4321
4322 void
4323 ParseBoard12 (char *string)
4324 {
4325 #if ZIPPY
4326     int i, takeback;
4327     char *bookHit = NULL; // [HGM] book
4328 #endif
4329     GameMode newGameMode;
4330     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4331     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4332     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4333     char to_play, board_chars[200];
4334     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4335     char black[32], white[32];
4336     Board board;
4337     int prevMove = currentMove;
4338     int ticking = 2;
4339     ChessMove moveType;
4340     int fromX, fromY, toX, toY;
4341     char promoChar;
4342     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4343     Boolean weird = FALSE, reqFlag = FALSE;
4344
4345     fromX = fromY = toX = toY = -1;
4346
4347     newGame = FALSE;
4348
4349     if (appData.debugMode)
4350       fprintf(debugFP, "Parsing board: %s\n", string);
4351
4352     move_str[0] = NULLCHAR;
4353     elapsed_time[0] = NULLCHAR;
4354     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4355         int  i = 0, j;
4356         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4357             if(string[i] == ' ') { ranks++; files = 0; }
4358             else files++;
4359             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4360             i++;
4361         }
4362         for(j = 0; j <i; j++) board_chars[j] = string[j];
4363         board_chars[i] = '\0';
4364         string += i + 1;
4365     }
4366     n = sscanf(string, PATTERN, &to_play, &double_push,
4367                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4368                &gamenum, white, black, &relation, &basetime, &increment,
4369                &white_stren, &black_stren, &white_time, &black_time,
4370                &moveNum, str, elapsed_time, move_str, &ics_flip,
4371                &ticking);
4372
4373     if (n < 21) {
4374         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4375         DisplayError(str, 0);
4376         return;
4377     }
4378
4379     /* Convert the move number to internal form */
4380     moveNum = (moveNum - 1) * 2;
4381     if (to_play == 'B') moveNum++;
4382     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4383       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4384                         0, 1);
4385       return;
4386     }
4387
4388     switch (relation) {
4389       case RELATION_OBSERVING_PLAYED:
4390       case RELATION_OBSERVING_STATIC:
4391         if (gamenum == -1) {
4392             /* Old ICC buglet */
4393             relation = RELATION_OBSERVING_STATIC;
4394         }
4395         newGameMode = IcsObserving;
4396         break;
4397       case RELATION_PLAYING_MYMOVE:
4398       case RELATION_PLAYING_NOTMYMOVE:
4399         newGameMode =
4400           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4401             IcsPlayingWhite : IcsPlayingBlack;
4402         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4403         break;
4404       case RELATION_EXAMINING:
4405         newGameMode = IcsExamining;
4406         break;
4407       case RELATION_ISOLATED_BOARD:
4408       default:
4409         /* Just display this board.  If user was doing something else,
4410            we will forget about it until the next board comes. */
4411         newGameMode = IcsIdle;
4412         break;
4413       case RELATION_STARTING_POSITION:
4414         newGameMode = gameMode;
4415         break;
4416     }
4417
4418     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4419         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4420          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4421       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4422       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4423       static int lastBgGame = -1;
4424       char *toSqr;
4425       for (k = 0; k < ranks; k++) {
4426         for (j = 0; j < files; j++)
4427           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4428         if(gameInfo.holdingsWidth > 1) {
4429              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4430              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4431         }
4432       }
4433       CopyBoard(partnerBoard, board);
4434       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4435         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4436         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4437       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4438       if(toSqr = strchr(str, '-')) {
4439         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4440         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4441       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4442       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4443       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4444       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4445       if(twoBoards) {
4446           DisplayWhiteClock(white_time*fac, to_play == 'W');
4447           DisplayBlackClock(black_time*fac, to_play != 'W');
4448           activePartner = to_play;
4449           if(gamenum != lastBgGame) {
4450               char buf[MSG_SIZ];
4451               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4452               DisplayTitle(buf);
4453           }
4454           lastBgGame = gamenum;
4455           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4456                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4457       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4458                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4459       if(!twoBoards) DisplayMessage(partnerStatus, "");
4460         partnerBoardValid = TRUE;
4461       return;
4462     }
4463
4464     if(appData.dualBoard && appData.bgObserve) {
4465         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4466             SendToICS(ics_prefix), SendToICS("pobserve\n");
4467         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4468             char buf[MSG_SIZ];
4469             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4470             SendToICS(buf);
4471         }
4472     }
4473
4474     /* Modify behavior for initial board display on move listing
4475        of wild games.
4476        */
4477     switch (ics_getting_history) {
4478       case H_FALSE:
4479       case H_REQUESTED:
4480         break;
4481       case H_GOT_REQ_HEADER:
4482       case H_GOT_UNREQ_HEADER:
4483         /* This is the initial position of the current game */
4484         gamenum = ics_gamenum;
4485         moveNum = 0;            /* old ICS bug workaround */
4486         if (to_play == 'B') {
4487           startedFromSetupPosition = TRUE;
4488           blackPlaysFirst = TRUE;
4489           moveNum = 1;
4490           if (forwardMostMove == 0) forwardMostMove = 1;
4491           if (backwardMostMove == 0) backwardMostMove = 1;
4492           if (currentMove == 0) currentMove = 1;
4493         }
4494         newGameMode = gameMode;
4495         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4496         break;
4497       case H_GOT_UNWANTED_HEADER:
4498         /* This is an initial board that we don't want */
4499         return;
4500       case H_GETTING_MOVES:
4501         /* Should not happen */
4502         DisplayError(_("Error gathering move list: extra board"), 0);
4503         ics_getting_history = H_FALSE;
4504         return;
4505     }
4506
4507    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4508                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4509                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4510      /* [HGM] We seem to have switched variant unexpectedly
4511       * Try to guess new variant from board size
4512       */
4513           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4514           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4515           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4516           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4517           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4518           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4519           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4520           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4521           /* Get a move list just to see the header, which
4522              will tell us whether this is really bug or zh */
4523           if (ics_getting_history == H_FALSE) {
4524             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4525             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4526             SendToICS(str);
4527           }
4528     }
4529
4530     /* Take action if this is the first board of a new game, or of a
4531        different game than is currently being displayed.  */
4532     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4533         relation == RELATION_ISOLATED_BOARD) {
4534
4535         /* Forget the old game and get the history (if any) of the new one */
4536         if (gameMode != BeginningOfGame) {
4537           Reset(TRUE, TRUE);
4538         }
4539         newGame = TRUE;
4540         if (appData.autoRaiseBoard) BoardToTop();
4541         prevMove = -3;
4542         if (gamenum == -1) {
4543             newGameMode = IcsIdle;
4544         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4545                    appData.getMoveList && !reqFlag) {
4546             /* Need to get game history */
4547             ics_getting_history = H_REQUESTED;
4548             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4549             SendToICS(str);
4550         }
4551
4552         /* Initially flip the board to have black on the bottom if playing
4553            black or if the ICS flip flag is set, but let the user change
4554            it with the Flip View button. */
4555         flipView = appData.autoFlipView ?
4556           (newGameMode == IcsPlayingBlack) || ics_flip :
4557           appData.flipView;
4558
4559         /* Done with values from previous mode; copy in new ones */
4560         gameMode = newGameMode;
4561         ModeHighlight();
4562         ics_gamenum = gamenum;
4563         if (gamenum == gs_gamenum) {
4564             int klen = strlen(gs_kind);
4565             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4566             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4567             gameInfo.event = StrSave(str);
4568         } else {
4569             gameInfo.event = StrSave("ICS game");
4570         }
4571         gameInfo.site = StrSave(appData.icsHost);
4572         gameInfo.date = PGNDate();
4573         gameInfo.round = StrSave("-");
4574         gameInfo.white = StrSave(white);
4575         gameInfo.black = StrSave(black);
4576         timeControl = basetime * 60 * 1000;
4577         timeControl_2 = 0;
4578         timeIncrement = increment * 1000;
4579         movesPerSession = 0;
4580         gameInfo.timeControl = TimeControlTagValue();
4581         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4582   if (appData.debugMode) {
4583     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4584     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4585     setbuf(debugFP, NULL);
4586   }
4587
4588         gameInfo.outOfBook = NULL;
4589
4590         /* Do we have the ratings? */
4591         if (strcmp(player1Name, white) == 0 &&
4592             strcmp(player2Name, black) == 0) {
4593             if (appData.debugMode)
4594               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4595                       player1Rating, player2Rating);
4596             gameInfo.whiteRating = player1Rating;
4597             gameInfo.blackRating = player2Rating;
4598         } else if (strcmp(player2Name, white) == 0 &&
4599                    strcmp(player1Name, black) == 0) {
4600             if (appData.debugMode)
4601               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4602                       player2Rating, player1Rating);
4603             gameInfo.whiteRating = player2Rating;
4604             gameInfo.blackRating = player1Rating;
4605         }
4606         player1Name[0] = player2Name[0] = NULLCHAR;
4607
4608         /* Silence shouts if requested */
4609         if (appData.quietPlay &&
4610             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4611             SendToICS(ics_prefix);
4612             SendToICS("set shout 0\n");
4613         }
4614     }
4615
4616     /* Deal with midgame name changes */
4617     if (!newGame) {
4618         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4619             if (gameInfo.white) free(gameInfo.white);
4620             gameInfo.white = StrSave(white);
4621         }
4622         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4623             if (gameInfo.black) free(gameInfo.black);
4624             gameInfo.black = StrSave(black);
4625         }
4626     }
4627
4628     /* Throw away game result if anything actually changes in examine mode */
4629     if (gameMode == IcsExamining && !newGame) {
4630         gameInfo.result = GameUnfinished;
4631         if (gameInfo.resultDetails != NULL) {
4632             free(gameInfo.resultDetails);
4633             gameInfo.resultDetails = NULL;
4634         }
4635     }
4636
4637     /* In pausing && IcsExamining mode, we ignore boards coming
4638        in if they are in a different variation than we are. */
4639     if (pauseExamInvalid) return;
4640     if (pausing && gameMode == IcsExamining) {
4641         if (moveNum <= pauseExamForwardMostMove) {
4642             pauseExamInvalid = TRUE;
4643             forwardMostMove = pauseExamForwardMostMove;
4644             return;
4645         }
4646     }
4647
4648   if (appData.debugMode) {
4649     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4650   }
4651     /* Parse the board */
4652     for (k = 0; k < ranks; k++) {
4653       for (j = 0; j < files; j++)
4654         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4655       if(gameInfo.holdingsWidth > 1) {
4656            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4657            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4658       }
4659     }
4660     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4661       board[5][BOARD_RGHT+1] = WhiteAngel;
4662       board[6][BOARD_RGHT+1] = WhiteMarshall;
4663       board[1][0] = BlackMarshall;
4664       board[2][0] = BlackAngel;
4665       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4666     }
4667     CopyBoard(boards[moveNum], board);
4668     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4669     if (moveNum == 0) {
4670         startedFromSetupPosition =
4671           !CompareBoards(board, initialPosition);
4672         if(startedFromSetupPosition)
4673             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4674     }
4675
4676     /* [HGM] Set castling rights. Take the outermost Rooks,
4677        to make it also work for FRC opening positions. Note that board12
4678        is really defective for later FRC positions, as it has no way to
4679        indicate which Rook can castle if they are on the same side of King.
4680        For the initial position we grant rights to the outermost Rooks,
4681        and remember thos rights, and we then copy them on positions
4682        later in an FRC game. This means WB might not recognize castlings with
4683        Rooks that have moved back to their original position as illegal,
4684        but in ICS mode that is not its job anyway.
4685     */
4686     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4687     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4688
4689         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4690             if(board[0][i] == WhiteRook) j = i;
4691         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4692         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4693             if(board[0][i] == WhiteRook) j = i;
4694         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4695         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4696             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4697         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4698         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4699             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4700         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4701
4702         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4703         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4704         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4705             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4706         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4707             if(board[BOARD_HEIGHT-1][k] == bKing)
4708                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4709         if(gameInfo.variant == VariantTwoKings) {
4710             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4711             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4712             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4713         }
4714     } else { int r;
4715         r = boards[moveNum][CASTLING][0] = initialRights[0];
4716         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4717         r = boards[moveNum][CASTLING][1] = initialRights[1];
4718         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4719         r = boards[moveNum][CASTLING][3] = initialRights[3];
4720         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4721         r = boards[moveNum][CASTLING][4] = initialRights[4];
4722         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4723         /* wildcastle kludge: always assume King has rights */
4724         r = boards[moveNum][CASTLING][2] = initialRights[2];
4725         r = boards[moveNum][CASTLING][5] = initialRights[5];
4726     }
4727     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4728     boards[moveNum][EP_STATUS] = EP_NONE;
4729     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4730     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4731     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4732
4733
4734     if (ics_getting_history == H_GOT_REQ_HEADER ||
4735         ics_getting_history == H_GOT_UNREQ_HEADER) {
4736         /* This was an initial position from a move list, not
4737            the current position */
4738         return;
4739     }
4740
4741     /* Update currentMove and known move number limits */
4742     newMove = newGame || moveNum > forwardMostMove;
4743
4744     if (newGame) {
4745         forwardMostMove = backwardMostMove = currentMove = moveNum;
4746         if (gameMode == IcsExamining && moveNum == 0) {
4747           /* Workaround for ICS limitation: we are not told the wild
4748              type when starting to examine a game.  But if we ask for
4749              the move list, the move list header will tell us */
4750             ics_getting_history = H_REQUESTED;
4751             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4752             SendToICS(str);
4753         }
4754     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4755                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4756 #if ZIPPY
4757         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4758         /* [HGM] applied this also to an engine that is silently watching        */
4759         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4760             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4761             gameInfo.variant == currentlyInitializedVariant) {
4762           takeback = forwardMostMove - moveNum;
4763           for (i = 0; i < takeback; i++) {
4764             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4765             SendToProgram("undo\n", &first);
4766           }
4767         }
4768 #endif
4769
4770         forwardMostMove = moveNum;
4771         if (!pausing || currentMove > forwardMostMove)
4772           currentMove = forwardMostMove;
4773     } else {
4774         /* New part of history that is not contiguous with old part */
4775         if (pausing && gameMode == IcsExamining) {
4776             pauseExamInvalid = TRUE;
4777             forwardMostMove = pauseExamForwardMostMove;
4778             return;
4779         }
4780         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4781 #if ZIPPY
4782             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4783                 // [HGM] when we will receive the move list we now request, it will be
4784                 // fed to the engine from the first move on. So if the engine is not
4785                 // in the initial position now, bring it there.
4786                 InitChessProgram(&first, 0);
4787             }
4788 #endif
4789             ics_getting_history = H_REQUESTED;
4790             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4791             SendToICS(str);
4792         }
4793         forwardMostMove = backwardMostMove = currentMove = moveNum;
4794     }
4795
4796     /* Update the clocks */
4797     if (strchr(elapsed_time, '.')) {
4798       /* Time is in ms */
4799       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4800       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4801     } else {
4802       /* Time is in seconds */
4803       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4804       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4805     }
4806
4807
4808 #if ZIPPY
4809     if (appData.zippyPlay && newGame &&
4810         gameMode != IcsObserving && gameMode != IcsIdle &&
4811         gameMode != IcsExamining)
4812       ZippyFirstBoard(moveNum, basetime, increment);
4813 #endif
4814
4815     /* Put the move on the move list, first converting
4816        to canonical algebraic form. */
4817     if (moveNum > 0) {
4818   if (appData.debugMode) {
4819     int f = forwardMostMove;
4820     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4821             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4822             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4823     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4824     fprintf(debugFP, "moveNum = %d\n", moveNum);
4825     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4826     setbuf(debugFP, NULL);
4827   }
4828         if (moveNum <= backwardMostMove) {
4829             /* We don't know what the board looked like before
4830                this move.  Punt. */
4831           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4832             strcat(parseList[moveNum - 1], " ");
4833             strcat(parseList[moveNum - 1], elapsed_time);
4834             moveList[moveNum - 1][0] = NULLCHAR;
4835         } else if (strcmp(move_str, "none") == 0) {
4836             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4837             /* Again, we don't know what the board looked like;
4838                this is really the start of the game. */
4839             parseList[moveNum - 1][0] = NULLCHAR;
4840             moveList[moveNum - 1][0] = NULLCHAR;
4841             backwardMostMove = moveNum;
4842             startedFromSetupPosition = TRUE;
4843             fromX = fromY = toX = toY = -1;
4844         } else {
4845           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4846           //                 So we parse the long-algebraic move string in stead of the SAN move
4847           int valid; char buf[MSG_SIZ], *prom;
4848
4849           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4850                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4851           // str looks something like "Q/a1-a2"; kill the slash
4852           if(str[1] == '/')
4853             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4854           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4855           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4856                 strcat(buf, prom); // long move lacks promo specification!
4857           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4858                 if(appData.debugMode)
4859                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4860                 safeStrCpy(move_str, buf, MSG_SIZ);
4861           }
4862           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4863                                 &fromX, &fromY, &toX, &toY, &promoChar)
4864                || ParseOneMove(buf, moveNum - 1, &moveType,
4865                                 &fromX, &fromY, &toX, &toY, &promoChar);
4866           // end of long SAN patch
4867           if (valid) {
4868             (void) CoordsToAlgebraic(boards[moveNum - 1],
4869                                      PosFlags(moveNum - 1),
4870                                      fromY, fromX, toY, toX, promoChar,
4871                                      parseList[moveNum-1]);
4872             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4873               case MT_NONE:
4874               case MT_STALEMATE:
4875               default:
4876                 break;
4877               case MT_CHECK:
4878                 if(!IS_SHOGI(gameInfo.variant))
4879                     strcat(parseList[moveNum - 1], "+");
4880                 break;
4881               case MT_CHECKMATE:
4882               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4883                 strcat(parseList[moveNum - 1], "#");
4884                 break;
4885             }
4886             strcat(parseList[moveNum - 1], " ");
4887             strcat(parseList[moveNum - 1], elapsed_time);
4888             /* currentMoveString is set as a side-effect of ParseOneMove */
4889             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4890             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4891             strcat(moveList[moveNum - 1], "\n");
4892
4893             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4894                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4895               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4896                 ChessSquare old, new = boards[moveNum][k][j];
4897                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4898                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4899                   if(old == new) continue;
4900                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4901                   else if(new == WhiteWazir || new == BlackWazir) {
4902                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4903                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4904                       else boards[moveNum][k][j] = old; // preserve type of Gold
4905                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4906                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4907               }
4908           } else {
4909             /* Move from ICS was illegal!?  Punt. */
4910             if (appData.debugMode) {
4911               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4912               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4913             }
4914             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4915             strcat(parseList[moveNum - 1], " ");
4916             strcat(parseList[moveNum - 1], elapsed_time);
4917             moveList[moveNum - 1][0] = NULLCHAR;
4918             fromX = fromY = toX = toY = -1;
4919           }
4920         }
4921   if (appData.debugMode) {
4922     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4923     setbuf(debugFP, NULL);
4924   }
4925
4926 #if ZIPPY
4927         /* Send move to chess program (BEFORE animating it). */
4928         if (appData.zippyPlay && !newGame && newMove &&
4929            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4930
4931             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4932                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4933                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4934                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4935                             move_str);
4936                     DisplayError(str, 0);
4937                 } else {
4938                     if (first.sendTime) {
4939                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4940                     }
4941                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4942                     if (firstMove && !bookHit) {
4943                         firstMove = FALSE;
4944                         if (first.useColors) {
4945                           SendToProgram(gameMode == IcsPlayingWhite ?
4946                                         "white\ngo\n" :
4947                                         "black\ngo\n", &first);
4948                         } else {
4949                           SendToProgram("go\n", &first);
4950                         }
4951                         first.maybeThinking = TRUE;
4952                     }
4953                 }
4954             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4955               if (moveList[moveNum - 1][0] == NULLCHAR) {
4956                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4957                 DisplayError(str, 0);
4958               } else {
4959                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4960                 SendMoveToProgram(moveNum - 1, &first);
4961               }
4962             }
4963         }
4964 #endif
4965     }
4966
4967     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4968         /* If move comes from a remote source, animate it.  If it
4969            isn't remote, it will have already been animated. */
4970         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4971             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4972         }
4973         if (!pausing && appData.highlightLastMove) {
4974             SetHighlights(fromX, fromY, toX, toY);
4975         }
4976     }
4977
4978     /* Start the clocks */
4979     whiteFlag = blackFlag = FALSE;
4980     appData.clockMode = !(basetime == 0 && increment == 0);
4981     if (ticking == 0) {
4982       ics_clock_paused = TRUE;
4983       StopClocks();
4984     } else if (ticking == 1) {
4985       ics_clock_paused = FALSE;
4986     }
4987     if (gameMode == IcsIdle ||
4988         relation == RELATION_OBSERVING_STATIC ||
4989         relation == RELATION_EXAMINING ||
4990         ics_clock_paused)
4991       DisplayBothClocks();
4992     else
4993       StartClocks();
4994
4995     /* Display opponents and material strengths */
4996     if (gameInfo.variant != VariantBughouse &&
4997         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4998         if (tinyLayout || smallLayout) {
4999             if(gameInfo.variant == VariantNormal)
5000               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
5001                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5002                     basetime, increment);
5003             else
5004               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
5005                     gameInfo.white, white_stren, gameInfo.black, black_stren,
5006                     basetime, increment, (int) gameInfo.variant);
5007         } else {
5008             if(gameInfo.variant == VariantNormal)
5009               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5010                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5011                     basetime, increment);
5012             else
5013               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5014                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5015                     basetime, increment, VariantName(gameInfo.variant));
5016         }
5017         DisplayTitle(str);
5018   if (appData.debugMode) {
5019     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5020   }
5021     }
5022
5023
5024     /* Display the board */
5025     if (!pausing && !appData.noGUI) {
5026
5027       if (appData.premove)
5028           if (!gotPremove ||
5029              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5030              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5031               ClearPremoveHighlights();
5032
5033       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5034         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5035       DrawPosition(j, boards[currentMove]);
5036
5037       DisplayMove(moveNum - 1);
5038       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5039             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5040               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5041         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5042       }
5043     }
5044
5045     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5046 #if ZIPPY
5047     if(bookHit) { // [HGM] book: simulate book reply
5048         static char bookMove[MSG_SIZ]; // a bit generous?
5049
5050         programStats.nodes = programStats.depth = programStats.time =
5051         programStats.score = programStats.got_only_move = 0;
5052         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5053
5054         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5055         strcat(bookMove, bookHit);
5056         HandleMachineMove(bookMove, &first);
5057     }
5058 #endif
5059 }
5060
5061 void
5062 GetMoveListEvent ()
5063 {
5064     char buf[MSG_SIZ];
5065     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5066         ics_getting_history = H_REQUESTED;
5067         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5068         SendToICS(buf);
5069     }
5070 }
5071
5072 void
5073 SendToBoth (char *msg)
5074 {   // to make it easy to keep two engines in step in dual analysis
5075     SendToProgram(msg, &first);
5076     if(second.analyzing) SendToProgram(msg, &second);
5077 }
5078
5079 void
5080 AnalysisPeriodicEvent (int force)
5081 {
5082     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5083          && !force) || !appData.periodicUpdates)
5084       return;
5085
5086     /* Send . command to Crafty to collect stats */
5087     SendToBoth(".\n");
5088
5089     /* Don't send another until we get a response (this makes
5090        us stop sending to old Crafty's which don't understand
5091        the "." command (sending illegal cmds resets node count & time,
5092        which looks bad)) */
5093     programStats.ok_to_send = 0;
5094 }
5095
5096 void
5097 ics_update_width (int new_width)
5098 {
5099         ics_printf("set width %d\n", new_width);
5100 }
5101
5102 void
5103 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5104 {
5105     char buf[MSG_SIZ];
5106
5107     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5108         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5109             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5110             SendToProgram(buf, cps);
5111             return;
5112         }
5113         // null move in variant where engine does not understand it (for analysis purposes)
5114         SendBoard(cps, moveNum + 1); // send position after move in stead.
5115         return;
5116     }
5117     if (cps->useUsermove) {
5118       SendToProgram("usermove ", cps);
5119     }
5120     if (cps->useSAN) {
5121       char *space;
5122       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5123         int len = space - parseList[moveNum];
5124         memcpy(buf, parseList[moveNum], len);
5125         buf[len++] = '\n';
5126         buf[len] = NULLCHAR;
5127       } else {
5128         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5129       }
5130       SendToProgram(buf, cps);
5131     } else {
5132       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5133         AlphaRank(moveList[moveNum], 4);
5134         SendToProgram(moveList[moveNum], cps);
5135         AlphaRank(moveList[moveNum], 4); // and back
5136       } else
5137       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5138        * the engine. It would be nice to have a better way to identify castle
5139        * moves here. */
5140       if(appData.fischerCastling && cps->useOOCastle) {
5141         int fromX = moveList[moveNum][0] - AAA;
5142         int fromY = moveList[moveNum][1] - ONE;
5143         int toX = moveList[moveNum][2] - AAA;
5144         int toY = moveList[moveNum][3] - ONE;
5145         if((boards[moveNum][fromY][fromX] == WhiteKing
5146             && boards[moveNum][toY][toX] == WhiteRook)
5147            || (boards[moveNum][fromY][fromX] == BlackKing
5148                && boards[moveNum][toY][toX] == BlackRook)) {
5149           if(toX > fromX) SendToProgram("O-O\n", cps);
5150           else SendToProgram("O-O-O\n", cps);
5151         }
5152         else SendToProgram(moveList[moveNum], cps);
5153       } else
5154       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5155         char *m = moveList[moveNum];
5156         static char c[2];
5157         *c = m[7]; // promoChar
5158         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
5159           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5160                                                m[2], m[3] - '0',
5161                                                m[5], m[6] - '0',
5162                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5163         else
5164           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
5165                                                m[5], m[6] - '0',
5166                                                m[5], m[6] - '0',
5167                                                m[2], m[3] - '0', c);
5168           SendToProgram(buf, cps);
5169       } else
5170       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5171         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5172           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5173           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5174                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5175         } else
5176           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5177                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5178         SendToProgram(buf, cps);
5179       }
5180       else SendToProgram(moveList[moveNum], cps);
5181       /* End of additions by Tord */
5182     }
5183
5184     /* [HGM] setting up the opening has brought engine in force mode! */
5185     /*       Send 'go' if we are in a mode where machine should play. */
5186     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5187         (gameMode == TwoMachinesPlay   ||
5188 #if ZIPPY
5189          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5190 #endif
5191          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5192         SendToProgram("go\n", cps);
5193   if (appData.debugMode) {
5194     fprintf(debugFP, "(extra)\n");
5195   }
5196     }
5197     setboardSpoiledMachineBlack = 0;
5198 }
5199
5200 void
5201 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5202 {
5203     char user_move[MSG_SIZ];
5204     char suffix[4];
5205
5206     if(gameInfo.variant == VariantSChess && promoChar) {
5207         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5208         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5209     } else suffix[0] = NULLCHAR;
5210
5211     switch (moveType) {
5212       default:
5213         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5214                 (int)moveType, fromX, fromY, toX, toY);
5215         DisplayError(user_move + strlen("say "), 0);
5216         break;
5217       case WhiteKingSideCastle:
5218       case BlackKingSideCastle:
5219       case WhiteQueenSideCastleWild:
5220       case BlackQueenSideCastleWild:
5221       /* PUSH Fabien */
5222       case WhiteHSideCastleFR:
5223       case BlackHSideCastleFR:
5224       /* POP Fabien */
5225         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5226         break;
5227       case WhiteQueenSideCastle:
5228       case BlackQueenSideCastle:
5229       case WhiteKingSideCastleWild:
5230       case BlackKingSideCastleWild:
5231       /* PUSH Fabien */
5232       case WhiteASideCastleFR:
5233       case BlackASideCastleFR:
5234       /* POP Fabien */
5235         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5236         break;
5237       case WhiteNonPromotion:
5238       case BlackNonPromotion:
5239         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5240         break;
5241       case WhitePromotion:
5242       case BlackPromotion:
5243         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5244            gameInfo.variant == VariantMakruk)
5245           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5246                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5247                 PieceToChar(WhiteFerz));
5248         else if(gameInfo.variant == VariantGreat)
5249           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5250                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5251                 PieceToChar(WhiteMan));
5252         else
5253           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5254                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5255                 promoChar);
5256         break;
5257       case WhiteDrop:
5258       case BlackDrop:
5259       drop:
5260         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5261                  ToUpper(PieceToChar((ChessSquare) fromX)),
5262                  AAA + toX, ONE + toY);
5263         break;
5264       case IllegalMove:  /* could be a variant we don't quite understand */
5265         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5266       case NormalMove:
5267       case WhiteCapturesEnPassant:
5268       case BlackCapturesEnPassant:
5269         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5270                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5271         break;
5272     }
5273     SendToICS(user_move);
5274     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5275         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5276 }
5277
5278 void
5279 UploadGameEvent ()
5280 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5281     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5282     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5283     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5284       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5285       return;
5286     }
5287     if(gameMode != IcsExamining) { // is this ever not the case?
5288         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5289
5290         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5291           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5292         } else { // on FICS we must first go to general examine mode
5293           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5294         }
5295         if(gameInfo.variant != VariantNormal) {
5296             // try figure out wild number, as xboard names are not always valid on ICS
5297             for(i=1; i<=36; i++) {
5298               snprintf(buf, MSG_SIZ, "wild/%d", i);
5299                 if(StringToVariant(buf) == gameInfo.variant) break;
5300             }
5301             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5302             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5303             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5304         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5305         SendToICS(ics_prefix);
5306         SendToICS(buf);
5307         if(startedFromSetupPosition || backwardMostMove != 0) {
5308           fen = PositionToFEN(backwardMostMove, NULL, 1);
5309           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5310             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5311             SendToICS(buf);
5312           } else { // FICS: everything has to set by separate bsetup commands
5313             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5314             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5315             SendToICS(buf);
5316             if(!WhiteOnMove(backwardMostMove)) {
5317                 SendToICS("bsetup tomove black\n");
5318             }
5319             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5320             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5321             SendToICS(buf);
5322             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5323             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5324             SendToICS(buf);
5325             i = boards[backwardMostMove][EP_STATUS];
5326             if(i >= 0) { // set e.p.
5327               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5328                 SendToICS(buf);
5329             }
5330             bsetup++;
5331           }
5332         }
5333       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5334             SendToICS("bsetup done\n"); // switch to normal examining.
5335     }
5336     for(i = backwardMostMove; i<last; i++) {
5337         char buf[20];
5338         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5339         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5340             int len = strlen(moveList[i]);
5341             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5342             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5343         }
5344         SendToICS(buf);
5345     }
5346     SendToICS(ics_prefix);
5347     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5348 }
5349
5350 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5351 int legNr = 1;
5352
5353 void
5354 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5355 {
5356     if (rf == DROP_RANK) {
5357       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5358       sprintf(move, "%c@%c%c\n",
5359                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5360     } else {
5361         if (promoChar == 'x' || promoChar == NULLCHAR) {
5362           sprintf(move, "%c%c%c%c\n",
5363                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5364           if(killX >= 0 && killY >= 0) {
5365             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5366             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5367           }
5368         } else {
5369             sprintf(move, "%c%c%c%c%c\n",
5370                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5371           if(killX >= 0 && killY >= 0) {
5372             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5373             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + killX, ONE + killY, promoChar);
5374           }
5375         }
5376     }
5377 }
5378
5379 void
5380 ProcessICSInitScript (FILE *f)
5381 {
5382     char buf[MSG_SIZ];
5383
5384     while (fgets(buf, MSG_SIZ, f)) {
5385         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5386     }
5387
5388     fclose(f);
5389 }
5390
5391
5392 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5393 int dragging;
5394 static ClickType lastClickType;
5395
5396 int
5397 PieceInString (char *s, ChessSquare piece)
5398 {
5399   char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
5400   while((p = strchr(s, ID))) {
5401     if(!suffix || p[1] == suffix) return TRUE;
5402     s = p;
5403   }
5404   return FALSE;
5405 }
5406
5407 int
5408 Partner (ChessSquare *p)
5409 { // change piece into promotion partner if one shogi-promotes to the other
5410   ChessSquare partner = promoPartner[*p];
5411   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5412   if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
5413   *p = partner;
5414   return 1;
5415 }
5416
5417 void
5418 Sweep (int step)
5419 {
5420     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5421     static int toggleFlag;
5422     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5423     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5424     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5425     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5426     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5427     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5428     do {
5429         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5430         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5431         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5432         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5433         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5434         if(!step) step = -1;
5435     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5436             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5437             promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
5438             promoSweep == pawn ||
5439             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5440             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5441     if(toX >= 0) {
5442         int victim = boards[currentMove][toY][toX];
5443         boards[currentMove][toY][toX] = promoSweep;
5444         DrawPosition(FALSE, boards[currentMove]);
5445         boards[currentMove][toY][toX] = victim;
5446     } else
5447     ChangeDragPiece(promoSweep);
5448 }
5449
5450 int
5451 PromoScroll (int x, int y)
5452 {
5453   int step = 0;
5454
5455   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5456   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5457   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5458   if(!step) return FALSE;
5459   lastX = x; lastY = y;
5460   if((promoSweep < BlackPawn) == flipView) step = -step;
5461   if(step > 0) selectFlag = 1;
5462   if(!selectFlag) Sweep(step);
5463   return FALSE;
5464 }
5465
5466 void
5467 NextPiece (int step)
5468 {
5469     ChessSquare piece = boards[currentMove][toY][toX];
5470     do {
5471         pieceSweep -= step;
5472         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5473         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5474         if(!step) step = -1;
5475     } while(PieceToChar(pieceSweep) == '.');
5476     boards[currentMove][toY][toX] = pieceSweep;
5477     DrawPosition(FALSE, boards[currentMove]);
5478     boards[currentMove][toY][toX] = piece;
5479 }
5480 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5481 void
5482 AlphaRank (char *move, int n)
5483 {
5484 //    char *p = move, c; int x, y;
5485
5486     if (appData.debugMode) {
5487         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5488     }
5489
5490     if(move[1]=='*' &&
5491        move[2]>='0' && move[2]<='9' &&
5492        move[3]>='a' && move[3]<='x'    ) {
5493         move[1] = '@';
5494         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5495         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5496     } else
5497     if(move[0]>='0' && move[0]<='9' &&
5498        move[1]>='a' && move[1]<='x' &&
5499        move[2]>='0' && move[2]<='9' &&
5500        move[3]>='a' && move[3]<='x'    ) {
5501         /* input move, Shogi -> normal */
5502         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5503         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5504         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5505         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5506     } else
5507     if(move[1]=='@' &&
5508        move[3]>='0' && move[3]<='9' &&
5509        move[2]>='a' && move[2]<='x'    ) {
5510         move[1] = '*';
5511         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5512         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5513     } else
5514     if(
5515        move[0]>='a' && move[0]<='x' &&
5516        move[3]>='0' && move[3]<='9' &&
5517        move[2]>='a' && move[2]<='x'    ) {
5518          /* output move, normal -> Shogi */
5519         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5520         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5521         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5522         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5523         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5524     }
5525     if (appData.debugMode) {
5526         fprintf(debugFP, "   out = '%s'\n", move);
5527     }
5528 }
5529
5530 char yy_textstr[8000];
5531
5532 /* Parser for moves from gnuchess, ICS, or user typein box */
5533 Boolean
5534 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5535 {
5536     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5537
5538     switch (*moveType) {
5539       case WhitePromotion:
5540       case BlackPromotion:
5541       case WhiteNonPromotion:
5542       case BlackNonPromotion:
5543       case NormalMove:
5544       case FirstLeg:
5545       case WhiteCapturesEnPassant:
5546       case BlackCapturesEnPassant:
5547       case WhiteKingSideCastle:
5548       case WhiteQueenSideCastle:
5549       case BlackKingSideCastle:
5550       case BlackQueenSideCastle:
5551       case WhiteKingSideCastleWild:
5552       case WhiteQueenSideCastleWild:
5553       case BlackKingSideCastleWild:
5554       case BlackQueenSideCastleWild:
5555       /* Code added by Tord: */
5556       case WhiteHSideCastleFR:
5557       case WhiteASideCastleFR:
5558       case BlackHSideCastleFR:
5559       case BlackASideCastleFR:
5560       /* End of code added by Tord */
5561       case IllegalMove:         /* bug or odd chess variant */
5562         if(currentMoveString[1] == '@') { // illegal drop
5563           *fromX = WhiteOnMove(moveNum) ?
5564             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5565             (int) CharToPiece(ToLower(currentMoveString[0]));
5566           goto drop;
5567         }
5568         *fromX = currentMoveString[0] - AAA;
5569         *fromY = currentMoveString[1] - ONE;
5570         *toX = currentMoveString[2] - AAA;
5571         *toY = currentMoveString[3] - ONE;
5572         *promoChar = currentMoveString[4];
5573         if(*promoChar == ';') *promoChar = currentMoveString[7];
5574         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5575             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5576     if (appData.debugMode) {
5577         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5578     }
5579             *fromX = *fromY = *toX = *toY = 0;
5580             return FALSE;
5581         }
5582         if (appData.testLegality) {
5583           return (*moveType != IllegalMove);
5584         } else {
5585           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5586                          // [HGM] lion: if this is a double move we are less critical
5587                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5588         }
5589
5590       case WhiteDrop:
5591       case BlackDrop:
5592         *fromX = *moveType == WhiteDrop ?
5593           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5594           (int) CharToPiece(ToLower(currentMoveString[0]));
5595       drop:
5596         *fromY = DROP_RANK;
5597         *toX = currentMoveString[2] - AAA;
5598         *toY = currentMoveString[3] - ONE;
5599         *promoChar = NULLCHAR;
5600         return TRUE;
5601
5602       case AmbiguousMove:
5603       case ImpossibleMove:
5604       case EndOfFile:
5605       case ElapsedTime:
5606       case Comment:
5607       case PGNTag:
5608       case NAG:
5609       case WhiteWins:
5610       case BlackWins:
5611       case GameIsDrawn:
5612       default:
5613     if (appData.debugMode) {
5614         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5615     }
5616         /* bug? */
5617         *fromX = *fromY = *toX = *toY = 0;
5618         *promoChar = NULLCHAR;
5619         return FALSE;
5620     }
5621 }
5622
5623 Boolean pushed = FALSE;
5624 char *lastParseAttempt;
5625
5626 void
5627 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5628 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5629   int fromX, fromY, toX, toY; char promoChar;
5630   ChessMove moveType;
5631   Boolean valid;
5632   int nr = 0;
5633
5634   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5635   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5636     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5637     pushed = TRUE;
5638   }
5639   endPV = forwardMostMove;
5640   do {
5641     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5642     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5643     lastParseAttempt = pv;
5644     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5645     if(!valid && nr == 0 &&
5646        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5647         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5648         // Hande case where played move is different from leading PV move
5649         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5650         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5651         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5652         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5653           endPV += 2; // if position different, keep this
5654           moveList[endPV-1][0] = fromX + AAA;
5655           moveList[endPV-1][1] = fromY + ONE;
5656           moveList[endPV-1][2] = toX + AAA;
5657           moveList[endPV-1][3] = toY + ONE;
5658           parseList[endPV-1][0] = NULLCHAR;
5659           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5660         }
5661       }
5662     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5663     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5664     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5665     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5666         valid++; // allow comments in PV
5667         continue;
5668     }
5669     nr++;
5670     if(endPV+1 > framePtr) break; // no space, truncate
5671     if(!valid) break;
5672     endPV++;
5673     CopyBoard(boards[endPV], boards[endPV-1]);
5674     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5675     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5676     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5677     CoordsToAlgebraic(boards[endPV - 1],
5678                              PosFlags(endPV - 1),
5679                              fromY, fromX, toY, toX, promoChar,
5680                              parseList[endPV - 1]);
5681   } while(valid);
5682   if(atEnd == 2) return; // used hidden, for PV conversion
5683   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5684   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5685   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5686                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5687   DrawPosition(TRUE, boards[currentMove]);
5688 }
5689
5690 int
5691 MultiPV (ChessProgramState *cps, int kind)
5692 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5693         int i;
5694         for(i=0; i<cps->nrOptions; i++) {
5695             char *s = cps->option[i].name;
5696             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5697             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5698                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5699         }
5700         return -1;
5701 }
5702
5703 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5704 static int multi, pv_margin;
5705 static ChessProgramState *activeCps;
5706
5707 Boolean
5708 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5709 {
5710         int startPV, lineStart, origIndex = index;
5711         char *p, buf2[MSG_SIZ];
5712         ChessProgramState *cps = (pane ? &second : &first);
5713
5714         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5715         lastX = x; lastY = y;
5716         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5717         lineStart = startPV = index;
5718         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5719         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5720         index = startPV;
5721         do{ while(buf[index] && buf[index] != '\n') index++;
5722         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5723         buf[index] = 0;
5724         if(lineStart == 0 && gameMode == AnalyzeMode) {
5725             int n = 0;
5726             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5727             if(n == 0) { // click not on "fewer" or "more"
5728                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5729                     pv_margin = cps->option[multi].value;
5730                     activeCps = cps; // non-null signals margin adjustment
5731                 }
5732             } else if((multi = MultiPV(cps, 1)) >= 0) {
5733                 n += cps->option[multi].value; if(n < 1) n = 1;
5734                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5735                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5736                 cps->option[multi].value = n;
5737                 *start = *end = 0;
5738                 return FALSE;
5739             }
5740         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5741                 ExcludeClick(origIndex - lineStart);
5742                 return FALSE;
5743         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5744                 Collapse(origIndex - lineStart);
5745                 return FALSE;
5746         }
5747         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5748         *start = startPV; *end = index-1;
5749         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5750         return TRUE;
5751 }
5752
5753 char *
5754 PvToSAN (char *pv)
5755 {
5756         static char buf[10*MSG_SIZ];
5757         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5758         *buf = NULLCHAR;
5759         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5760         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5761         for(i = forwardMostMove; i<endPV; i++){
5762             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5763             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5764             k += strlen(buf+k);
5765         }
5766         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5767         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5768         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5769         endPV = savedEnd;
5770         return buf;
5771 }
5772
5773 Boolean
5774 LoadPV (int x, int y)
5775 { // called on right mouse click to load PV
5776   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5777   lastX = x; lastY = y;
5778   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5779   extendGame = FALSE;
5780   return TRUE;
5781 }
5782
5783 void
5784 UnLoadPV ()
5785 {
5786   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5787   if(activeCps) {
5788     if(pv_margin != activeCps->option[multi].value) {
5789       char buf[MSG_SIZ];
5790       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5791       SendToProgram(buf, activeCps);
5792       activeCps->option[multi].value = pv_margin;
5793     }
5794     activeCps = NULL;
5795     return;
5796   }
5797   if(endPV < 0) return;
5798   if(appData.autoCopyPV) CopyFENToClipboard();
5799   endPV = -1;
5800   if(extendGame && currentMove > forwardMostMove) {
5801         Boolean saveAnimate = appData.animate;
5802         if(pushed) {
5803             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5804                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5805             } else storedGames--; // abandon shelved tail of original game
5806         }
5807         pushed = FALSE;
5808         forwardMostMove = currentMove;
5809         currentMove = oldFMM;
5810         appData.animate = FALSE;
5811         ToNrEvent(forwardMostMove);
5812         appData.animate = saveAnimate;
5813   }
5814   currentMove = forwardMostMove;
5815   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5816   ClearPremoveHighlights();
5817   DrawPosition(TRUE, boards[currentMove]);
5818 }
5819
5820 void
5821 MovePV (int x, int y, int h)
5822 { // step through PV based on mouse coordinates (called on mouse move)
5823   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5824
5825   if(activeCps) { // adjusting engine's multi-pv margin
5826     if(x > lastX) pv_margin++; else
5827     if(x < lastX) pv_margin -= (pv_margin > 0);
5828     if(x != lastX) {
5829       char buf[MSG_SIZ];
5830       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5831       DisplayMessage(buf, "");
5832     }
5833     lastX = x;
5834     return;
5835   }
5836   // we must somehow check if right button is still down (might be released off board!)
5837   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5838   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5839   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5840   if(!step) return;
5841   lastX = x; lastY = y;
5842
5843   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5844   if(endPV < 0) return;
5845   if(y < margin) step = 1; else
5846   if(y > h - margin) step = -1;
5847   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5848   currentMove += step;
5849   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5850   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5851                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5852   DrawPosition(FALSE, boards[currentMove]);
5853 }
5854
5855
5856 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5857 // All positions will have equal probability, but the current method will not provide a unique
5858 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5859 #define DARK 1
5860 #define LITE 2
5861 #define ANY 3
5862
5863 int squaresLeft[4];
5864 int piecesLeft[(int)BlackPawn];
5865 int seed, nrOfShuffles;
5866
5867 void
5868 GetPositionNumber ()
5869 {       // sets global variable seed
5870         int i;
5871
5872         seed = appData.defaultFrcPosition;
5873         if(seed < 0) { // randomize based on time for negative FRC position numbers
5874                 for(i=0; i<50; i++) seed += random();
5875                 seed = random() ^ random() >> 8 ^ random() << 8;
5876                 if(seed<0) seed = -seed;
5877         }
5878 }
5879
5880 int
5881 put (Board board, int pieceType, int rank, int n, int shade)
5882 // put the piece on the (n-1)-th empty squares of the given shade
5883 {
5884         int i;
5885
5886         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5887                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5888                         board[rank][i] = (ChessSquare) pieceType;
5889                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5890                         squaresLeft[ANY]--;
5891                         piecesLeft[pieceType]--;
5892                         return i;
5893                 }
5894         }
5895         return -1;
5896 }
5897
5898
5899 void
5900 AddOnePiece (Board board, int pieceType, int rank, int shade)
5901 // calculate where the next piece goes, (any empty square), and put it there
5902 {
5903         int i;
5904
5905         i = seed % squaresLeft[shade];
5906         nrOfShuffles *= squaresLeft[shade];
5907         seed /= squaresLeft[shade];
5908         put(board, pieceType, rank, i, shade);
5909 }
5910
5911 void
5912 AddTwoPieces (Board board, int pieceType, int rank)
5913 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5914 {
5915         int i, n=squaresLeft[ANY], j=n-1, k;
5916
5917         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5918         i = seed % k;  // pick one
5919         nrOfShuffles *= k;
5920         seed /= k;
5921         while(i >= j) i -= j--;
5922         j = n - 1 - j; i += j;
5923         put(board, pieceType, rank, j, ANY);
5924         put(board, pieceType, rank, i, ANY);
5925 }
5926
5927 void
5928 SetUpShuffle (Board board, int number)
5929 {
5930         int i, p, first=1;
5931
5932         GetPositionNumber(); nrOfShuffles = 1;
5933
5934         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5935         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5936         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5937
5938         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5939
5940         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5941             p = (int) board[0][i];
5942             if(p < (int) BlackPawn) piecesLeft[p] ++;
5943             board[0][i] = EmptySquare;
5944         }
5945
5946         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5947             // shuffles restricted to allow normal castling put KRR first
5948             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5949                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5950             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5951                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5952             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5953                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5954             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5955                 put(board, WhiteRook, 0, 0, ANY);
5956             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5957         }
5958
5959         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5960             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5961             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5962                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5963                 while(piecesLeft[p] >= 2) {
5964                     AddOnePiece(board, p, 0, LITE);
5965                     AddOnePiece(board, p, 0, DARK);
5966                 }
5967                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5968             }
5969
5970         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5971             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5972             // but we leave King and Rooks for last, to possibly obey FRC restriction
5973             if(p == (int)WhiteRook) continue;
5974             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5975             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5976         }
5977
5978         // now everything is placed, except perhaps King (Unicorn) and Rooks
5979
5980         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5981             // Last King gets castling rights
5982             while(piecesLeft[(int)WhiteUnicorn]) {
5983                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5984                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5985             }
5986
5987             while(piecesLeft[(int)WhiteKing]) {
5988                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5989                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5990             }
5991
5992
5993         } else {
5994             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5995             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5996         }
5997
5998         // Only Rooks can be left; simply place them all
5999         while(piecesLeft[(int)WhiteRook]) {
6000                 i = put(board, WhiteRook, 0, 0, ANY);
6001                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
6002                         if(first) {
6003                                 first=0;
6004                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
6005                         }
6006                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
6007                 }
6008         }
6009         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
6010             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
6011         }
6012
6013         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
6014 }
6015
6016 int
6017 ptclen (const char *s, char *escapes)
6018 {
6019     int n = 0;
6020     if(!*escapes) return strlen(s);
6021     while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
6022     return n;
6023 }
6024
6025 int
6026 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6027 /* [HGM] moved here from winboard.c because of its general usefulness */
6028 /*       Basically a safe strcpy that uses the last character as King */
6029 {
6030     int result = FALSE; int NrPieces;
6031     unsigned char partner[EmptySquare];
6032
6033     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6034                     && NrPieces >= 12 && !(NrPieces&1)) {
6035         int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
6036
6037         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6038         for( i=offs=0; i<NrPieces/2-1; i++ ) {
6039             char *p, c=0;
6040             if(map[j] == '/') offs = WhitePBishop - i, j++;
6041             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6042             table[i+offs] = map[j++];
6043             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6044             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6045             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6046         }
6047         table[(int) WhiteKing]  = map[j++];
6048         for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
6049             char *p, c=0;
6050             if(map[j] == '/') offs = WhitePBishop - ii, j++;
6051             i = WHITE_TO_BLACK ii;
6052             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6053             table[i+offs] = map[j++];
6054             if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
6055             if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
6056             if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
6057         }
6058         table[(int) BlackKing]  = map[j++];
6059
6060
6061         if(*escapes) { // set up promotion pairing
6062             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6063             // pieceToChar entirely filled, so we can look up specified partners
6064             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6065                 int c = table[i];
6066                 if(c == '^' || c == '-') { // has specified partner
6067                     int p;
6068                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6069                     if(c == '^') table[i] = '+';
6070                     if(p < EmptySquare) {
6071                         if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
6072                         if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
6073                         promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
6074                     }
6075                 } else if(c == '*') {
6076                     table[i] = partner[i];
6077                     promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6078                 }
6079             }
6080         }
6081
6082         result = TRUE;
6083     }
6084
6085     return result;
6086 }
6087
6088 int
6089 SetCharTable (unsigned char *table, const char * map)
6090 {
6091     return SetCharTableEsc(table, map, "");
6092 }
6093
6094 void
6095 Prelude (Board board)
6096 {       // [HGM] superchess: random selection of exo-pieces
6097         int i, j, k; ChessSquare p;
6098         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6099
6100         GetPositionNumber(); // use FRC position number
6101
6102         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6103             SetCharTable(pieceToChar, appData.pieceToCharTable);
6104             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6105                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6106         }
6107
6108         j = seed%4;                 seed /= 4;
6109         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6110         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6111         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6112         j = seed%3 + (seed%3 >= j); seed /= 3;
6113         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6114         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6115         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6116         j = seed%3;                 seed /= 3;
6117         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6118         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6119         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6120         j = seed%2 + (seed%2 >= j); seed /= 2;
6121         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6122         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6123         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6124         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6125         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6126         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6127         put(board, exoPieces[0],    0, 0, ANY);
6128         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6129 }
6130
6131 void
6132 InitPosition (int redraw)
6133 {
6134     ChessSquare (* pieces)[BOARD_FILES];
6135     int i, j, pawnRow=1, pieceRows=1, overrule,
6136     oldx = gameInfo.boardWidth,
6137     oldy = gameInfo.boardHeight,
6138     oldh = gameInfo.holdingsWidth;
6139     static int oldv;
6140
6141     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6142
6143     /* [AS] Initialize pv info list [HGM] and game status */
6144     {
6145         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6146             pvInfoList[i].depth = 0;
6147             boards[i][EP_STATUS] = EP_NONE;
6148             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6149         }
6150
6151         initialRulePlies = 0; /* 50-move counter start */
6152
6153         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6154         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6155     }
6156
6157
6158     /* [HGM] logic here is completely changed. In stead of full positions */
6159     /* the initialized data only consist of the two backranks. The switch */
6160     /* selects which one we will use, which is than copied to the Board   */
6161     /* initialPosition, which for the rest is initialized by Pawns and    */
6162     /* empty squares. This initial position is then copied to boards[0],  */
6163     /* possibly after shuffling, so that it remains available.            */
6164
6165     gameInfo.holdingsWidth = 0; /* default board sizes */
6166     gameInfo.boardWidth    = 8;
6167     gameInfo.boardHeight   = 8;
6168     gameInfo.holdingsSize  = 0;
6169     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6170     for(i=0; i<BOARD_FILES-6; i++)
6171       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6172     initialPosition[EP_STATUS] = EP_NONE;
6173     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6174     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6175     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6176          SetCharTable(pieceNickName, appData.pieceNickNames);
6177     else SetCharTable(pieceNickName, "............");
6178     pieces = FIDEArray;
6179
6180     switch (gameInfo.variant) {
6181     case VariantFischeRandom:
6182       shuffleOpenings = TRUE;
6183       appData.fischerCastling = TRUE;
6184     default:
6185       break;
6186     case VariantShatranj:
6187       pieces = ShatranjArray;
6188       nrCastlingRights = 0;
6189       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6190       break;
6191     case VariantMakruk:
6192       pieces = makrukArray;
6193       nrCastlingRights = 0;
6194       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6195       break;
6196     case VariantASEAN:
6197       pieces = aseanArray;
6198       nrCastlingRights = 0;
6199       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6200       break;
6201     case VariantTwoKings:
6202       pieces = twoKingsArray;
6203       break;
6204     case VariantGrand:
6205       pieces = GrandArray;
6206       nrCastlingRights = 0;
6207       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6208       gameInfo.boardWidth = 10;
6209       gameInfo.boardHeight = 10;
6210       gameInfo.holdingsSize = 7;
6211       break;
6212     case VariantCapaRandom:
6213       shuffleOpenings = TRUE;
6214       appData.fischerCastling = TRUE;
6215     case VariantCapablanca:
6216       pieces = CapablancaArray;
6217       gameInfo.boardWidth = 10;
6218       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6219       break;
6220     case VariantGothic:
6221       pieces = GothicArray;
6222       gameInfo.boardWidth = 10;
6223       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6224       break;
6225     case VariantSChess:
6226       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6227       gameInfo.holdingsSize = 7;
6228       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6229       break;
6230     case VariantJanus:
6231       pieces = JanusArray;
6232       gameInfo.boardWidth = 10;
6233       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6234       nrCastlingRights = 6;
6235         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6236         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6237         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6238         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6239         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6240         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6241       break;
6242     case VariantFalcon:
6243       pieces = FalconArray;
6244       gameInfo.boardWidth = 10;
6245       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6246       break;
6247     case VariantXiangqi:
6248       pieces = XiangqiArray;
6249       gameInfo.boardWidth  = 9;
6250       gameInfo.boardHeight = 10;
6251       nrCastlingRights = 0;
6252       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6253       break;
6254     case VariantShogi:
6255       pieces = ShogiArray;
6256       gameInfo.boardWidth  = 9;
6257       gameInfo.boardHeight = 9;
6258       gameInfo.holdingsSize = 7;
6259       nrCastlingRights = 0;
6260       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6261       break;
6262     case VariantChu:
6263       pieces = ChuArray; pieceRows = 3;
6264       gameInfo.boardWidth  = 12;
6265       gameInfo.boardHeight = 12;
6266       nrCastlingRights = 0;
6267       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
6268                                    "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
6269       break;
6270     case VariantCourier:
6271       pieces = CourierArray;
6272       gameInfo.boardWidth  = 12;
6273       nrCastlingRights = 0;
6274       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6275       break;
6276     case VariantKnightmate:
6277       pieces = KnightmateArray;
6278       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6279       break;
6280     case VariantSpartan:
6281       pieces = SpartanArray;
6282       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6283       break;
6284     case VariantLion:
6285       pieces = lionArray;
6286       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6287       break;
6288     case VariantChuChess:
6289       pieces = ChuChessArray;
6290       gameInfo.boardWidth = 10;
6291       gameInfo.boardHeight = 10;
6292       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6293       break;
6294     case VariantFairy:
6295       pieces = fairyArray;
6296       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6297       break;
6298     case VariantGreat:
6299       pieces = GreatArray;
6300       gameInfo.boardWidth = 10;
6301       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6302       gameInfo.holdingsSize = 8;
6303       break;
6304     case VariantSuper:
6305       pieces = FIDEArray;
6306       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6307       gameInfo.holdingsSize = 8;
6308       startedFromSetupPosition = TRUE;
6309       break;
6310     case VariantCrazyhouse:
6311     case VariantBughouse:
6312       pieces = FIDEArray;
6313       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6314       gameInfo.holdingsSize = 5;
6315       break;
6316     case VariantWildCastle:
6317       pieces = FIDEArray;
6318       /* !!?shuffle with kings guaranteed to be on d or e file */
6319       shuffleOpenings = 1;
6320       break;
6321     case VariantNoCastle:
6322       pieces = FIDEArray;
6323       nrCastlingRights = 0;
6324       /* !!?unconstrained back-rank shuffle */
6325       shuffleOpenings = 1;
6326       break;
6327     }
6328
6329     overrule = 0;
6330     if(appData.NrFiles >= 0) {
6331         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6332         gameInfo.boardWidth = appData.NrFiles;
6333     }
6334     if(appData.NrRanks >= 0) {
6335         gameInfo.boardHeight = appData.NrRanks;
6336     }
6337     if(appData.holdingsSize >= 0) {
6338         i = appData.holdingsSize;
6339         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6340         gameInfo.holdingsSize = i;
6341     }
6342     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6343     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6344         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6345
6346     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6347     if(pawnRow < 1) pawnRow = 1;
6348     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6349        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6350     if(gameInfo.variant == VariantChu) pawnRow = 3;
6351
6352     /* User pieceToChar list overrules defaults */
6353     if(appData.pieceToCharTable != NULL)
6354         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6355
6356     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6357
6358         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6359             s = (ChessSquare) 0; /* account holding counts in guard band */
6360         for( i=0; i<BOARD_HEIGHT; i++ )
6361             initialPosition[i][j] = s;
6362
6363         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6364         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6365         initialPosition[pawnRow][j] = WhitePawn;
6366         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6367         if(gameInfo.variant == VariantXiangqi) {
6368             if(j&1) {
6369                 initialPosition[pawnRow][j] =
6370                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6371                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6372                    initialPosition[2][j] = WhiteCannon;
6373                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6374                 }
6375             }
6376         }
6377         if(gameInfo.variant == VariantChu) {
6378              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6379                initialPosition[pawnRow+1][j] = WhiteCobra,
6380                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6381              for(i=1; i<pieceRows; i++) {
6382                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6383                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6384              }
6385         }
6386         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6387             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6388                initialPosition[0][j] = WhiteRook;
6389                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6390             }
6391         }
6392         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6393     }
6394     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6395     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6396
6397             j=BOARD_LEFT+1;
6398             initialPosition[1][j] = WhiteBishop;
6399             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6400             j=BOARD_RGHT-2;
6401             initialPosition[1][j] = WhiteRook;
6402             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6403     }
6404
6405     if( nrCastlingRights == -1) {
6406         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6407         /*       This sets default castling rights from none to normal corners   */
6408         /* Variants with other castling rights must set them themselves above    */
6409         nrCastlingRights = 6;
6410
6411         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6412         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6413         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6414         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6415         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6416         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6417      }
6418
6419      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6420      if(gameInfo.variant == VariantGreat) { // promotion commoners
6421         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6422         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6423         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6424         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6425      }
6426      if( gameInfo.variant == VariantSChess ) {
6427       initialPosition[1][0] = BlackMarshall;
6428       initialPosition[2][0] = BlackAngel;
6429       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6430       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6431       initialPosition[1][1] = initialPosition[2][1] =
6432       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6433      }
6434   if (appData.debugMode) {
6435     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6436   }
6437     if(shuffleOpenings) {
6438         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6439         startedFromSetupPosition = TRUE;
6440     }
6441     if(startedFromPositionFile) {
6442       /* [HGM] loadPos: use PositionFile for every new game */
6443       CopyBoard(initialPosition, filePosition);
6444       for(i=0; i<nrCastlingRights; i++)
6445           initialRights[i] = filePosition[CASTLING][i];
6446       startedFromSetupPosition = TRUE;
6447     }
6448
6449     CopyBoard(boards[0], initialPosition);
6450
6451     if(oldx != gameInfo.boardWidth ||
6452        oldy != gameInfo.boardHeight ||
6453        oldv != gameInfo.variant ||
6454        oldh != gameInfo.holdingsWidth
6455                                          )
6456             InitDrawingSizes(-2 ,0);
6457
6458     oldv = gameInfo.variant;
6459     if (redraw)
6460       DrawPosition(TRUE, boards[currentMove]);
6461 }
6462
6463 void
6464 SendBoard (ChessProgramState *cps, int moveNum)
6465 {
6466     char message[MSG_SIZ];
6467
6468     if (cps->useSetboard) {
6469       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6470       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6471       SendToProgram(message, cps);
6472       free(fen);
6473
6474     } else {
6475       ChessSquare *bp;
6476       int i, j, left=0, right=BOARD_WIDTH;
6477       /* Kludge to set black to move, avoiding the troublesome and now
6478        * deprecated "black" command.
6479        */
6480       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6481         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6482
6483       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6484
6485       SendToProgram("edit\n", cps);
6486       SendToProgram("#\n", cps);
6487       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6488         bp = &boards[moveNum][i][left];
6489         for (j = left; j < right; j++, bp++) {
6490           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6491           if ((int) *bp < (int) BlackPawn) {
6492             if(j == BOARD_RGHT+1)
6493                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6494             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6495             if(message[0] == '+' || message[0] == '~') {
6496               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6497                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6498                         AAA + j, ONE + i - '0');
6499             }
6500             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6501                 message[1] = BOARD_RGHT   - 1 - j + '1';
6502                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6503             }
6504             SendToProgram(message, cps);
6505           }
6506         }
6507       }
6508
6509       SendToProgram("c\n", cps);
6510       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6511         bp = &boards[moveNum][i][left];
6512         for (j = left; j < right; j++, bp++) {
6513           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6514           if (((int) *bp != (int) EmptySquare)
6515               && ((int) *bp >= (int) BlackPawn)) {
6516             if(j == BOARD_LEFT-2)
6517                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6518             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6519                     AAA + j, ONE + i - '0');
6520             if(message[0] == '+' || message[0] == '~') {
6521               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6522                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6523                         AAA + j, ONE + i - '0');
6524             }
6525             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6526                 message[1] = BOARD_RGHT   - 1 - j + '1';
6527                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6528             }
6529             SendToProgram(message, cps);
6530           }
6531         }
6532       }
6533
6534       SendToProgram(".\n", cps);
6535     }
6536     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6537 }
6538
6539 char exclusionHeader[MSG_SIZ];
6540 int exCnt, excludePtr;
6541 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6542 static Exclusion excluTab[200];
6543 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6544
6545 static void
6546 WriteMap (int s)
6547 {
6548     int j;
6549     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6550     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6551 }
6552
6553 static void
6554 ClearMap ()
6555 {
6556     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6557     excludePtr = 24; exCnt = 0;
6558     WriteMap(0);
6559 }
6560
6561 static void
6562 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6563 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6564     char buf[2*MOVE_LEN], *p;
6565     Exclusion *e = excluTab;
6566     int i;
6567     for(i=0; i<exCnt; i++)
6568         if(e[i].ff == fromX && e[i].fr == fromY &&
6569            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6570     if(i == exCnt) { // was not in exclude list; add it
6571         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6572         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6573             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6574             return; // abort
6575         }
6576         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6577         excludePtr++; e[i].mark = excludePtr++;
6578         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6579         exCnt++;
6580     }
6581     exclusionHeader[e[i].mark] = state;
6582 }
6583
6584 static int
6585 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6586 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6587     char buf[MSG_SIZ];
6588     int j, k;
6589     ChessMove moveType;
6590     if((signed char)promoChar == -1) { // kludge to indicate best move
6591         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6592             return 1; // if unparsable, abort
6593     }
6594     // update exclusion map (resolving toggle by consulting existing state)
6595     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6596     j = k%8; k >>= 3;
6597     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6598     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6599          excludeMap[k] |=   1<<j;
6600     else excludeMap[k] &= ~(1<<j);
6601     // update header
6602     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6603     // inform engine
6604     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6605     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6606     SendToBoth(buf);
6607     return (state == '+');
6608 }
6609
6610 static void
6611 ExcludeClick (int index)
6612 {
6613     int i, j;
6614     Exclusion *e = excluTab;
6615     if(index < 25) { // none, best or tail clicked
6616         if(index < 13) { // none: include all
6617             WriteMap(0); // clear map
6618             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6619             SendToBoth("include all\n"); // and inform engine
6620         } else if(index > 18) { // tail
6621             if(exclusionHeader[19] == '-') { // tail was excluded
6622                 SendToBoth("include all\n");
6623                 WriteMap(0); // clear map completely
6624                 // now re-exclude selected moves
6625                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6626                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6627             } else { // tail was included or in mixed state
6628                 SendToBoth("exclude all\n");
6629                 WriteMap(0xFF); // fill map completely
6630                 // now re-include selected moves
6631                 j = 0; // count them
6632                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6633                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6634                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6635             }
6636         } else { // best
6637             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6638         }
6639     } else {
6640         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6641             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6642             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6643             break;
6644         }
6645     }
6646 }
6647
6648 ChessSquare
6649 DefaultPromoChoice (int white)
6650 {
6651     ChessSquare result;
6652     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6653        gameInfo.variant == VariantMakruk)
6654         result = WhiteFerz; // no choice
6655     else if(gameInfo.variant == VariantASEAN)
6656         result = WhiteRook; // no choice
6657     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6658         result= WhiteKing; // in Suicide Q is the last thing we want
6659     else if(gameInfo.variant == VariantSpartan)
6660         result = white ? WhiteQueen : WhiteAngel;
6661     else result = WhiteQueen;
6662     if(!white) result = WHITE_TO_BLACK result;
6663     return result;
6664 }
6665
6666 static int autoQueen; // [HGM] oneclick
6667
6668 int
6669 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6670 {
6671     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6672     /* [HGM] add Shogi promotions */
6673     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6674     ChessSquare piece, partner;
6675     ChessMove moveType;
6676     Boolean premove;
6677
6678     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6679     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6680
6681     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6682       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6683         return FALSE;
6684
6685     piece = boards[currentMove][fromY][fromX];
6686     if(gameInfo.variant == VariantChu) {
6687         promotionZoneSize = BOARD_HEIGHT/3;
6688         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6689     } else if(gameInfo.variant == VariantShogi) {
6690         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6691         highestPromotingPiece = (int)WhiteAlfil;
6692     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6693         promotionZoneSize = 3;
6694     }
6695
6696     // Treat Lance as Pawn when it is not representing Amazon or Lance
6697     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6698         if(piece == WhiteLance) piece = WhitePawn; else
6699         if(piece == BlackLance) piece = BlackPawn;
6700     }
6701
6702     // next weed out all moves that do not touch the promotion zone at all
6703     if((int)piece >= BlackPawn) {
6704         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6705              return FALSE;
6706         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6707         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6708     } else {
6709         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6710            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6711         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6712              return FALSE;
6713     }
6714
6715     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6716
6717     // weed out mandatory Shogi promotions
6718     if(gameInfo.variant == VariantShogi) {
6719         if(piece >= BlackPawn) {
6720             if(toY == 0 && piece == BlackPawn ||
6721                toY == 0 && piece == BlackQueen ||
6722                toY <= 1 && piece == BlackKnight) {
6723                 *promoChoice = '+';
6724                 return FALSE;
6725             }
6726         } else {
6727             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6728                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6729                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6730                 *promoChoice = '+';
6731                 return FALSE;
6732             }
6733         }
6734     }
6735
6736     // weed out obviously illegal Pawn moves
6737     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6738         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6739         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6740         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6741         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6742         // note we are not allowed to test for valid (non-)capture, due to premove
6743     }
6744
6745     // we either have a choice what to promote to, or (in Shogi) whether to promote
6746     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6747        gameInfo.variant == VariantMakruk) {
6748         ChessSquare p=BlackFerz;  // no choice
6749         while(p < EmptySquare) {  //but make sure we use piece that exists
6750             *promoChoice = PieceToChar(p++);
6751             if(*promoChoice != '.') break;
6752         }
6753         if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
6754     }
6755     // no sense asking what we must promote to if it is going to explode...
6756     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6757         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6758         return FALSE;
6759     }
6760     // give caller the default choice even if we will not make it
6761     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6762     partner = piece; // pieces can promote if the pieceToCharTable says so
6763     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6764     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6765     if(        sweepSelect && gameInfo.variant != VariantGreat
6766                            && gameInfo.variant != VariantGrand
6767                            && gameInfo.variant != VariantSuper) return FALSE;
6768     if(autoQueen) return FALSE; // predetermined
6769
6770     // suppress promotion popup on illegal moves that are not premoves
6771     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6772               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6773     if(appData.testLegality && !premove) {
6774         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6775                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6776         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6777         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6778             return FALSE;
6779     }
6780
6781     return TRUE;
6782 }
6783
6784 int
6785 InPalace (int row, int column)
6786 {   /* [HGM] for Xiangqi */
6787     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6788          column < (BOARD_WIDTH + 4)/2 &&
6789          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6790     return FALSE;
6791 }
6792
6793 int
6794 PieceForSquare (int x, int y)
6795 {
6796   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6797      return -1;
6798   else
6799      return boards[currentMove][y][x];
6800 }
6801
6802 int
6803 OKToStartUserMove (int x, int y)
6804 {
6805     ChessSquare from_piece;
6806     int white_piece;
6807
6808     if (matchMode) return FALSE;
6809     if (gameMode == EditPosition) return TRUE;
6810
6811     if (x >= 0 && y >= 0)
6812       from_piece = boards[currentMove][y][x];
6813     else
6814       from_piece = EmptySquare;
6815
6816     if (from_piece == EmptySquare) return FALSE;
6817
6818     white_piece = (int)from_piece >= (int)WhitePawn &&
6819       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6820
6821     switch (gameMode) {
6822       case AnalyzeFile:
6823       case TwoMachinesPlay:
6824       case EndOfGame:
6825         return FALSE;
6826
6827       case IcsObserving:
6828       case IcsIdle:
6829         return FALSE;
6830
6831       case MachinePlaysWhite:
6832       case IcsPlayingBlack:
6833         if (appData.zippyPlay) return FALSE;
6834         if (white_piece) {
6835             DisplayMoveError(_("You are playing Black"));
6836             return FALSE;
6837         }
6838         break;
6839
6840       case MachinePlaysBlack:
6841       case IcsPlayingWhite:
6842         if (appData.zippyPlay) return FALSE;
6843         if (!white_piece) {
6844             DisplayMoveError(_("You are playing White"));
6845             return FALSE;
6846         }
6847         break;
6848
6849       case PlayFromGameFile:
6850             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6851       case EditGame:
6852         if (!white_piece && WhiteOnMove(currentMove)) {
6853             DisplayMoveError(_("It is White's turn"));
6854             return FALSE;
6855         }
6856         if (white_piece && !WhiteOnMove(currentMove)) {
6857             DisplayMoveError(_("It is Black's turn"));
6858             return FALSE;
6859         }
6860         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6861             /* Editing correspondence game history */
6862             /* Could disallow this or prompt for confirmation */
6863             cmailOldMove = -1;
6864         }
6865         break;
6866
6867       case BeginningOfGame:
6868         if (appData.icsActive) return FALSE;
6869         if (!appData.noChessProgram) {
6870             if (!white_piece) {
6871                 DisplayMoveError(_("You are playing White"));
6872                 return FALSE;
6873             }
6874         }
6875         break;
6876
6877       case Training:
6878         if (!white_piece && WhiteOnMove(currentMove)) {
6879             DisplayMoveError(_("It is White's turn"));
6880             return FALSE;
6881         }
6882         if (white_piece && !WhiteOnMove(currentMove)) {
6883             DisplayMoveError(_("It is Black's turn"));
6884             return FALSE;
6885         }
6886         break;
6887
6888       default:
6889       case IcsExamining:
6890         break;
6891     }
6892     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6893         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6894         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6895         && gameMode != AnalyzeFile && gameMode != Training) {
6896         DisplayMoveError(_("Displayed position is not current"));
6897         return FALSE;
6898     }
6899     return TRUE;
6900 }
6901
6902 Boolean
6903 OnlyMove (int *x, int *y, Boolean captures)
6904 {
6905     DisambiguateClosure cl;
6906     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6907     switch(gameMode) {
6908       case MachinePlaysBlack:
6909       case IcsPlayingWhite:
6910       case BeginningOfGame:
6911         if(!WhiteOnMove(currentMove)) return FALSE;
6912         break;
6913       case MachinePlaysWhite:
6914       case IcsPlayingBlack:
6915         if(WhiteOnMove(currentMove)) return FALSE;
6916         break;
6917       case EditGame:
6918         break;
6919       default:
6920         return FALSE;
6921     }
6922     cl.pieceIn = EmptySquare;
6923     cl.rfIn = *y;
6924     cl.ffIn = *x;
6925     cl.rtIn = -1;
6926     cl.ftIn = -1;
6927     cl.promoCharIn = NULLCHAR;
6928     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6929     if( cl.kind == NormalMove ||
6930         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6931         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6932         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6933       fromX = cl.ff;
6934       fromY = cl.rf;
6935       *x = cl.ft;
6936       *y = cl.rt;
6937       return TRUE;
6938     }
6939     if(cl.kind != ImpossibleMove) return FALSE;
6940     cl.pieceIn = EmptySquare;
6941     cl.rfIn = -1;
6942     cl.ffIn = -1;
6943     cl.rtIn = *y;
6944     cl.ftIn = *x;
6945     cl.promoCharIn = NULLCHAR;
6946     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6947     if( cl.kind == NormalMove ||
6948         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6949         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6950         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6951       fromX = cl.ff;
6952       fromY = cl.rf;
6953       *x = cl.ft;
6954       *y = cl.rt;
6955       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6956       return TRUE;
6957     }
6958     return FALSE;
6959 }
6960
6961 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6962 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6963 int lastLoadGameUseList = FALSE;
6964 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6965 ChessMove lastLoadGameStart = EndOfFile;
6966 int doubleClick;
6967 Boolean addToBookFlag;
6968
6969 void
6970 UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
6971 {
6972     ChessMove moveType;
6973     ChessSquare pup;
6974     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6975
6976     /* Check if the user is playing in turn.  This is complicated because we
6977        let the user "pick up" a piece before it is his turn.  So the piece he
6978        tried to pick up may have been captured by the time he puts it down!
6979        Therefore we use the color the user is supposed to be playing in this
6980        test, not the color of the piece that is currently on the starting
6981        square---except in EditGame mode, where the user is playing both
6982        sides; fortunately there the capture race can't happen.  (It can
6983        now happen in IcsExamining mode, but that's just too bad.  The user
6984        will get a somewhat confusing message in that case.)
6985        */
6986
6987     switch (gameMode) {
6988       case AnalyzeFile:
6989       case TwoMachinesPlay:
6990       case EndOfGame:
6991       case IcsObserving:
6992       case IcsIdle:
6993         /* We switched into a game mode where moves are not accepted,
6994            perhaps while the mouse button was down. */
6995         return;
6996
6997       case MachinePlaysWhite:
6998         /* User is moving for Black */
6999         if (WhiteOnMove(currentMove)) {
7000             DisplayMoveError(_("It is White's turn"));
7001             return;
7002         }
7003         break;
7004
7005       case MachinePlaysBlack:
7006         /* User is moving for White */
7007         if (!WhiteOnMove(currentMove)) {
7008             DisplayMoveError(_("It is Black's turn"));
7009             return;
7010         }
7011         break;
7012
7013       case PlayFromGameFile:
7014             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
7015       case EditGame:
7016       case IcsExamining:
7017       case BeginningOfGame:
7018       case AnalyzeMode:
7019       case Training:
7020         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
7021         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
7022             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
7023             /* User is moving for Black */
7024             if (WhiteOnMove(currentMove)) {
7025                 DisplayMoveError(_("It is White's turn"));
7026                 return;
7027             }
7028         } else {
7029             /* User is moving for White */
7030             if (!WhiteOnMove(currentMove)) {
7031                 DisplayMoveError(_("It is Black's turn"));
7032                 return;
7033             }
7034         }
7035         break;
7036
7037       case IcsPlayingBlack:
7038         /* User is moving for Black */
7039         if (WhiteOnMove(currentMove)) {
7040             if (!appData.premove) {
7041                 DisplayMoveError(_("It is White's turn"));
7042             } else if (toX >= 0 && toY >= 0) {
7043                 premoveToX = toX;
7044                 premoveToY = toY;
7045                 premoveFromX = fromX;
7046                 premoveFromY = fromY;
7047                 premovePromoChar = promoChar;
7048                 gotPremove = 1;
7049                 if (appData.debugMode)
7050                     fprintf(debugFP, "Got premove: fromX %d,"
7051                             "fromY %d, toX %d, toY %d\n",
7052                             fromX, fromY, toX, toY);
7053             }
7054             DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
7055             return;
7056         }
7057         break;
7058
7059       case IcsPlayingWhite:
7060         /* User is moving for White */
7061         if (!WhiteOnMove(currentMove)) {
7062             if (!appData.premove) {
7063                 DisplayMoveError(_("It is Black's turn"));
7064             } else if (toX >= 0 && toY >= 0) {
7065                 premoveToX = toX;
7066                 premoveToY = toY;
7067                 premoveFromX = fromX;
7068                 premoveFromY = fromY;
7069                 premovePromoChar = promoChar;
7070                 gotPremove = 1;
7071                 if (appData.debugMode)
7072                     fprintf(debugFP, "Got premove: fromX %d,"
7073                             "fromY %d, toX %d, toY %d\n",
7074                             fromX, fromY, toX, toY);
7075             }
7076             DrawPosition(TRUE, boards[currentMove]);
7077             return;
7078         }
7079         break;
7080
7081       default:
7082         break;
7083
7084       case EditPosition:
7085         /* EditPosition, empty square, or different color piece;
7086            click-click move is possible */
7087         if (toX == -2 || toY == -2) {
7088             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7089             DrawPosition(FALSE, boards[currentMove]);
7090             return;
7091         } else if (toX >= 0 && toY >= 0) {
7092             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7093                 ChessSquare p = boards[0][rf][ff];
7094                 if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
7095                 if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
7096             }
7097             boards[0][toY][toX] = boards[0][fromY][fromX];
7098             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7099                 if(boards[0][fromY][0] != EmptySquare) {
7100                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7101                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7102                 }
7103             } else
7104             if(fromX == BOARD_RGHT+1) {
7105                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7106                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7107                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7108                 }
7109             } else
7110             boards[0][fromY][fromX] = gatingPiece;
7111             ClearHighlights();
7112             DrawPosition(FALSE, boards[currentMove]);
7113             return;
7114         }
7115         return;
7116     }
7117
7118     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7119     pup = boards[currentMove][toY][toX];
7120
7121     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7122     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7123          if( pup != EmptySquare ) return;
7124          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7125            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7126                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7127            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7128            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7129            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7130            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7131          fromY = DROP_RANK;
7132     }
7133
7134     /* [HGM] always test for legality, to get promotion info */
7135     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7136                                          fromY, fromX, toY, toX, promoChar);
7137
7138     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7139
7140     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7141
7142     /* [HGM] but possibly ignore an IllegalMove result */
7143     if (appData.testLegality) {
7144         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7145             DisplayMoveError(_("Illegal move"));
7146             return;
7147         }
7148     }
7149
7150     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7151         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7152              ClearPremoveHighlights(); // was included
7153         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7154         return;
7155     }
7156
7157     if(addToBookFlag) { // adding moves to book
7158         char buf[MSG_SIZ], move[MSG_SIZ];
7159         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7160         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
7161                                                                    killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
7162         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7163         AddBookMove(buf);
7164         addToBookFlag = FALSE;
7165         ClearHighlights();
7166         return;
7167     }
7168
7169     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7170 }
7171
7172 /* Common tail of UserMoveEvent and DropMenuEvent */
7173 int
7174 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7175 {
7176     char *bookHit = 0;
7177
7178     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7179         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7180         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7181         if(WhiteOnMove(currentMove)) {
7182             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7183         } else {
7184             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7185         }
7186     }
7187
7188     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7189        move type in caller when we know the move is a legal promotion */
7190     if(moveType == NormalMove && promoChar)
7191         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7192
7193     /* [HGM] <popupFix> The following if has been moved here from
7194        UserMoveEvent(). Because it seemed to belong here (why not allow
7195        piece drops in training games?), and because it can only be
7196        performed after it is known to what we promote. */
7197     if (gameMode == Training) {
7198       /* compare the move played on the board to the next move in the
7199        * game. If they match, display the move and the opponent's response.
7200        * If they don't match, display an error message.
7201        */
7202       int saveAnimate;
7203       Board testBoard;
7204       CopyBoard(testBoard, boards[currentMove]);
7205       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7206
7207       if (CompareBoards(testBoard, boards[currentMove+1])) {
7208         ForwardInner(currentMove+1);
7209
7210         /* Autoplay the opponent's response.
7211          * if appData.animate was TRUE when Training mode was entered,
7212          * the response will be animated.
7213          */
7214         saveAnimate = appData.animate;
7215         appData.animate = animateTraining;
7216         ForwardInner(currentMove+1);
7217         appData.animate = saveAnimate;
7218
7219         /* check for the end of the game */
7220         if (currentMove >= forwardMostMove) {
7221           gameMode = PlayFromGameFile;
7222           ModeHighlight();
7223           SetTrainingModeOff();
7224           DisplayInformation(_("End of game"));
7225         }
7226       } else {
7227         DisplayError(_("Incorrect move"), 0);
7228       }
7229       return 1;
7230     }
7231
7232   /* Ok, now we know that the move is good, so we can kill
7233      the previous line in Analysis Mode */
7234   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7235                                 && currentMove < forwardMostMove) {
7236     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7237     else forwardMostMove = currentMove;
7238   }
7239
7240   ClearMap();
7241
7242   /* If we need the chess program but it's dead, restart it */
7243   ResurrectChessProgram();
7244
7245   /* A user move restarts a paused game*/
7246   if (pausing)
7247     PauseEvent();
7248
7249   thinkOutput[0] = NULLCHAR;
7250
7251   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7252
7253   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7254     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7255     return 1;
7256   }
7257
7258   if (gameMode == BeginningOfGame) {
7259     if (appData.noChessProgram) {
7260       gameMode = EditGame;
7261       SetGameInfo();
7262     } else {
7263       char buf[MSG_SIZ];
7264       gameMode = MachinePlaysBlack;
7265       StartClocks();
7266       SetGameInfo();
7267       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7268       DisplayTitle(buf);
7269       if (first.sendName) {
7270         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7271         SendToProgram(buf, &first);
7272       }
7273       StartClocks();
7274     }
7275     ModeHighlight();
7276   }
7277
7278   /* Relay move to ICS or chess engine */
7279   if (appData.icsActive) {
7280     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7281         gameMode == IcsExamining) {
7282       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7283         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7284         SendToICS("draw ");
7285         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7286       }
7287       // also send plain move, in case ICS does not understand atomic claims
7288       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7289       ics_user_moved = 1;
7290     }
7291   } else {
7292     if (first.sendTime && (gameMode == BeginningOfGame ||
7293                            gameMode == MachinePlaysWhite ||
7294                            gameMode == MachinePlaysBlack)) {
7295       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7296     }
7297     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7298          // [HGM] book: if program might be playing, let it use book
7299         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7300         first.maybeThinking = TRUE;
7301     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7302         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7303         SendBoard(&first, currentMove+1);
7304         if(second.analyzing) {
7305             if(!second.useSetboard) SendToProgram("undo\n", &second);
7306             SendBoard(&second, currentMove+1);
7307         }
7308     } else {
7309         SendMoveToProgram(forwardMostMove-1, &first);
7310         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7311     }
7312     if (currentMove == cmailOldMove + 1) {
7313       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7314     }
7315   }
7316
7317   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7318
7319   switch (gameMode) {
7320   case EditGame:
7321     if(appData.testLegality)
7322     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7323     case MT_NONE:
7324     case MT_CHECK:
7325       break;
7326     case MT_CHECKMATE:
7327     case MT_STAINMATE:
7328       if (WhiteOnMove(currentMove)) {
7329         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7330       } else {
7331         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7332       }
7333       break;
7334     case MT_STALEMATE:
7335       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7336       break;
7337     }
7338     break;
7339
7340   case MachinePlaysBlack:
7341   case MachinePlaysWhite:
7342     /* disable certain menu options while machine is thinking */
7343     SetMachineThinkingEnables();
7344     break;
7345
7346   default:
7347     break;
7348   }
7349
7350   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7351   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7352
7353   if(bookHit) { // [HGM] book: simulate book reply
7354         static char bookMove[MSG_SIZ]; // a bit generous?
7355
7356         programStats.nodes = programStats.depth = programStats.time =
7357         programStats.score = programStats.got_only_move = 0;
7358         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7359
7360         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7361         strcat(bookMove, bookHit);
7362         HandleMachineMove(bookMove, &first);
7363   }
7364   return 1;
7365 }
7366
7367 void
7368 MarkByFEN(char *fen)
7369 {
7370         int r, f;
7371         if(!appData.markers || !appData.highlightDragging) return;
7372         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7373         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7374         while(*fen) {
7375             int s = 0;
7376             marker[r][f] = 0;
7377             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7378             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7379             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7380             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7381             if(*fen == 'T') marker[r][f++] = 0; else
7382             if(*fen == 'Y') marker[r][f++] = 1; else
7383             if(*fen == 'G') marker[r][f++] = 3; else
7384             if(*fen == 'B') marker[r][f++] = 4; else
7385             if(*fen == 'C') marker[r][f++] = 5; else
7386             if(*fen == 'M') marker[r][f++] = 6; else
7387             if(*fen == 'W') marker[r][f++] = 7; else
7388             if(*fen == 'D') marker[r][f++] = 8; else
7389             if(*fen == 'R') marker[r][f++] = 2; else {
7390                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7391               f += s; fen -= s>0;
7392             }
7393             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7394             if(r < 0) break;
7395             fen++;
7396         }
7397         DrawPosition(TRUE, NULL);
7398 }
7399
7400 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7401
7402 void
7403 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7404 {
7405     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7406     Markers *m = (Markers *) closure;
7407     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7408         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7409                          || kind == WhiteCapturesEnPassant
7410                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7411     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7412 }
7413
7414 static int hoverSavedValid;
7415
7416 void
7417 MarkTargetSquares (int clear)
7418 {
7419   int x, y, sum=0;
7420   if(clear) { // no reason to ever suppress clearing
7421     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7422     hoverSavedValid = 0;
7423     if(!sum) return; // nothing was cleared,no redraw needed
7424   } else {
7425     int capt = 0;
7426     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7427        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7428     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7429     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7430       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7431       if(capt)
7432       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7433     }
7434   }
7435   DrawPosition(FALSE, NULL);
7436 }
7437
7438 int
7439 Explode (Board board, int fromX, int fromY, int toX, int toY)
7440 {
7441     if(gameInfo.variant == VariantAtomic &&
7442        (board[toY][toX] != EmptySquare ||                     // capture?
7443         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7444                          board[fromY][fromX] == BlackPawn   )
7445       )) {
7446         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7447         return TRUE;
7448     }
7449     return FALSE;
7450 }
7451
7452 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7453
7454 int
7455 CanPromote (ChessSquare piece, int y)
7456 {
7457         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7458         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7459         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7460         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7461            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7462           (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7463            gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
7464         return (piece == BlackPawn && y <= zone ||
7465                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7466                 piece == BlackLance && y <= zone ||
7467                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7468 }
7469
7470 void
7471 HoverEvent (int xPix, int yPix, int x, int y)
7472 {
7473         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7474         int r, f;
7475         if(!first.highlight) return;
7476         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7477         if(x == oldX && y == oldY) return; // only do something if we enter new square
7478         oldFromX = fromX; oldFromY = fromY;
7479         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7480           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7481             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7482           hoverSavedValid = 1;
7483         } else if(oldX != x || oldY != y) {
7484           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7485           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7486           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7487             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7488           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7489             char buf[MSG_SIZ];
7490             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7491             SendToProgram(buf, &first);
7492           }
7493           oldX = x; oldY = y;
7494 //        SetHighlights(fromX, fromY, x, y);
7495         }
7496 }
7497
7498 void ReportClick(char *action, int x, int y)
7499 {
7500         char buf[MSG_SIZ]; // Inform engine of what user does
7501         int r, f;
7502         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7503           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7504             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7505         if(!first.highlight || gameMode == EditPosition) return;
7506         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7507         SendToProgram(buf, &first);
7508 }
7509
7510 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7511
7512 void
7513 LeftClick (ClickType clickType, int xPix, int yPix)
7514 {
7515     int x, y;
7516     Boolean saveAnimate;
7517     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7518     char promoChoice = NULLCHAR;
7519     ChessSquare piece;
7520     static TimeMark lastClickTime, prevClickTime;
7521
7522     x = EventToSquare(xPix, BOARD_WIDTH);
7523     y = EventToSquare(yPix, BOARD_HEIGHT);
7524     if (!flipView && y >= 0) {
7525         y = BOARD_HEIGHT - 1 - y;
7526     }
7527     if (flipView && x >= 0) {
7528         x = BOARD_WIDTH - 1 - x;
7529     }
7530
7531     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7532         static int dummy;
7533         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7534         right = TRUE;
7535         return;
7536     }
7537
7538     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7539
7540     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7541
7542     if (clickType == Press) ErrorPopDown();
7543     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7544
7545     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7546         defaultPromoChoice = promoSweep;
7547         promoSweep = EmptySquare;   // terminate sweep
7548         promoDefaultAltered = TRUE;
7549         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7550     }
7551
7552     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7553         if(clickType == Release) return; // ignore upclick of click-click destination
7554         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7555         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7556         if(gameInfo.holdingsWidth &&
7557                 (WhiteOnMove(currentMove)
7558                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7559                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7560             // click in right holdings, for determining promotion piece
7561             ChessSquare p = boards[currentMove][y][x];
7562             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7563             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7564             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7565                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7566                 fromX = fromY = -1;
7567                 return;
7568             }
7569         }
7570         DrawPosition(FALSE, boards[currentMove]);
7571         return;
7572     }
7573
7574     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7575     if(clickType == Press
7576             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7577               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7578               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7579         return;
7580
7581     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7582         // could be static click on premove from-square: abort premove
7583         gotPremove = 0;
7584         ClearPremoveHighlights();
7585     }
7586
7587     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7588         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7589
7590     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7591         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7592                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7593         defaultPromoChoice = DefaultPromoChoice(side);
7594     }
7595
7596     autoQueen = appData.alwaysPromoteToQueen;
7597
7598     if (fromX == -1) {
7599       int originalY = y;
7600       gatingPiece = EmptySquare;
7601       if (clickType != Press) {
7602         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7603             DragPieceEnd(xPix, yPix); dragging = 0;
7604             DrawPosition(FALSE, NULL);
7605         }
7606         return;
7607       }
7608       doubleClick = FALSE;
7609       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7610         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7611       }
7612       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7613       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7614          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7615          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7616             /* First square */
7617             if (OKToStartUserMove(fromX, fromY)) {
7618                 second = 0;
7619                 ReportClick("lift", x, y);
7620                 MarkTargetSquares(0);
7621                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7622                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7623                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7624                     promoSweep = defaultPromoChoice;
7625                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7626                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7627                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7628                 }
7629                 if (appData.highlightDragging) {
7630                     SetHighlights(fromX, fromY, -1, -1);
7631                 } else {
7632                     ClearHighlights();
7633                 }
7634             } else fromX = fromY = -1;
7635             return;
7636         }
7637     }
7638
7639     /* fromX != -1 */
7640     if (clickType == Press && gameMode != EditPosition) {
7641         ChessSquare fromP;
7642         ChessSquare toP;
7643         int frc;
7644
7645         // ignore off-board to clicks
7646         if(y < 0 || x < 0) return;
7647
7648         /* Check if clicking again on the same color piece */
7649         fromP = boards[currentMove][fromY][fromX];
7650         toP = boards[currentMove][y][x];
7651         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7652         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7653             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7654            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7655              WhitePawn <= toP && toP <= WhiteKing &&
7656              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7657              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7658             (BlackPawn <= fromP && fromP <= BlackKing &&
7659              BlackPawn <= toP && toP <= BlackKing &&
7660              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7661              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7662             /* Clicked again on same color piece -- changed his mind */
7663             second = (x == fromX && y == fromY);
7664             killX = killY = -1;
7665             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7666                 second = FALSE; // first double-click rather than scond click
7667                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7668             }
7669             promoDefaultAltered = FALSE;
7670             MarkTargetSquares(1);
7671            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7672             if (appData.highlightDragging) {
7673                 SetHighlights(x, y, -1, -1);
7674             } else {
7675                 ClearHighlights();
7676             }
7677             if (OKToStartUserMove(x, y)) {
7678                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7679                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7680                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7681                  gatingPiece = boards[currentMove][fromY][fromX];
7682                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7683                 fromX = x;
7684                 fromY = y; dragging = 1;
7685                 if(!second) ReportClick("lift", x, y);
7686                 MarkTargetSquares(0);
7687                 DragPieceBegin(xPix, yPix, FALSE);
7688                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7689                     promoSweep = defaultPromoChoice;
7690                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7691                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7692                 }
7693             }
7694            }
7695            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7696            second = FALSE;
7697         }
7698         // ignore clicks on holdings
7699         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7700     }
7701
7702     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7703         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7704         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7705         return;
7706     }
7707
7708     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7709         DragPieceEnd(xPix, yPix); dragging = 0;
7710         if(clearFlag) {
7711             // a deferred attempt to click-click move an empty square on top of a piece
7712             boards[currentMove][y][x] = EmptySquare;
7713             ClearHighlights();
7714             DrawPosition(FALSE, boards[currentMove]);
7715             fromX = fromY = -1; clearFlag = 0;
7716             return;
7717         }
7718         if (appData.animateDragging) {
7719             /* Undo animation damage if any */
7720             DrawPosition(FALSE, NULL);
7721         }
7722         if (second) {
7723             /* Second up/down in same square; just abort move */
7724             second = 0;
7725             fromX = fromY = -1;
7726             gatingPiece = EmptySquare;
7727             MarkTargetSquares(1);
7728             ClearHighlights();
7729             gotPremove = 0;
7730             ClearPremoveHighlights();
7731         } else {
7732             /* First upclick in same square; start click-click mode */
7733             SetHighlights(x, y, -1, -1);
7734         }
7735         return;
7736     }
7737
7738     clearFlag = 0;
7739
7740     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7741        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7742         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7743         DisplayMessage(_("only marked squares are legal"),"");
7744         DrawPosition(TRUE, NULL);
7745         return; // ignore to-click
7746     }
7747
7748     /* we now have a different from- and (possibly off-board) to-square */
7749     /* Completed move */
7750     if(!sweepSelecting) {
7751         toX = x;
7752         toY = y;
7753     }
7754
7755     piece = boards[currentMove][fromY][fromX];
7756
7757     saveAnimate = appData.animate;
7758     if (clickType == Press) {
7759         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7760         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7761             // must be Edit Position mode with empty-square selected
7762             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7763             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7764             return;
7765         }
7766         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7767             return;
7768         }
7769         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7770             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7771         } else
7772         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7773         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7774           if(appData.sweepSelect) {
7775             promoSweep = defaultPromoChoice;
7776             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7777             selectFlag = 0; lastX = xPix; lastY = yPix;
7778             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7779             Sweep(0); // Pawn that is going to promote: preview promotion piece
7780             sweepSelecting = 1;
7781             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7782             MarkTargetSquares(1);
7783           }
7784           return; // promo popup appears on up-click
7785         }
7786         /* Finish clickclick move */
7787         if (appData.animate || appData.highlightLastMove) {
7788             SetHighlights(fromX, fromY, toX, toY);
7789         } else {
7790             ClearHighlights();
7791         }
7792     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7793         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7794         *promoRestrict = 0;
7795         if (appData.animate || appData.highlightLastMove) {
7796             SetHighlights(fromX, fromY, toX, toY);
7797         } else {
7798             ClearHighlights();
7799         }
7800     } else {
7801 #if 0
7802 // [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
7803         /* Finish drag move */
7804         if (appData.highlightLastMove) {
7805             SetHighlights(fromX, fromY, toX, toY);
7806         } else {
7807             ClearHighlights();
7808         }
7809 #endif
7810         if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
7811           defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
7812         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7813         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7814           dragging *= 2;            // flag button-less dragging if we are dragging
7815           MarkTargetSquares(1);
7816           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7817           else {
7818             kill2X = killX; kill2Y = killY;
7819             killX = x; killY = y;     //remeber this square as intermediate
7820             ReportClick("put", x, y); // and inform engine
7821             ReportClick("lift", x, y);
7822             MarkTargetSquares(0);
7823             return;
7824           }
7825         }
7826         DragPieceEnd(xPix, yPix); dragging = 0;
7827         /* Don't animate move and drag both */
7828         appData.animate = FALSE;
7829     }
7830
7831     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7832     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7833         ChessSquare piece = boards[currentMove][fromY][fromX];
7834         if(gameMode == EditPosition && piece != EmptySquare &&
7835            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7836             int n;
7837
7838             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7839                 n = PieceToNumber(piece - (int)BlackPawn);
7840                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7841                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7842                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7843             } else
7844             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7845                 n = PieceToNumber(piece);
7846                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7847                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7848                 boards[currentMove][n][BOARD_WIDTH-2]++;
7849             }
7850             boards[currentMove][fromY][fromX] = EmptySquare;
7851         }
7852         ClearHighlights();
7853         fromX = fromY = -1;
7854         MarkTargetSquares(1);
7855         DrawPosition(TRUE, boards[currentMove]);
7856         return;
7857     }
7858
7859     // off-board moves should not be highlighted
7860     if(x < 0 || y < 0) ClearHighlights();
7861     else ReportClick("put", x, y);
7862
7863     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7864
7865     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7866
7867     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7868         SetHighlights(fromX, fromY, toX, toY);
7869         MarkTargetSquares(1);
7870         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7871             // [HGM] super: promotion to captured piece selected from holdings
7872             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7873             promotionChoice = TRUE;
7874             // kludge follows to temporarily execute move on display, without promoting yet
7875             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7876             boards[currentMove][toY][toX] = p;
7877             DrawPosition(FALSE, boards[currentMove]);
7878             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7879             boards[currentMove][toY][toX] = q;
7880             DisplayMessage("Click in holdings to choose piece", "");
7881             return;
7882         }
7883         PromotionPopUp(promoChoice);
7884     } else {
7885         int oldMove = currentMove;
7886         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7887         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7888         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7889         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7890            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7891             DrawPosition(TRUE, boards[currentMove]);
7892         MarkTargetSquares(1);
7893         fromX = fromY = -1;
7894     }
7895     appData.animate = saveAnimate;
7896     if (appData.animate || appData.animateDragging) {
7897         /* Undo animation damage if needed */
7898         DrawPosition(FALSE, NULL);
7899     }
7900 }
7901
7902 int
7903 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7904 {   // front-end-free part taken out of PieceMenuPopup
7905     int whichMenu; int xSqr, ySqr;
7906
7907     if(seekGraphUp) { // [HGM] seekgraph
7908         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7909         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7910         return -2;
7911     }
7912
7913     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7914          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7915         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7916         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7917         if(action == Press)   {
7918             originalFlip = flipView;
7919             flipView = !flipView; // temporarily flip board to see game from partners perspective
7920             DrawPosition(TRUE, partnerBoard);
7921             DisplayMessage(partnerStatus, "");
7922             partnerUp = TRUE;
7923         } else if(action == Release) {
7924             flipView = originalFlip;
7925             DrawPosition(TRUE, boards[currentMove]);
7926             partnerUp = FALSE;
7927         }
7928         return -2;
7929     }
7930
7931     xSqr = EventToSquare(x, BOARD_WIDTH);
7932     ySqr = EventToSquare(y, BOARD_HEIGHT);
7933     if (action == Release) {
7934         if(pieceSweep != EmptySquare) {
7935             EditPositionMenuEvent(pieceSweep, toX, toY);
7936             pieceSweep = EmptySquare;
7937         } else UnLoadPV(); // [HGM] pv
7938     }
7939     if (action != Press) return -2; // return code to be ignored
7940     switch (gameMode) {
7941       case IcsExamining:
7942         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7943       case EditPosition:
7944         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7945         if (xSqr < 0 || ySqr < 0) return -1;
7946         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7947         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7948         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7949         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7950         NextPiece(0);
7951         return 2; // grab
7952       case IcsObserving:
7953         if(!appData.icsEngineAnalyze) return -1;
7954       case IcsPlayingWhite:
7955       case IcsPlayingBlack:
7956         if(!appData.zippyPlay) goto noZip;
7957       case AnalyzeMode:
7958       case AnalyzeFile:
7959       case MachinePlaysWhite:
7960       case MachinePlaysBlack:
7961       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7962         if (!appData.dropMenu) {
7963           LoadPV(x, y);
7964           return 2; // flag front-end to grab mouse events
7965         }
7966         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7967            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7968       case EditGame:
7969       noZip:
7970         if (xSqr < 0 || ySqr < 0) return -1;
7971         if (!appData.dropMenu || appData.testLegality &&
7972             gameInfo.variant != VariantBughouse &&
7973             gameInfo.variant != VariantCrazyhouse) return -1;
7974         whichMenu = 1; // drop menu
7975         break;
7976       default:
7977         return -1;
7978     }
7979
7980     if (((*fromX = xSqr) < 0) ||
7981         ((*fromY = ySqr) < 0)) {
7982         *fromX = *fromY = -1;
7983         return -1;
7984     }
7985     if (flipView)
7986       *fromX = BOARD_WIDTH - 1 - *fromX;
7987     else
7988       *fromY = BOARD_HEIGHT - 1 - *fromY;
7989
7990     return whichMenu;
7991 }
7992
7993 void
7994 Wheel (int dir, int x, int y)
7995 {
7996     if(gameMode == EditPosition) {
7997         int xSqr = EventToSquare(x, BOARD_WIDTH);
7998         int ySqr = EventToSquare(y, BOARD_HEIGHT);
7999         if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
8000         if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
8001         do {
8002             boards[currentMove][ySqr][xSqr] += dir;
8003             if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
8004             if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
8005         } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
8006         DrawPosition(FALSE, boards[currentMove]);
8007     } else if(dir > 0) ForwardEvent(); else BackwardEvent();
8008 }
8009
8010 void
8011 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
8012 {
8013 //    char * hint = lastHint;
8014     FrontEndProgramStats stats;
8015
8016     stats.which = cps == &first ? 0 : 1;
8017     stats.depth = cpstats->depth;
8018     stats.nodes = cpstats->nodes;
8019     stats.score = cpstats->score;
8020     stats.time = cpstats->time;
8021     stats.pv = cpstats->movelist;
8022     stats.hint = lastHint;
8023     stats.an_move_index = 0;
8024     stats.an_move_count = 0;
8025
8026     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
8027         stats.hint = cpstats->move_name;
8028         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
8029         stats.an_move_count = cpstats->nr_moves;
8030     }
8031
8032     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
8033
8034     SetProgramStats( &stats );
8035 }
8036
8037 void
8038 ClearEngineOutputPane (int which)
8039 {
8040     static FrontEndProgramStats dummyStats;
8041     dummyStats.which = which;
8042     dummyStats.pv = "#";
8043     SetProgramStats( &dummyStats );
8044 }
8045
8046 #define MAXPLAYERS 500
8047
8048 char *
8049 TourneyStandings (int display)
8050 {
8051     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8052     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8053     char result, *p, *names[MAXPLAYERS];
8054
8055     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8056         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8057     names[0] = p = strdup(appData.participants);
8058     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8059
8060     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8061
8062     while(result = appData.results[nr]) {
8063         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8064         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8065         wScore = bScore = 0;
8066         switch(result) {
8067           case '+': wScore = 2; break;
8068           case '-': bScore = 2; break;
8069           case '=': wScore = bScore = 1; break;
8070           case ' ':
8071           case '*': return strdup("busy"); // tourney not finished
8072         }
8073         score[w] += wScore;
8074         score[b] += bScore;
8075         games[w]++;
8076         games[b]++;
8077         nr++;
8078     }
8079     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8080     for(w=0; w<nPlayers; w++) {
8081         bScore = -1;
8082         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8083         ranking[w] = b; points[w] = bScore; score[b] = -2;
8084     }
8085     p = malloc(nPlayers*34+1);
8086     for(w=0; w<nPlayers && w<display; w++)
8087         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8088     free(names[0]);
8089     return p;
8090 }
8091
8092 void
8093 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8094 {       // count all piece types
8095         int p, f, r;
8096         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8097         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8098         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8099                 p = board[r][f];
8100                 pCnt[p]++;
8101                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8102                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8103                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8104                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8105                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8106                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8107         }
8108 }
8109
8110 int
8111 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8112 {
8113         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8114         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8115
8116         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8117         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8118         if(myPawns == 2 && nMine == 3) // KPP
8119             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8120         if(myPawns == 1 && nMine == 2) // KP
8121             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8122         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8123             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8124         if(myPawns) return FALSE;
8125         if(pCnt[WhiteRook+side])
8126             return pCnt[BlackRook-side] ||
8127                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8128                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8129                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8130         if(pCnt[WhiteCannon+side]) {
8131             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8132             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8133         }
8134         if(pCnt[WhiteKnight+side])
8135             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8136         return FALSE;
8137 }
8138
8139 int
8140 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8141 {
8142         VariantClass v = gameInfo.variant;
8143
8144         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8145         if(v == VariantShatranj) return TRUE; // always winnable through baring
8146         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8147         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8148
8149         if(v == VariantXiangqi) {
8150                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8151
8152                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8153                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8154                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8155                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8156                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8157                 if(stale) // we have at least one last-rank P plus perhaps C
8158                     return majors // KPKX
8159                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8160                 else // KCA*E*
8161                     return pCnt[WhiteFerz+side] // KCAK
8162                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8163                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8164                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8165
8166         } else if(v == VariantKnightmate) {
8167                 if(nMine == 1) return FALSE;
8168                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8169         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8170                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8171
8172                 if(nMine == 1) return FALSE; // bare King
8173                 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
8174                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8175                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8176                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8177                 if(pCnt[WhiteKnight+side])
8178                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8179                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8180                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8181                 if(nBishops)
8182                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8183                 if(pCnt[WhiteAlfil+side])
8184                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8185                 if(pCnt[WhiteWazir+side])
8186                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8187         }
8188
8189         return TRUE;
8190 }
8191
8192 int
8193 CompareWithRights (Board b1, Board b2)
8194 {
8195     int rights = 0;
8196     if(!CompareBoards(b1, b2)) return FALSE;
8197     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8198     /* compare castling rights */
8199     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8200            rights++; /* King lost rights, while rook still had them */
8201     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8202         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8203            rights++; /* but at least one rook lost them */
8204     }
8205     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8206            rights++;
8207     if( b1[CASTLING][5] != NoRights ) {
8208         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8209            rights++;
8210     }
8211     return rights == 0;
8212 }
8213
8214 int
8215 Adjudicate (ChessProgramState *cps)
8216 {       // [HGM] some adjudications useful with buggy engines
8217         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8218         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8219         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8220         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8221         int k, drop, count = 0; static int bare = 1;
8222         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8223         Boolean canAdjudicate = !appData.icsActive;
8224
8225         // most tests only when we understand the game, i.e. legality-checking on
8226             if( appData.testLegality )
8227             {   /* [HGM] Some more adjudications for obstinate engines */
8228                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8229                 static int moveCount = 6;
8230                 ChessMove result;
8231                 char *reason = NULL;
8232
8233                 /* Count what is on board. */
8234                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8235
8236                 /* Some material-based adjudications that have to be made before stalemate test */
8237                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8238                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8239                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8240                      if(canAdjudicate && appData.checkMates) {
8241                          if(engineOpponent)
8242                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8243                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8244                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8245                          return 1;
8246                      }
8247                 }
8248
8249                 /* Bare King in Shatranj (loses) or Losers (wins) */
8250                 if( nrW == 1 || nrB == 1) {
8251                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8252                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8253                      if(canAdjudicate && appData.checkMates) {
8254                          if(engineOpponent)
8255                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8256                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8257                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8258                          return 1;
8259                      }
8260                   } else
8261                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8262                   {    /* bare King */
8263                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8264                         if(canAdjudicate && appData.checkMates) {
8265                             /* but only adjudicate if adjudication enabled */
8266                             if(engineOpponent)
8267                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8268                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8269                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8270                             return 1;
8271                         }
8272                   }
8273                 } else bare = 1;
8274
8275
8276             // don't wait for engine to announce game end if we can judge ourselves
8277             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8278               case MT_CHECK:
8279                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8280                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8281                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8282                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8283                             checkCnt++;
8284                         if(checkCnt >= 2) {
8285                             reason = "Xboard adjudication: 3rd check";
8286                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8287                             break;
8288                         }
8289                     }
8290                 }
8291               case MT_NONE:
8292               default:
8293                 break;
8294               case MT_STEALMATE:
8295               case MT_STALEMATE:
8296               case MT_STAINMATE:
8297                 reason = "Xboard adjudication: Stalemate";
8298                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8299                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8300                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8301                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8302                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8303                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8304                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8305                                                                         EP_CHECKMATE : EP_WINS);
8306                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8307                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8308                 }
8309                 break;
8310               case MT_CHECKMATE:
8311                 reason = "Xboard adjudication: Checkmate";
8312                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8313                 if(gameInfo.variant == VariantShogi) {
8314                     if(forwardMostMove > backwardMostMove
8315                        && moveList[forwardMostMove-1][1] == '@'
8316                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8317                         reason = "XBoard adjudication: pawn-drop mate";
8318                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8319                     }
8320                 }
8321                 break;
8322             }
8323
8324                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8325                     case EP_STALEMATE:
8326                         result = GameIsDrawn; break;
8327                     case EP_CHECKMATE:
8328                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8329                     case EP_WINS:
8330                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8331                     default:
8332                         result = EndOfFile;
8333                 }
8334                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8335                     if(engineOpponent)
8336                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8337                     GameEnds( result, reason, GE_XBOARD );
8338                     return 1;
8339                 }
8340
8341                 /* Next absolutely insufficient mating material. */
8342                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8343                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8344                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8345
8346                      /* always flag draws, for judging claims */
8347                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8348
8349                      if(canAdjudicate && appData.materialDraws) {
8350                          /* but only adjudicate them if adjudication enabled */
8351                          if(engineOpponent) {
8352                            SendToProgram("force\n", engineOpponent); // suppress reply
8353                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8354                          }
8355                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8356                          return 1;
8357                      }
8358                 }
8359
8360                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8361                 if(gameInfo.variant == VariantXiangqi ?
8362                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8363                  : nrW + nrB == 4 &&
8364                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8365                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8366                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8367                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8368                    ) ) {
8369                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8370                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8371                           if(engineOpponent) {
8372                             SendToProgram("force\n", engineOpponent); // suppress reply
8373                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8374                           }
8375                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8376                           return 1;
8377                      }
8378                 } else moveCount = 6;
8379             }
8380
8381         // Repetition draws and 50-move rule can be applied independently of legality testing
8382
8383                 /* Check for rep-draws */
8384                 count = 0;
8385                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8386                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8387                 for(k = forwardMostMove-2;
8388                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8389                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8390                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8391                     k-=2)
8392                 {   int rights=0;
8393                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8394                         /* compare castling rights */
8395                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8396                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8397                                 rights++; /* King lost rights, while rook still had them */
8398                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8399                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8400                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8401                                    rights++; /* but at least one rook lost them */
8402                         }
8403                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8404                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8405                                 rights++;
8406                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8407                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8408                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8409                                    rights++;
8410                         }
8411                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8412                             && appData.drawRepeats > 1) {
8413                              /* adjudicate after user-specified nr of repeats */
8414                              int result = GameIsDrawn;
8415                              char *details = "XBoard adjudication: repetition draw";
8416                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8417                                 // [HGM] xiangqi: check for forbidden perpetuals
8418                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8419                                 for(m=forwardMostMove; m>k; m-=2) {
8420                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8421                                         ourPerpetual = 0; // the current mover did not always check
8422                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8423                                         hisPerpetual = 0; // the opponent did not always check
8424                                 }
8425                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8426                                                                         ourPerpetual, hisPerpetual);
8427                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8428                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8429                                     details = "Xboard adjudication: perpetual checking";
8430                                 } else
8431                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8432                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8433                                 } else
8434                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8435                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8436                                         result = BlackWins;
8437                                         details = "Xboard adjudication: repetition";
8438                                     }
8439                                 } else // it must be XQ
8440                                 // Now check for perpetual chases
8441                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8442                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8443                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8444                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8445                                         static char resdet[MSG_SIZ];
8446                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8447                                         details = resdet;
8448                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8449                                     } else
8450                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8451                                         break; // Abort repetition-checking loop.
8452                                 }
8453                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8454                              }
8455                              if(engineOpponent) {
8456                                SendToProgram("force\n", engineOpponent); // suppress reply
8457                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8458                              }
8459                              GameEnds( result, details, GE_XBOARD );
8460                              return 1;
8461                         }
8462                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8463                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8464                     }
8465                 }
8466
8467                 /* Now we test for 50-move draws. Determine ply count */
8468                 count = forwardMostMove;
8469                 /* look for last irreversble move */
8470                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8471                     count--;
8472                 /* if we hit starting position, add initial plies */
8473                 if( count == backwardMostMove )
8474                     count -= initialRulePlies;
8475                 count = forwardMostMove - count;
8476                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8477                         // adjust reversible move counter for checks in Xiangqi
8478                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8479                         if(i < backwardMostMove) i = backwardMostMove;
8480                         while(i <= forwardMostMove) {
8481                                 lastCheck = inCheck; // check evasion does not count
8482                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8483                                 if(inCheck || lastCheck) count--; // check does not count
8484                                 i++;
8485                         }
8486                 }
8487                 if( count >= 100)
8488                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8489                          /* this is used to judge if draw claims are legal */
8490                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8491                          if(engineOpponent) {
8492                            SendToProgram("force\n", engineOpponent); // suppress reply
8493                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8494                          }
8495                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8496                          return 1;
8497                 }
8498
8499                 /* if draw offer is pending, treat it as a draw claim
8500                  * when draw condition present, to allow engines a way to
8501                  * claim draws before making their move to avoid a race
8502                  * condition occurring after their move
8503                  */
8504                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8505                          char *p = NULL;
8506                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8507                              p = "Draw claim: 50-move rule";
8508                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8509                              p = "Draw claim: 3-fold repetition";
8510                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8511                              p = "Draw claim: insufficient mating material";
8512                          if( p != NULL && canAdjudicate) {
8513                              if(engineOpponent) {
8514                                SendToProgram("force\n", engineOpponent); // suppress reply
8515                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8516                              }
8517                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8518                              return 1;
8519                          }
8520                 }
8521
8522                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8523                     if(engineOpponent) {
8524                       SendToProgram("force\n", engineOpponent); // suppress reply
8525                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8526                     }
8527                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8528                     return 1;
8529                 }
8530         return 0;
8531 }
8532
8533 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8534 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8535 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8536
8537 static int
8538 BitbaseProbe ()
8539 {
8540     int pieces[10], squares[10], cnt=0, r, f, res;
8541     static int loaded;
8542     static PPROBE_EGBB probeBB;
8543     if(!appData.testLegality) return 10;
8544     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8545     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8546     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8547     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8548         ChessSquare piece = boards[forwardMostMove][r][f];
8549         int black = (piece >= BlackPawn);
8550         int type = piece - black*BlackPawn;
8551         if(piece == EmptySquare) continue;
8552         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8553         if(type == WhiteKing) type = WhiteQueen + 1;
8554         type = egbbCode[type];
8555         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8556         pieces[cnt] = type + black*6;
8557         if(++cnt > 5) return 11;
8558     }
8559     pieces[cnt] = squares[cnt] = 0;
8560     // probe EGBB
8561     if(loaded == 2) return 13; // loading failed before
8562     if(loaded == 0) {
8563         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8564         HMODULE lib;
8565         PLOAD_EGBB loadBB;
8566         loaded = 2; // prepare for failure
8567         if(!path) return 13; // no egbb installed
8568         strncpy(buf, path + 8, MSG_SIZ);
8569         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8570         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8571         lib = LoadLibrary(buf);
8572         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8573         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8574         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8575         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8576         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8577         loaded = 1; // success!
8578     }
8579     res = probeBB(forwardMostMove & 1, pieces, squares);
8580     return res > 0 ? 1 : res < 0 ? -1 : 0;
8581 }
8582
8583 char *
8584 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8585 {   // [HGM] book: this routine intercepts moves to simulate book replies
8586     char *bookHit = NULL;
8587
8588     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8589         char buf[MSG_SIZ];
8590         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8591         SendToProgram(buf, cps);
8592     }
8593     //first determine if the incoming move brings opponent into his book
8594     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8595         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8596     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8597     if(bookHit != NULL && !cps->bookSuspend) {
8598         // make sure opponent is not going to reply after receiving move to book position
8599         SendToProgram("force\n", cps);
8600         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8601     }
8602     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8603     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8604     // now arrange restart after book miss
8605     if(bookHit) {
8606         // after a book hit we never send 'go', and the code after the call to this routine
8607         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8608         char buf[MSG_SIZ], *move = bookHit;
8609         if(cps->useSAN) {
8610             int fromX, fromY, toX, toY;
8611             char promoChar;
8612             ChessMove moveType;
8613             move = buf + 30;
8614             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8615                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8616                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8617                                     PosFlags(forwardMostMove),
8618                                     fromY, fromX, toY, toX, promoChar, move);
8619             } else {
8620                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8621                 bookHit = NULL;
8622             }
8623         }
8624         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8625         SendToProgram(buf, cps);
8626         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8627     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8628         SendToProgram("go\n", cps);
8629         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8630     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8631         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8632             SendToProgram("go\n", cps);
8633         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8634     }
8635     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8636 }
8637
8638 int
8639 LoadError (char *errmess, ChessProgramState *cps)
8640 {   // unloads engine and switches back to -ncp mode if it was first
8641     if(cps->initDone) return FALSE;
8642     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8643     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8644     cps->pr = NoProc;
8645     if(cps == &first) {
8646         appData.noChessProgram = TRUE;
8647         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8648         gameMode = BeginningOfGame; ModeHighlight();
8649         SetNCPMode();
8650     }
8651     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8652     DisplayMessage("", ""); // erase waiting message
8653     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8654     return TRUE;
8655 }
8656
8657 char *savedMessage;
8658 ChessProgramState *savedState;
8659 void
8660 DeferredBookMove (void)
8661 {
8662         if(savedState->lastPing != savedState->lastPong)
8663                     ScheduleDelayedEvent(DeferredBookMove, 10);
8664         else
8665         HandleMachineMove(savedMessage, savedState);
8666 }
8667
8668 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8669 static ChessProgramState *stalledEngine;
8670 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8671
8672 void
8673 HandleMachineMove (char *message, ChessProgramState *cps)
8674 {
8675     static char firstLeg[20];
8676     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8677     char realname[MSG_SIZ];
8678     int fromX, fromY, toX, toY;
8679     ChessMove moveType;
8680     char promoChar, roar;
8681     char *p, *pv=buf1;
8682     int oldError;
8683     char *bookHit;
8684
8685     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8686         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8687         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8688             DisplayError(_("Invalid pairing from pairing engine"), 0);
8689             return;
8690         }
8691         pairingReceived = 1;
8692         NextMatchGame();
8693         return; // Skim the pairing messages here.
8694     }
8695
8696     oldError = cps->userError; cps->userError = 0;
8697
8698 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8699     /*
8700      * Kludge to ignore BEL characters
8701      */
8702     while (*message == '\007') message++;
8703
8704     /*
8705      * [HGM] engine debug message: ignore lines starting with '#' character
8706      */
8707     if(cps->debug && *message == '#') return;
8708
8709     /*
8710      * Look for book output
8711      */
8712     if (cps == &first && bookRequested) {
8713         if (message[0] == '\t' || message[0] == ' ') {
8714             /* Part of the book output is here; append it */
8715             strcat(bookOutput, message);
8716             strcat(bookOutput, "  \n");
8717             return;
8718         } else if (bookOutput[0] != NULLCHAR) {
8719             /* All of book output has arrived; display it */
8720             char *p = bookOutput;
8721             while (*p != NULLCHAR) {
8722                 if (*p == '\t') *p = ' ';
8723                 p++;
8724             }
8725             DisplayInformation(bookOutput);
8726             bookRequested = FALSE;
8727             /* Fall through to parse the current output */
8728         }
8729     }
8730
8731     /*
8732      * Look for machine move.
8733      */
8734     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8735         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8736     {
8737         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8738             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8739             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8740             stalledEngine = cps;
8741             if(appData.ponderNextMove) { // bring opponent out of ponder
8742                 if(gameMode == TwoMachinesPlay) {
8743                     if(cps->other->pause)
8744                         PauseEngine(cps->other);
8745                     else
8746                         SendToProgram("easy\n", cps->other);
8747                 }
8748             }
8749             StopClocks();
8750             return;
8751         }
8752
8753       if(cps->usePing) {
8754
8755         /* This method is only useful on engines that support ping */
8756         if(abortEngineThink) {
8757             if (appData.debugMode) {
8758                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8759             }
8760             SendToProgram("undo\n", cps);
8761             return;
8762         }
8763
8764         if (cps->lastPing != cps->lastPong) {
8765             /* Extra move from before last new; ignore */
8766             if (appData.debugMode) {
8767                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8768             }
8769           return;
8770         }
8771
8772       } else {
8773
8774         int machineWhite = FALSE;
8775
8776         switch (gameMode) {
8777           case BeginningOfGame:
8778             /* Extra move from before last reset; ignore */
8779             if (appData.debugMode) {
8780                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8781             }
8782             return;
8783
8784           case EndOfGame:
8785           case IcsIdle:
8786           default:
8787             /* Extra move after we tried to stop.  The mode test is
8788                not a reliable way of detecting this problem, but it's
8789                the best we can do on engines that don't support ping.
8790             */
8791             if (appData.debugMode) {
8792                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8793                         cps->which, gameMode);
8794             }
8795             SendToProgram("undo\n", cps);
8796             return;
8797
8798           case MachinePlaysWhite:
8799           case IcsPlayingWhite:
8800             machineWhite = TRUE;
8801             break;
8802
8803           case MachinePlaysBlack:
8804           case IcsPlayingBlack:
8805             machineWhite = FALSE;
8806             break;
8807
8808           case TwoMachinesPlay:
8809             machineWhite = (cps->twoMachinesColor[0] == 'w');
8810             break;
8811         }
8812         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8813             if (appData.debugMode) {
8814                 fprintf(debugFP,
8815                         "Ignoring move out of turn by %s, gameMode %d"
8816                         ", forwardMost %d\n",
8817                         cps->which, gameMode, forwardMostMove);
8818             }
8819             return;
8820         }
8821       }
8822
8823         if(cps->alphaRank) AlphaRank(machineMove, 4);
8824
8825         // [HGM] lion: (some very limited) support for Alien protocol
8826         killX = killY = kill2X = kill2Y = -1;
8827         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8828             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8829             return;
8830         }
8831         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8832             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8833             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8834         }
8835         if(firstLeg[0]) { // there was a previous leg;
8836             // only support case where same piece makes two step
8837             char buf[20], *p = machineMove+1, *q = buf+1, f;
8838             safeStrCpy(buf, machineMove, 20);
8839             while(isdigit(*q)) q++; // find start of to-square
8840             safeStrCpy(machineMove, firstLeg, 20);
8841             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8842             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8843             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8844             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8845             firstLeg[0] = NULLCHAR;
8846         }
8847
8848         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8849                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8850             /* Machine move could not be parsed; ignore it. */
8851           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8852                     machineMove, _(cps->which));
8853             DisplayMoveError(buf1);
8854             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8855                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8856             if (gameMode == TwoMachinesPlay) {
8857               GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8858                        buf1, GE_XBOARD);
8859             }
8860             return;
8861         }
8862
8863         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8864         /* So we have to redo legality test with true e.p. status here,  */
8865         /* to make sure an illegal e.p. capture does not slip through,   */
8866         /* to cause a forfeit on a justified illegal-move complaint      */
8867         /* of the opponent.                                              */
8868         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8869            ChessMove moveType;
8870            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8871                              fromY, fromX, toY, toX, promoChar);
8872             if(moveType == IllegalMove) {
8873               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8874                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8875                 GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8876                            buf1, GE_XBOARD);
8877                 return;
8878            } else if(!appData.fischerCastling)
8879            /* [HGM] Kludge to handle engines that send FRC-style castling
8880               when they shouldn't (like TSCP-Gothic) */
8881            switch(moveType) {
8882              case WhiteASideCastleFR:
8883              case BlackASideCastleFR:
8884                toX+=2;
8885                currentMoveString[2]++;
8886                break;
8887              case WhiteHSideCastleFR:
8888              case BlackHSideCastleFR:
8889                toX--;
8890                currentMoveString[2]--;
8891                break;
8892              default: ; // nothing to do, but suppresses warning of pedantic compilers
8893            }
8894         }
8895         hintRequested = FALSE;
8896         lastHint[0] = NULLCHAR;
8897         bookRequested = FALSE;
8898         /* Program may be pondering now */
8899         cps->maybeThinking = TRUE;
8900         if (cps->sendTime == 2) cps->sendTime = 1;
8901         if (cps->offeredDraw) cps->offeredDraw--;
8902
8903         /* [AS] Save move info*/
8904         pvInfoList[ forwardMostMove ].score = programStats.score;
8905         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8906         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8907
8908         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8909
8910         /* Test suites abort the 'game' after one move */
8911         if(*appData.finger) {
8912            static FILE *f;
8913            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8914            if(!f) f = fopen(appData.finger, "w");
8915            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8916            else { DisplayFatalError("Bad output file", errno, 0); return; }
8917            free(fen);
8918            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8919         }
8920         if(appData.epd) {
8921            if(solvingTime >= 0) {
8922               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8923               totalTime += solvingTime; first.matchWins++;
8924            } else {
8925               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8926               second.matchWins++;
8927            }
8928            OutputKibitz(2, buf1);
8929            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8930         }
8931
8932         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8933         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8934             int count = 0;
8935
8936             while( count < adjudicateLossPlies ) {
8937                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8938
8939                 if( count & 1 ) {
8940                     score = -score; /* Flip score for winning side */
8941                 }
8942
8943                 if( score > appData.adjudicateLossThreshold ) {
8944                     break;
8945                 }
8946
8947                 count++;
8948             }
8949
8950             if( count >= adjudicateLossPlies ) {
8951                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8952
8953                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8954                     "Xboard adjudication",
8955                     GE_XBOARD );
8956
8957                 return;
8958             }
8959         }
8960
8961         if(Adjudicate(cps)) {
8962             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8963             return; // [HGM] adjudicate: for all automatic game ends
8964         }
8965
8966 #if ZIPPY
8967         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8968             first.initDone) {
8969           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8970                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8971                 SendToICS("draw ");
8972                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8973           }
8974           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8975           ics_user_moved = 1;
8976           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8977                 char buf[3*MSG_SIZ];
8978
8979                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8980                         programStats.score / 100.,
8981                         programStats.depth,
8982                         programStats.time / 100.,
8983                         (unsigned int)programStats.nodes,
8984                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8985                         programStats.movelist);
8986                 SendToICS(buf);
8987           }
8988         }
8989 #endif
8990
8991         /* [AS] Clear stats for next move */
8992         ClearProgramStats();
8993         thinkOutput[0] = NULLCHAR;
8994         hiddenThinkOutputState = 0;
8995
8996         bookHit = NULL;
8997         if (gameMode == TwoMachinesPlay) {
8998             /* [HGM] relaying draw offers moved to after reception of move */
8999             /* and interpreting offer as claim if it brings draw condition */
9000             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
9001                 SendToProgram("draw\n", cps->other);
9002             }
9003             if (cps->other->sendTime) {
9004                 SendTimeRemaining(cps->other,
9005                                   cps->other->twoMachinesColor[0] == 'w');
9006             }
9007             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
9008             if (firstMove && !bookHit) {
9009                 firstMove = FALSE;
9010                 if (cps->other->useColors) {
9011                   SendToProgram(cps->other->twoMachinesColor, cps->other);
9012                 }
9013                 SendToProgram("go\n", cps->other);
9014             }
9015             cps->other->maybeThinking = TRUE;
9016         }
9017
9018         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
9019
9020         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
9021
9022         if (!pausing && appData.ringBellAfterMoves) {
9023             if(!roar) RingBell();
9024         }
9025
9026         /*
9027          * Reenable menu items that were disabled while
9028          * machine was thinking
9029          */
9030         if (gameMode != TwoMachinesPlay)
9031             SetUserThinkingEnables();
9032
9033         // [HGM] book: after book hit opponent has received move and is now in force mode
9034         // force the book reply into it, and then fake that it outputted this move by jumping
9035         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
9036         if(bookHit) {
9037                 static char bookMove[MSG_SIZ]; // a bit generous?
9038
9039                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
9040                 strcat(bookMove, bookHit);
9041                 message = bookMove;
9042                 cps = cps->other;
9043                 programStats.nodes = programStats.depth = programStats.time =
9044                 programStats.score = programStats.got_only_move = 0;
9045                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
9046
9047                 if(cps->lastPing != cps->lastPong) {
9048                     savedMessage = message; // args for deferred call
9049                     savedState = cps;
9050                     ScheduleDelayedEvent(DeferredBookMove, 10);
9051                     return;
9052                 }
9053                 goto FakeBookMove;
9054         }
9055
9056         return;
9057     }
9058
9059     /* Set special modes for chess engines.  Later something general
9060      *  could be added here; for now there is just one kludge feature,
9061      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9062      *  when "xboard" is given as an interactive command.
9063      */
9064     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9065         cps->useSigint = FALSE;
9066         cps->useSigterm = FALSE;
9067     }
9068     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9069       ParseFeatures(message+8, cps);
9070       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9071     }
9072
9073     if (!strncmp(message, "setup ", 6) && 
9074         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9075           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9076                                         ) { // [HGM] allow first engine to define opening position
9077       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9078       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9079       *buf = NULLCHAR;
9080       if(sscanf(message, "setup (%s", buf) == 1) {
9081         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9082         ASSIGN(appData.pieceToCharTable, buf);
9083       }
9084       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9085       if(dummy >= 3) {
9086         while(message[s] && message[s++] != ' ');
9087         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9088            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9089             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9090             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9091           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9092           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9093           startedFromSetupPosition = FALSE;
9094         }
9095       }
9096       if(startedFromSetupPosition) return;
9097       ParseFEN(boards[0], &dummy, message+s, FALSE);
9098       DrawPosition(TRUE, boards[0]);
9099       CopyBoard(initialPosition, boards[0]);
9100       startedFromSetupPosition = TRUE;
9101       return;
9102     }
9103     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9104       ChessSquare piece = WhitePawn;
9105       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9106       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9107       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9108       piece += CharToPiece(ID & 255) - WhitePawn;
9109       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9110       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9111       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9112       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9113       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9114       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9115                                                && gameInfo.variant != VariantGreat
9116                                                && gameInfo.variant != VariantFairy    ) return;
9117       if(piece < EmptySquare) {
9118         pieceDefs = TRUE;
9119         ASSIGN(pieceDesc[piece], buf1);
9120         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9121       }
9122       return;
9123     }
9124     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9125       promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9126       Sweep(0);
9127       return;
9128     }
9129     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9130      * want this, I was asked to put it in, and obliged.
9131      */
9132     if (!strncmp(message, "setboard ", 9)) {
9133         Board initial_position;
9134
9135         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9136
9137         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9138             DisplayError(_("Bad FEN received from engine"), 0);
9139             return ;
9140         } else {
9141            Reset(TRUE, FALSE);
9142            CopyBoard(boards[0], initial_position);
9143            initialRulePlies = FENrulePlies;
9144            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9145            else gameMode = MachinePlaysBlack;
9146            DrawPosition(FALSE, boards[currentMove]);
9147         }
9148         return;
9149     }
9150
9151     /*
9152      * Look for communication commands
9153      */
9154     if (!strncmp(message, "telluser ", 9)) {
9155         if(message[9] == '\\' && message[10] == '\\')
9156             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9157         PlayTellSound();
9158         DisplayNote(message + 9);
9159         return;
9160     }
9161     if (!strncmp(message, "tellusererror ", 14)) {
9162         cps->userError = 1;
9163         if(message[14] == '\\' && message[15] == '\\')
9164             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9165         PlayTellSound();
9166         DisplayError(message + 14, 0);
9167         return;
9168     }
9169     if (!strncmp(message, "tellopponent ", 13)) {
9170       if (appData.icsActive) {
9171         if (loggedOn) {
9172           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9173           SendToICS(buf1);
9174         }
9175       } else {
9176         DisplayNote(message + 13);
9177       }
9178       return;
9179     }
9180     if (!strncmp(message, "tellothers ", 11)) {
9181       if (appData.icsActive) {
9182         if (loggedOn) {
9183           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9184           SendToICS(buf1);
9185         }
9186       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9187       return;
9188     }
9189     if (!strncmp(message, "tellall ", 8)) {
9190       if (appData.icsActive) {
9191         if (loggedOn) {
9192           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9193           SendToICS(buf1);
9194         }
9195       } else {
9196         DisplayNote(message + 8);
9197       }
9198       return;
9199     }
9200     if (strncmp(message, "warning", 7) == 0) {
9201         /* Undocumented feature, use tellusererror in new code */
9202         DisplayError(message, 0);
9203         return;
9204     }
9205     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9206         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9207         strcat(realname, " query");
9208         AskQuestion(realname, buf2, buf1, cps->pr);
9209         return;
9210     }
9211     /* Commands from the engine directly to ICS.  We don't allow these to be
9212      *  sent until we are logged on. Crafty kibitzes have been known to
9213      *  interfere with the login process.
9214      */
9215     if (loggedOn) {
9216         if (!strncmp(message, "tellics ", 8)) {
9217             SendToICS(message + 8);
9218             SendToICS("\n");
9219             return;
9220         }
9221         if (!strncmp(message, "tellicsnoalias ", 15)) {
9222             SendToICS(ics_prefix);
9223             SendToICS(message + 15);
9224             SendToICS("\n");
9225             return;
9226         }
9227         /* The following are for backward compatibility only */
9228         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9229             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9230             SendToICS(ics_prefix);
9231             SendToICS(message);
9232             SendToICS("\n");
9233             return;
9234         }
9235     }
9236     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9237         if(initPing == cps->lastPong) {
9238             if(gameInfo.variant == VariantUnknown) {
9239                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9240                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9241                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9242             }
9243             initPing = -1;
9244         }
9245         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9246             abortEngineThink = FALSE;
9247             DisplayMessage("", "");
9248             ThawUI();
9249         }
9250         return;
9251     }
9252     if(!strncmp(message, "highlight ", 10)) {
9253         if(appData.testLegality && !*engineVariant && appData.markers) return;
9254         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9255         return;
9256     }
9257     if(!strncmp(message, "click ", 6)) {
9258         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9259         if(appData.testLegality || !appData.oneClick) return;
9260         sscanf(message+6, "%c%d%c", &f, &y, &c);
9261         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9262         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9263         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9264         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9265         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9266         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9267             LeftClick(Release, lastLeftX, lastLeftY);
9268         controlKey  = (c == ',');
9269         LeftClick(Press, x, y);
9270         LeftClick(Release, x, y);
9271         first.highlight = f;
9272         return;
9273     }
9274     /*
9275      * If the move is illegal, cancel it and redraw the board.
9276      * Also deal with other error cases.  Matching is rather loose
9277      * here to accommodate engines written before the spec.
9278      */
9279     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9280         strncmp(message, "Error", 5) == 0) {
9281         if (StrStr(message, "name") ||
9282             StrStr(message, "rating") || StrStr(message, "?") ||
9283             StrStr(message, "result") || StrStr(message, "board") ||
9284             StrStr(message, "bk") || StrStr(message, "computer") ||
9285             StrStr(message, "variant") || StrStr(message, "hint") ||
9286             StrStr(message, "random") || StrStr(message, "depth") ||
9287             StrStr(message, "accepted")) {
9288             return;
9289         }
9290         if (StrStr(message, "protover")) {
9291           /* Program is responding to input, so it's apparently done
9292              initializing, and this error message indicates it is
9293              protocol version 1.  So we don't need to wait any longer
9294              for it to initialize and send feature commands. */
9295           FeatureDone(cps, 1);
9296           cps->protocolVersion = 1;
9297           return;
9298         }
9299         cps->maybeThinking = FALSE;
9300
9301         if (StrStr(message, "draw")) {
9302             /* Program doesn't have "draw" command */
9303             cps->sendDrawOffers = 0;
9304             return;
9305         }
9306         if (cps->sendTime != 1 &&
9307             (StrStr(message, "time") || StrStr(message, "otim"))) {
9308           /* Program apparently doesn't have "time" or "otim" command */
9309           cps->sendTime = 0;
9310           return;
9311         }
9312         if (StrStr(message, "analyze")) {
9313             cps->analysisSupport = FALSE;
9314             cps->analyzing = FALSE;
9315 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9316             EditGameEvent(); // [HGM] try to preserve loaded game
9317             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9318             DisplayError(buf2, 0);
9319             return;
9320         }
9321         if (StrStr(message, "(no matching move)st")) {
9322           /* Special kludge for GNU Chess 4 only */
9323           cps->stKludge = TRUE;
9324           SendTimeControl(cps, movesPerSession, timeControl,
9325                           timeIncrement, appData.searchDepth,
9326                           searchTime);
9327           return;
9328         }
9329         if (StrStr(message, "(no matching move)sd")) {
9330           /* Special kludge for GNU Chess 4 only */
9331           cps->sdKludge = TRUE;
9332           SendTimeControl(cps, movesPerSession, timeControl,
9333                           timeIncrement, appData.searchDepth,
9334                           searchTime);
9335           return;
9336         }
9337         if (!StrStr(message, "llegal")) {
9338             return;
9339         }
9340         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9341             gameMode == IcsIdle) return;
9342         if (forwardMostMove <= backwardMostMove) return;
9343         if (pausing) PauseEvent();
9344       if(appData.forceIllegal) {
9345             // [HGM] illegal: machine refused move; force position after move into it
9346           SendToProgram("force\n", cps);
9347           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9348                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9349                 // when black is to move, while there might be nothing on a2 or black
9350                 // might already have the move. So send the board as if white has the move.
9351                 // But first we must change the stm of the engine, as it refused the last move
9352                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9353                 if(WhiteOnMove(forwardMostMove)) {
9354                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9355                     SendBoard(cps, forwardMostMove); // kludgeless board
9356                 } else {
9357                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9358                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9359                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9360                 }
9361           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9362             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9363                  gameMode == TwoMachinesPlay)
9364               SendToProgram("go\n", cps);
9365             return;
9366       } else
9367         if (gameMode == PlayFromGameFile) {
9368             /* Stop reading this game file */
9369             gameMode = EditGame;
9370             ModeHighlight();
9371         }
9372         /* [HGM] illegal-move claim should forfeit game when Xboard */
9373         /* only passes fully legal moves                            */
9374         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9375             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9376                                 "False illegal-move claim", GE_XBOARD );
9377             return; // do not take back move we tested as valid
9378         }
9379         currentMove = forwardMostMove-1;
9380         DisplayMove(currentMove-1); /* before DisplayMoveError */
9381         SwitchClocks(forwardMostMove-1); // [HGM] race
9382         DisplayBothClocks();
9383         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9384                 parseList[currentMove], _(cps->which));
9385         DisplayMoveError(buf1);
9386         DrawPosition(FALSE, boards[currentMove]);
9387
9388         SetUserThinkingEnables();
9389         return;
9390     }
9391     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9392         /* Program has a broken "time" command that
9393            outputs a string not ending in newline.
9394            Don't use it. */
9395         cps->sendTime = 0;
9396     }
9397     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9398         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9399             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9400     }
9401
9402     /*
9403      * If chess program startup fails, exit with an error message.
9404      * Attempts to recover here are futile. [HGM] Well, we try anyway
9405      */
9406     if ((StrStr(message, "unknown host") != NULL)
9407         || (StrStr(message, "No remote directory") != NULL)
9408         || (StrStr(message, "not found") != NULL)
9409         || (StrStr(message, "No such file") != NULL)
9410         || (StrStr(message, "can't alloc") != NULL)
9411         || (StrStr(message, "Permission denied") != NULL)) {
9412
9413         cps->maybeThinking = FALSE;
9414         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9415                 _(cps->which), cps->program, cps->host, message);
9416         RemoveInputSource(cps->isr);
9417         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9418             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9419             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9420         }
9421         return;
9422     }
9423
9424     /*
9425      * Look for hint output
9426      */
9427     if (sscanf(message, "Hint: %s", buf1) == 1) {
9428         if (cps == &first && hintRequested) {
9429             hintRequested = FALSE;
9430             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9431                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9432                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9433                                     PosFlags(forwardMostMove),
9434                                     fromY, fromX, toY, toX, promoChar, buf1);
9435                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9436                 DisplayInformation(buf2);
9437             } else {
9438                 /* Hint move could not be parsed!? */
9439               snprintf(buf2, sizeof(buf2),
9440                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9441                         buf1, _(cps->which));
9442                 DisplayError(buf2, 0);
9443             }
9444         } else {
9445           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9446         }
9447         return;
9448     }
9449
9450     /*
9451      * Ignore other messages if game is not in progress
9452      */
9453     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9454         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9455
9456     /*
9457      * look for win, lose, draw, or draw offer
9458      */
9459     if (strncmp(message, "1-0", 3) == 0) {
9460         char *p, *q, *r = "";
9461         p = strchr(message, '{');
9462         if (p) {
9463             q = strchr(p, '}');
9464             if (q) {
9465                 *q = NULLCHAR;
9466                 r = p + 1;
9467             }
9468         }
9469         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9470         return;
9471     } else if (strncmp(message, "0-1", 3) == 0) {
9472         char *p, *q, *r = "";
9473         p = strchr(message, '{');
9474         if (p) {
9475             q = strchr(p, '}');
9476             if (q) {
9477                 *q = NULLCHAR;
9478                 r = p + 1;
9479             }
9480         }
9481         /* Kludge for Arasan 4.1 bug */
9482         if (strcmp(r, "Black resigns") == 0) {
9483             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9484             return;
9485         }
9486         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9487         return;
9488     } else if (strncmp(message, "1/2", 3) == 0) {
9489         char *p, *q, *r = "";
9490         p = strchr(message, '{');
9491         if (p) {
9492             q = strchr(p, '}');
9493             if (q) {
9494                 *q = NULLCHAR;
9495                 r = p + 1;
9496             }
9497         }
9498
9499         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9500         return;
9501
9502     } else if (strncmp(message, "White resign", 12) == 0) {
9503         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9504         return;
9505     } else if (strncmp(message, "Black resign", 12) == 0) {
9506         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9507         return;
9508     } else if (strncmp(message, "White matches", 13) == 0 ||
9509                strncmp(message, "Black matches", 13) == 0   ) {
9510         /* [HGM] ignore GNUShogi noises */
9511         return;
9512     } else if (strncmp(message, "White", 5) == 0 &&
9513                message[5] != '(' &&
9514                StrStr(message, "Black") == NULL) {
9515         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9516         return;
9517     } else if (strncmp(message, "Black", 5) == 0 &&
9518                message[5] != '(') {
9519         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9520         return;
9521     } else if (strcmp(message, "resign") == 0 ||
9522                strcmp(message, "computer resigns") == 0) {
9523         switch (gameMode) {
9524           case MachinePlaysBlack:
9525           case IcsPlayingBlack:
9526             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9527             break;
9528           case MachinePlaysWhite:
9529           case IcsPlayingWhite:
9530             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9531             break;
9532           case TwoMachinesPlay:
9533             if (cps->twoMachinesColor[0] == 'w')
9534               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9535             else
9536               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9537             break;
9538           default:
9539             /* can't happen */
9540             break;
9541         }
9542         return;
9543     } else if (strncmp(message, "opponent mates", 14) == 0) {
9544         switch (gameMode) {
9545           case MachinePlaysBlack:
9546           case IcsPlayingBlack:
9547             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9548             break;
9549           case MachinePlaysWhite:
9550           case IcsPlayingWhite:
9551             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9552             break;
9553           case TwoMachinesPlay:
9554             if (cps->twoMachinesColor[0] == 'w')
9555               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9556             else
9557               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9558             break;
9559           default:
9560             /* can't happen */
9561             break;
9562         }
9563         return;
9564     } else if (strncmp(message, "computer mates", 14) == 0) {
9565         switch (gameMode) {
9566           case MachinePlaysBlack:
9567           case IcsPlayingBlack:
9568             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9569             break;
9570           case MachinePlaysWhite:
9571           case IcsPlayingWhite:
9572             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9573             break;
9574           case TwoMachinesPlay:
9575             if (cps->twoMachinesColor[0] == 'w')
9576               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9577             else
9578               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9579             break;
9580           default:
9581             /* can't happen */
9582             break;
9583         }
9584         return;
9585     } else if (strncmp(message, "checkmate", 9) == 0) {
9586         if (WhiteOnMove(forwardMostMove)) {
9587             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9588         } else {
9589             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9590         }
9591         return;
9592     } else if (strstr(message, "Draw") != NULL ||
9593                strstr(message, "game is a draw") != NULL) {
9594         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9595         return;
9596     } else if (strstr(message, "offer") != NULL &&
9597                strstr(message, "draw") != NULL) {
9598 #if ZIPPY
9599         if (appData.zippyPlay && first.initDone) {
9600             /* Relay offer to ICS */
9601             SendToICS(ics_prefix);
9602             SendToICS("draw\n");
9603         }
9604 #endif
9605         cps->offeredDraw = 2; /* valid until this engine moves twice */
9606         if (gameMode == TwoMachinesPlay) {
9607             if (cps->other->offeredDraw) {
9608                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9609             /* [HGM] in two-machine mode we delay relaying draw offer      */
9610             /* until after we also have move, to see if it is really claim */
9611             }
9612         } else if (gameMode == MachinePlaysWhite ||
9613                    gameMode == MachinePlaysBlack) {
9614           if (userOfferedDraw) {
9615             DisplayInformation(_("Machine accepts your draw offer"));
9616             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9617           } else {
9618             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9619           }
9620         }
9621     }
9622
9623
9624     /*
9625      * Look for thinking output
9626      */
9627     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9628           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9629                                 ) {
9630         int plylev, mvleft, mvtot, curscore, time;
9631         char mvname[MOVE_LEN];
9632         u64 nodes; // [DM]
9633         char plyext;
9634         int ignore = FALSE;
9635         int prefixHint = FALSE;
9636         mvname[0] = NULLCHAR;
9637
9638         switch (gameMode) {
9639           case MachinePlaysBlack:
9640           case IcsPlayingBlack:
9641             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9642             break;
9643           case MachinePlaysWhite:
9644           case IcsPlayingWhite:
9645             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9646             break;
9647           case AnalyzeMode:
9648           case AnalyzeFile:
9649             break;
9650           case IcsObserving: /* [DM] icsEngineAnalyze */
9651             if (!appData.icsEngineAnalyze) ignore = TRUE;
9652             break;
9653           case TwoMachinesPlay:
9654             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9655                 ignore = TRUE;
9656             }
9657             break;
9658           default:
9659             ignore = TRUE;
9660             break;
9661         }
9662
9663         if (!ignore) {
9664             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9665             buf1[0] = NULLCHAR;
9666             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9667                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9668                 char score_buf[MSG_SIZ];
9669
9670                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9671                     nodes += u64Const(0x100000000);
9672
9673                 if (plyext != ' ' && plyext != '\t') {
9674                     time *= 100;
9675                 }
9676
9677                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9678                 if( cps->scoreIsAbsolute &&
9679                     ( gameMode == MachinePlaysBlack ||
9680                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9681                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9682                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9683                      !WhiteOnMove(currentMove)
9684                     ) )
9685                 {
9686                     curscore = -curscore;
9687                 }
9688
9689                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9690
9691                 if(*bestMove) { // rememer time best EPD move was first found
9692                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9693                     ChessMove mt;
9694                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9695                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9696                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9697                 }
9698
9699                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9700                         char buf[MSG_SIZ];
9701                         FILE *f;
9702                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9703                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9704                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9705                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9706                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9707                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9708                                 fclose(f);
9709                         }
9710                         else
9711                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9712                           DisplayError(_("failed writing PV"), 0);
9713                 }
9714
9715                 tempStats.depth = plylev;
9716                 tempStats.nodes = nodes;
9717                 tempStats.time = time;
9718                 tempStats.score = curscore;
9719                 tempStats.got_only_move = 0;
9720
9721                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9722                         int ticklen;
9723
9724                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9725                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9726                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9727                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9728                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9729                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9730                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9731                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9732                 }
9733
9734                 /* Buffer overflow protection */
9735                 if (pv[0] != NULLCHAR) {
9736                     if (strlen(pv) >= sizeof(tempStats.movelist)
9737                         && appData.debugMode) {
9738                         fprintf(debugFP,
9739                                 "PV is too long; using the first %u bytes.\n",
9740                                 (unsigned) sizeof(tempStats.movelist) - 1);
9741                     }
9742
9743                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9744                 } else {
9745                     sprintf(tempStats.movelist, " no PV\n");
9746                 }
9747
9748                 if (tempStats.seen_stat) {
9749                     tempStats.ok_to_send = 1;
9750                 }
9751
9752                 if (strchr(tempStats.movelist, '(') != NULL) {
9753                     tempStats.line_is_book = 1;
9754                     tempStats.nr_moves = 0;
9755                     tempStats.moves_left = 0;
9756                 } else {
9757                     tempStats.line_is_book = 0;
9758                 }
9759
9760                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9761                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9762
9763                 SendProgramStatsToFrontend( cps, &tempStats );
9764
9765                 /*
9766                     [AS] Protect the thinkOutput buffer from overflow... this
9767                     is only useful if buf1 hasn't overflowed first!
9768                 */
9769                 if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
9770                 if(curscore >= MATE_SCORE) 
9771                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9772                 else if(curscore <= -MATE_SCORE) 
9773                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9774                 else
9775                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9776                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9777                          plylev,
9778                          (gameMode == TwoMachinesPlay ?
9779                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9780                          score_buf,
9781                          prefixHint ? lastHint : "",
9782                          prefixHint ? " " : "" );
9783
9784                 if( buf1[0] != NULLCHAR ) {
9785                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9786
9787                     if( strlen(pv) > max_len ) {
9788                         if( appData.debugMode) {
9789                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9790                         }
9791                         pv[max_len+1] = '\0';
9792                     }
9793
9794                     strcat( thinkOutput, pv);
9795                 }
9796
9797                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9798                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9799                     DisplayMove(currentMove - 1);
9800                 }
9801                 return;
9802
9803             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9804                 /* crafty (9.25+) says "(only move) <move>"
9805                  * if there is only 1 legal move
9806                  */
9807                 sscanf(p, "(only move) %s", buf1);
9808                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9809                 sprintf(programStats.movelist, "%s (only move)", buf1);
9810                 programStats.depth = 1;
9811                 programStats.nr_moves = 1;
9812                 programStats.moves_left = 1;
9813                 programStats.nodes = 1;
9814                 programStats.time = 1;
9815                 programStats.got_only_move = 1;
9816
9817                 /* Not really, but we also use this member to
9818                    mean "line isn't going to change" (Crafty
9819                    isn't searching, so stats won't change) */
9820                 programStats.line_is_book = 1;
9821
9822                 SendProgramStatsToFrontend( cps, &programStats );
9823
9824                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9825                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9826                     DisplayMove(currentMove - 1);
9827                 }
9828                 return;
9829             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9830                               &time, &nodes, &plylev, &mvleft,
9831                               &mvtot, mvname) >= 5) {
9832                 /* The stat01: line is from Crafty (9.29+) in response
9833                    to the "." command */
9834                 programStats.seen_stat = 1;
9835                 cps->maybeThinking = TRUE;
9836
9837                 if (programStats.got_only_move || !appData.periodicUpdates)
9838                   return;
9839
9840                 programStats.depth = plylev;
9841                 programStats.time = time;
9842                 programStats.nodes = nodes;
9843                 programStats.moves_left = mvleft;
9844                 programStats.nr_moves = mvtot;
9845                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9846                 programStats.ok_to_send = 1;
9847                 programStats.movelist[0] = '\0';
9848
9849                 SendProgramStatsToFrontend( cps, &programStats );
9850
9851                 return;
9852
9853             } else if (strncmp(message,"++",2) == 0) {
9854                 /* Crafty 9.29+ outputs this */
9855                 programStats.got_fail = 2;
9856                 return;
9857
9858             } else if (strncmp(message,"--",2) == 0) {
9859                 /* Crafty 9.29+ outputs this */
9860                 programStats.got_fail = 1;
9861                 return;
9862
9863             } else if (thinkOutput[0] != NULLCHAR &&
9864                        strncmp(message, "    ", 4) == 0) {
9865                 unsigned message_len;
9866
9867                 p = message;
9868                 while (*p && *p == ' ') p++;
9869
9870                 message_len = strlen( p );
9871
9872                 /* [AS] Avoid buffer overflow */
9873                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9874                     strcat(thinkOutput, " ");
9875                     strcat(thinkOutput, p);
9876                 }
9877
9878                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9879                     strcat(programStats.movelist, " ");
9880                     strcat(programStats.movelist, p);
9881                 }
9882
9883                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9884                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9885                     DisplayMove(currentMove - 1);
9886                 }
9887                 return;
9888             }
9889         }
9890         else {
9891             buf1[0] = NULLCHAR;
9892
9893             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9894                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9895             {
9896                 ChessProgramStats cpstats;
9897
9898                 if (plyext != ' ' && plyext != '\t') {
9899                     time *= 100;
9900                 }
9901
9902                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9903                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9904                     curscore = -curscore;
9905                 }
9906
9907                 cpstats.depth = plylev;
9908                 cpstats.nodes = nodes;
9909                 cpstats.time = time;
9910                 cpstats.score = curscore;
9911                 cpstats.got_only_move = 0;
9912                 cpstats.movelist[0] = '\0';
9913
9914                 if (buf1[0] != NULLCHAR) {
9915                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9916                 }
9917
9918                 cpstats.ok_to_send = 0;
9919                 cpstats.line_is_book = 0;
9920                 cpstats.nr_moves = 0;
9921                 cpstats.moves_left = 0;
9922
9923                 SendProgramStatsToFrontend( cps, &cpstats );
9924             }
9925         }
9926     }
9927 }
9928
9929
9930 /* Parse a game score from the character string "game", and
9931    record it as the history of the current game.  The game
9932    score is NOT assumed to start from the standard position.
9933    The display is not updated in any way.
9934    */
9935 void
9936 ParseGameHistory (char *game)
9937 {
9938     ChessMove moveType;
9939     int fromX, fromY, toX, toY, boardIndex;
9940     char promoChar;
9941     char *p, *q;
9942     char buf[MSG_SIZ];
9943
9944     if (appData.debugMode)
9945       fprintf(debugFP, "Parsing game history: %s\n", game);
9946
9947     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9948     gameInfo.site = StrSave(appData.icsHost);
9949     gameInfo.date = PGNDate();
9950     gameInfo.round = StrSave("-");
9951
9952     /* Parse out names of players */
9953     while (*game == ' ') game++;
9954     p = buf;
9955     while (*game != ' ') *p++ = *game++;
9956     *p = NULLCHAR;
9957     gameInfo.white = StrSave(buf);
9958     while (*game == ' ') game++;
9959     p = buf;
9960     while (*game != ' ' && *game != '\n') *p++ = *game++;
9961     *p = NULLCHAR;
9962     gameInfo.black = StrSave(buf);
9963
9964     /* Parse moves */
9965     boardIndex = blackPlaysFirst ? 1 : 0;
9966     yynewstr(game);
9967     for (;;) {
9968         yyboardindex = boardIndex;
9969         moveType = (ChessMove) Myylex();
9970         switch (moveType) {
9971           case IllegalMove:             /* maybe suicide chess, etc. */
9972   if (appData.debugMode) {
9973     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9974     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9975     setbuf(debugFP, NULL);
9976   }
9977           case WhitePromotion:
9978           case BlackPromotion:
9979           case WhiteNonPromotion:
9980           case BlackNonPromotion:
9981           case NormalMove:
9982           case FirstLeg:
9983           case WhiteCapturesEnPassant:
9984           case BlackCapturesEnPassant:
9985           case WhiteKingSideCastle:
9986           case WhiteQueenSideCastle:
9987           case BlackKingSideCastle:
9988           case BlackQueenSideCastle:
9989           case WhiteKingSideCastleWild:
9990           case WhiteQueenSideCastleWild:
9991           case BlackKingSideCastleWild:
9992           case BlackQueenSideCastleWild:
9993           /* PUSH Fabien */
9994           case WhiteHSideCastleFR:
9995           case WhiteASideCastleFR:
9996           case BlackHSideCastleFR:
9997           case BlackASideCastleFR:
9998           /* POP Fabien */
9999             fromX = currentMoveString[0] - AAA;
10000             fromY = currentMoveString[1] - ONE;
10001             toX = currentMoveString[2] - AAA;
10002             toY = currentMoveString[3] - ONE;
10003             promoChar = currentMoveString[4];
10004             break;
10005           case WhiteDrop:
10006           case BlackDrop:
10007             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10008             fromX = moveType == WhiteDrop ?
10009               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10010             (int) CharToPiece(ToLower(currentMoveString[0]));
10011             fromY = DROP_RANK;
10012             toX = currentMoveString[2] - AAA;
10013             toY = currentMoveString[3] - ONE;
10014             promoChar = NULLCHAR;
10015             break;
10016           case AmbiguousMove:
10017             /* bug? */
10018             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10019   if (appData.debugMode) {
10020     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10021     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10022     setbuf(debugFP, NULL);
10023   }
10024             DisplayError(buf, 0);
10025             return;
10026           case ImpossibleMove:
10027             /* bug? */
10028             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10029   if (appData.debugMode) {
10030     fprintf(debugFP, "Impossible 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             DisplayError(buf, 0);
10035             return;
10036           case EndOfFile:
10037             if (boardIndex < backwardMostMove) {
10038                 /* Oops, gap.  How did that happen? */
10039                 DisplayError(_("Gap in move list"), 0);
10040                 return;
10041             }
10042             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10043             if (boardIndex > forwardMostMove) {
10044                 forwardMostMove = boardIndex;
10045             }
10046             return;
10047           case ElapsedTime:
10048             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10049                 strcat(parseList[boardIndex-1], " ");
10050                 strcat(parseList[boardIndex-1], yy_text);
10051             }
10052             continue;
10053           case Comment:
10054           case PGNTag:
10055           case NAG:
10056           default:
10057             /* ignore */
10058             continue;
10059           case WhiteWins:
10060           case BlackWins:
10061           case GameIsDrawn:
10062           case GameUnfinished:
10063             if (gameMode == IcsExamining) {
10064                 if (boardIndex < backwardMostMove) {
10065                     /* Oops, gap.  How did that happen? */
10066                     return;
10067                 }
10068                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10069                 return;
10070             }
10071             gameInfo.result = moveType;
10072             p = strchr(yy_text, '{');
10073             if (p == NULL) p = strchr(yy_text, '(');
10074             if (p == NULL) {
10075                 p = yy_text;
10076                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10077             } else {
10078                 q = strchr(p, *p == '{' ? '}' : ')');
10079                 if (q != NULL) *q = NULLCHAR;
10080                 p++;
10081             }
10082             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10083             gameInfo.resultDetails = StrSave(p);
10084             continue;
10085         }
10086         if (boardIndex >= forwardMostMove &&
10087             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10088             backwardMostMove = blackPlaysFirst ? 1 : 0;
10089             return;
10090         }
10091         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10092                                  fromY, fromX, toY, toX, promoChar,
10093                                  parseList[boardIndex]);
10094         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10095         /* currentMoveString is set as a side-effect of yylex */
10096         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10097         strcat(moveList[boardIndex], "\n");
10098         boardIndex++;
10099         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10100         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10101           case MT_NONE:
10102           case MT_STALEMATE:
10103           default:
10104             break;
10105           case MT_CHECK:
10106             if(!IS_SHOGI(gameInfo.variant))
10107                 strcat(parseList[boardIndex - 1], "+");
10108             break;
10109           case MT_CHECKMATE:
10110           case MT_STAINMATE:
10111             strcat(parseList[boardIndex - 1], "#");
10112             break;
10113         }
10114     }
10115 }
10116
10117
10118 /* Apply a move to the given board  */
10119 void
10120 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10121 {
10122   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10123   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10124
10125     /* [HGM] compute & store e.p. status and castling rights for new position */
10126     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10127
10128       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10129       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10130       board[EP_STATUS] = EP_NONE;
10131       board[EP_FILE] = board[EP_RANK] = 100;
10132
10133   if (fromY == DROP_RANK) {
10134         /* must be first */
10135         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10136             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10137             return;
10138         }
10139         piece = board[toY][toX] = (ChessSquare) fromX;
10140   } else {
10141 //      ChessSquare victim;
10142       int i;
10143
10144       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10145 //           victim = board[killY][killX],
10146            killed = board[killY][killX],
10147            board[killY][killX] = EmptySquare,
10148            board[EP_STATUS] = EP_CAPTURE;
10149            if( kill2X >= 0 && kill2Y >= 0)
10150              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10151       }
10152
10153       if( board[toY][toX] != EmptySquare ) {
10154            board[EP_STATUS] = EP_CAPTURE;
10155            if( (fromX != toX || fromY != toY) && // not igui!
10156                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10157                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10158                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10159            }
10160       }
10161
10162       pawn = board[fromY][fromX];
10163       if( pawn == WhiteLance || pawn == BlackLance ) {
10164            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10165                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10166                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10167            }
10168       }
10169       if( pawn == WhitePawn ) {
10170            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10171                board[EP_STATUS] = EP_PAWN_MOVE;
10172            if( toY-fromY>=2) {
10173                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10174                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10175                         gameInfo.variant != VariantBerolina || toX < fromX)
10176                       board[EP_STATUS] = toX | berolina;
10177                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10178                         gameInfo.variant != VariantBerolina || toX > fromX)
10179                       board[EP_STATUS] = toX;
10180            }
10181       } else
10182       if( pawn == BlackPawn ) {
10183            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10184                board[EP_STATUS] = EP_PAWN_MOVE;
10185            if( toY-fromY<= -2) {
10186                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10187                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10188                         gameInfo.variant != VariantBerolina || toX < fromX)
10189                       board[EP_STATUS] = toX | berolina;
10190                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10191                         gameInfo.variant != VariantBerolina || toX > fromX)
10192                       board[EP_STATUS] = toX;
10193            }
10194        }
10195
10196        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10197        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10198        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10199        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10200
10201        for(i=0; i<nrCastlingRights; i++) {
10202            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10203               board[CASTLING][i] == toX   && castlingRank[i] == toY
10204              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10205        }
10206
10207        if(gameInfo.variant == VariantSChess) { // update virginity
10208            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10209            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10210            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10211            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10212        }
10213
10214      if (fromX == toX && fromY == toY) return;
10215
10216      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10217      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10218      if(gameInfo.variant == VariantKnightmate)
10219          king += (int) WhiteUnicorn - (int) WhiteKing;
10220
10221     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10222        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10223         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10224         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10225         board[EP_STATUS] = EP_NONE; // capture was fake!
10226     } else
10227     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10228         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10229         board[toY][toX] = piece;
10230         board[EP_STATUS] = EP_NONE; // capture was fake!
10231     } else
10232     /* Code added by Tord: */
10233     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10234     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10235         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10236       board[EP_STATUS] = EP_NONE; // capture was fake!
10237       board[fromY][fromX] = EmptySquare;
10238       board[toY][toX] = EmptySquare;
10239       if((toX > fromX) != (piece == WhiteRook)) {
10240         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10241       } else {
10242         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10243       }
10244     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10245                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10246       board[EP_STATUS] = EP_NONE;
10247       board[fromY][fromX] = EmptySquare;
10248       board[toY][toX] = EmptySquare;
10249       if((toX > fromX) != (piece == BlackRook)) {
10250         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10251       } else {
10252         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10253       }
10254     /* End of code added by Tord */
10255
10256     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10257         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10258         board[toY][toX] = piece;
10259     } else if (board[fromY][fromX] == king
10260         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10261         && toY == fromY && toX > fromX+1) {
10262         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10263         board[fromY][toX-1] = board[fromY][rookX];
10264         board[fromY][rookX] = EmptySquare;
10265         board[fromY][fromX] = EmptySquare;
10266         board[toY][toX] = king;
10267     } else if (board[fromY][fromX] == king
10268         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10269                && toY == fromY && toX < fromX-1) {
10270         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10271         board[fromY][toX+1] = board[fromY][rookX];
10272         board[fromY][rookX] = EmptySquare;
10273         board[fromY][fromX] = EmptySquare;
10274         board[toY][toX] = king;
10275     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10276                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10277                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10278                ) {
10279         /* white pawn promotion */
10280         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10281         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10282             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10283         board[fromY][fromX] = EmptySquare;
10284     } else if ((fromY >= BOARD_HEIGHT>>1)
10285                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10286                && (toX != fromX)
10287                && gameInfo.variant != VariantXiangqi
10288                && gameInfo.variant != VariantBerolina
10289                && (pawn == WhitePawn)
10290                && (board[toY][toX] == EmptySquare)) {
10291         board[fromY][fromX] = EmptySquare;
10292         board[toY][toX] = piece;
10293         if(toY == epRank - 128 + 1)
10294             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10295         else
10296             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10297     } else if ((fromY == BOARD_HEIGHT-4)
10298                && (toX == fromX)
10299                && gameInfo.variant == VariantBerolina
10300                && (board[fromY][fromX] == WhitePawn)
10301                && (board[toY][toX] == EmptySquare)) {
10302         board[fromY][fromX] = EmptySquare;
10303         board[toY][toX] = WhitePawn;
10304         if(oldEP & EP_BEROLIN_A) {
10305                 captured = board[fromY][fromX-1];
10306                 board[fromY][fromX-1] = EmptySquare;
10307         }else{  captured = board[fromY][fromX+1];
10308                 board[fromY][fromX+1] = EmptySquare;
10309         }
10310     } else if (board[fromY][fromX] == king
10311         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10312                && toY == fromY && toX > fromX+1) {
10313         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10314         board[fromY][toX-1] = board[fromY][rookX];
10315         board[fromY][rookX] = EmptySquare;
10316         board[fromY][fromX] = EmptySquare;
10317         board[toY][toX] = king;
10318     } else if (board[fromY][fromX] == king
10319         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10320                && toY == fromY && toX < fromX-1) {
10321         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10322         board[fromY][toX+1] = board[fromY][rookX];
10323         board[fromY][rookX] = EmptySquare;
10324         board[fromY][fromX] = EmptySquare;
10325         board[toY][toX] = king;
10326     } else if (fromY == 7 && fromX == 3
10327                && board[fromY][fromX] == BlackKing
10328                && toY == 7 && toX == 5) {
10329         board[fromY][fromX] = EmptySquare;
10330         board[toY][toX] = BlackKing;
10331         board[fromY][7] = EmptySquare;
10332         board[toY][4] = BlackRook;
10333     } else if (fromY == 7 && fromX == 3
10334                && board[fromY][fromX] == BlackKing
10335                && toY == 7 && toX == 1) {
10336         board[fromY][fromX] = EmptySquare;
10337         board[toY][toX] = BlackKing;
10338         board[fromY][0] = EmptySquare;
10339         board[toY][2] = BlackRook;
10340     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10341                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10342                && toY < promoRank && promoChar
10343                ) {
10344         /* black pawn promotion */
10345         board[toY][toX] = CharToPiece(ToLower(promoChar));
10346         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10347             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10348         board[fromY][fromX] = EmptySquare;
10349     } else if ((fromY < BOARD_HEIGHT>>1)
10350                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10351                && (toX != fromX)
10352                && gameInfo.variant != VariantXiangqi
10353                && gameInfo.variant != VariantBerolina
10354                && (pawn == BlackPawn)
10355                && (board[toY][toX] == EmptySquare)) {
10356         board[fromY][fromX] = EmptySquare;
10357         board[toY][toX] = piece;
10358         if(toY == epRank - 128 - 1)
10359             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10360         else
10361             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10362     } else if ((fromY == 3)
10363                && (toX == fromX)
10364                && gameInfo.variant == VariantBerolina
10365                && (board[fromY][fromX] == BlackPawn)
10366                && (board[toY][toX] == EmptySquare)) {
10367         board[fromY][fromX] = EmptySquare;
10368         board[toY][toX] = BlackPawn;
10369         if(oldEP & EP_BEROLIN_A) {
10370                 captured = board[fromY][fromX-1];
10371                 board[fromY][fromX-1] = EmptySquare;
10372         }else{  captured = board[fromY][fromX+1];
10373                 board[fromY][fromX+1] = EmptySquare;
10374         }
10375     } else {
10376         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10377         board[fromY][fromX] = EmptySquare;
10378         board[toY][toX] = piece;
10379     }
10380   }
10381
10382     if (gameInfo.holdingsWidth != 0) {
10383
10384       /* !!A lot more code needs to be written to support holdings  */
10385       /* [HGM] OK, so I have written it. Holdings are stored in the */
10386       /* penultimate board files, so they are automaticlly stored   */
10387       /* in the game history.                                       */
10388       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10389                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10390         /* Delete from holdings, by decreasing count */
10391         /* and erasing image if necessary            */
10392         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10393         if(p < (int) BlackPawn) { /* white drop */
10394              p -= (int)WhitePawn;
10395                  p = PieceToNumber((ChessSquare)p);
10396              if(p >= gameInfo.holdingsSize) p = 0;
10397              if(--board[p][BOARD_WIDTH-2] <= 0)
10398                   board[p][BOARD_WIDTH-1] = EmptySquare;
10399              if((int)board[p][BOARD_WIDTH-2] < 0)
10400                         board[p][BOARD_WIDTH-2] = 0;
10401         } else {                  /* black drop */
10402              p -= (int)BlackPawn;
10403                  p = PieceToNumber((ChessSquare)p);
10404              if(p >= gameInfo.holdingsSize) p = 0;
10405              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10406                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10407              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10408                         board[BOARD_HEIGHT-1-p][1] = 0;
10409         }
10410       }
10411       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10412           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10413         /* [HGM] holdings: Add to holdings, if holdings exist */
10414         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10415                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10416                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10417         }
10418         p = (int) captured;
10419         if (p >= (int) BlackPawn) {
10420           p -= (int)BlackPawn;
10421           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10422                   /* Restore shogi-promoted piece to its original  first */
10423                   captured = (ChessSquare) (DEMOTED(captured));
10424                   p = DEMOTED(p);
10425           }
10426           p = PieceToNumber((ChessSquare)p);
10427           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10428           board[p][BOARD_WIDTH-2]++;
10429           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10430         } else {
10431           p -= (int)WhitePawn;
10432           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10433                   captured = (ChessSquare) (DEMOTED(captured));
10434                   p = DEMOTED(p);
10435           }
10436           p = PieceToNumber((ChessSquare)p);
10437           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10438           board[BOARD_HEIGHT-1-p][1]++;
10439           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10440         }
10441       }
10442     } else if (gameInfo.variant == VariantAtomic) {
10443       if (captured != EmptySquare) {
10444         int y, x;
10445         for (y = toY-1; y <= toY+1; y++) {
10446           for (x = toX-1; x <= toX+1; x++) {
10447             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10448                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10449               board[y][x] = EmptySquare;
10450             }
10451           }
10452         }
10453         board[toY][toX] = EmptySquare;
10454       }
10455     }
10456
10457     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10458         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10459     } else
10460     if(promoChar == '+') {
10461         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10462         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10463         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10464           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10465     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10466         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10467         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10468            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10469         board[toY][toX] = newPiece;
10470     }
10471     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10472                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10473         // [HGM] superchess: take promotion piece out of holdings
10474         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10475         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10476             if(!--board[k][BOARD_WIDTH-2])
10477                 board[k][BOARD_WIDTH-1] = EmptySquare;
10478         } else {
10479             if(!--board[BOARD_HEIGHT-1-k][1])
10480                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10481         }
10482     }
10483 }
10484
10485 /* Updates forwardMostMove */
10486 void
10487 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10488 {
10489     int x = toX, y = toY;
10490     char *s = parseList[forwardMostMove];
10491     ChessSquare p = boards[forwardMostMove][toY][toX];
10492 //    forwardMostMove++; // [HGM] bare: moved downstream
10493
10494     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10495     (void) CoordsToAlgebraic(boards[forwardMostMove],
10496                              PosFlags(forwardMostMove),
10497                              fromY, fromX, y, x, (killX < 0)*promoChar,
10498                              s);
10499     if(killX >= 0 && killY >= 0)
10500         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0', promoChar);
10501
10502     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10503         int timeLeft; static int lastLoadFlag=0; int king, piece;
10504         piece = boards[forwardMostMove][fromY][fromX];
10505         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10506         if(gameInfo.variant == VariantKnightmate)
10507             king += (int) WhiteUnicorn - (int) WhiteKing;
10508         if(forwardMostMove == 0) {
10509             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10510                 fprintf(serverMoves, "%s;", UserName());
10511             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10512                 fprintf(serverMoves, "%s;", second.tidy);
10513             fprintf(serverMoves, "%s;", first.tidy);
10514             if(gameMode == MachinePlaysWhite)
10515                 fprintf(serverMoves, "%s;", UserName());
10516             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10517                 fprintf(serverMoves, "%s;", second.tidy);
10518         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10519         lastLoadFlag = loadFlag;
10520         // print base move
10521         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10522         // print castling suffix
10523         if( toY == fromY && piece == king ) {
10524             if(toX-fromX > 1)
10525                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10526             if(fromX-toX >1)
10527                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10528         }
10529         // e.p. suffix
10530         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10531              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10532              boards[forwardMostMove][toY][toX] == EmptySquare
10533              && fromX != toX && fromY != toY)
10534                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10535         // promotion suffix
10536         if(promoChar != NULLCHAR) {
10537             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10538                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10539                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10540             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10541         }
10542         if(!loadFlag) {
10543                 char buf[MOVE_LEN*2], *p; int len;
10544             fprintf(serverMoves, "/%d/%d",
10545                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10546             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10547             else                      timeLeft = blackTimeRemaining/1000;
10548             fprintf(serverMoves, "/%d", timeLeft);
10549                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10550                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10551                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10552                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10553             fprintf(serverMoves, "/%s", buf);
10554         }
10555         fflush(serverMoves);
10556     }
10557
10558     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10559         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10560       return;
10561     }
10562     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10563     if (commentList[forwardMostMove+1] != NULL) {
10564         free(commentList[forwardMostMove+1]);
10565         commentList[forwardMostMove+1] = NULL;
10566     }
10567     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10568     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10569     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10570     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10571     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10572     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10573     adjustedClock = FALSE;
10574     gameInfo.result = GameUnfinished;
10575     if (gameInfo.resultDetails != NULL) {
10576         free(gameInfo.resultDetails);
10577         gameInfo.resultDetails = NULL;
10578     }
10579     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10580                               moveList[forwardMostMove - 1]);
10581     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10582       case MT_NONE:
10583       case MT_STALEMATE:
10584       default:
10585         break;
10586       case MT_CHECK:
10587         if(!IS_SHOGI(gameInfo.variant))
10588             strcat(parseList[forwardMostMove - 1], "+");
10589         break;
10590       case MT_CHECKMATE:
10591       case MT_STAINMATE:
10592         strcat(parseList[forwardMostMove - 1], "#");
10593         break;
10594     }
10595 }
10596
10597 /* Updates currentMove if not pausing */
10598 void
10599 ShowMove (int fromX, int fromY, int toX, int toY)
10600 {
10601     int instant = (gameMode == PlayFromGameFile) ?
10602         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10603     if(appData.noGUI) return;
10604     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10605         if (!instant) {
10606             if (forwardMostMove == currentMove + 1) {
10607                 AnimateMove(boards[forwardMostMove - 1],
10608                             fromX, fromY, toX, toY);
10609             }
10610         }
10611         currentMove = forwardMostMove;
10612     }
10613
10614     killX = killY = -1; // [HGM] lion: used up
10615
10616     if (instant) return;
10617
10618     DisplayMove(currentMove - 1);
10619     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10620             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10621                 SetHighlights(fromX, fromY, toX, toY);
10622             }
10623     }
10624     DrawPosition(FALSE, boards[currentMove]);
10625     DisplayBothClocks();
10626     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10627 }
10628
10629 void
10630 SendEgtPath (ChessProgramState *cps)
10631 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10632         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10633
10634         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10635
10636         while(*p) {
10637             char c, *q = name+1, *r, *s;
10638
10639             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10640             while(*p && *p != ',') *q++ = *p++;
10641             *q++ = ':'; *q = 0;
10642             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10643                 strcmp(name, ",nalimov:") == 0 ) {
10644                 // take nalimov path from the menu-changeable option first, if it is defined
10645               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10646                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10647             } else
10648             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10649                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10650                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10651                 s = r = StrStr(s, ":") + 1; // beginning of path info
10652                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10653                 c = *r; *r = 0;             // temporarily null-terminate path info
10654                     *--q = 0;               // strip of trailig ':' from name
10655                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10656                 *r = c;
10657                 SendToProgram(buf,cps);     // send egtbpath command for this format
10658             }
10659             if(*p == ',') p++; // read away comma to position for next format name
10660         }
10661 }
10662
10663 static int
10664 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10665 {
10666       int width = 8, height = 8, holdings = 0;             // most common sizes
10667       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10668       // correct the deviations default for each variant
10669       if( v == VariantXiangqi ) width = 9,  height = 10;
10670       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10671       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10672       if( v == VariantCapablanca || v == VariantCapaRandom ||
10673           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10674                                 width = 10;
10675       if( v == VariantCourier ) width = 12;
10676       if( v == VariantSuper )                            holdings = 8;
10677       if( v == VariantGreat )   width = 10,              holdings = 8;
10678       if( v == VariantSChess )                           holdings = 7;
10679       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10680       if( v == VariantChuChess) width = 10, height = 10;
10681       if( v == VariantChu )     width = 12, height = 12;
10682       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10683              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10684              holdingsSize >= 0 && holdingsSize != holdings;
10685 }
10686
10687 char variantError[MSG_SIZ];
10688
10689 char *
10690 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10691 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10692       char *p, *variant = VariantName(v);
10693       static char b[MSG_SIZ];
10694       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10695            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10696                                                holdingsSize, variant); // cook up sized variant name
10697            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10698            if(StrStr(list, b) == NULL) {
10699                // specific sized variant not known, check if general sizing allowed
10700                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10701                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10702                             boardWidth, boardHeight, holdingsSize, engine);
10703                    return NULL;
10704                }
10705                /* [HGM] here we really should compare with the maximum supported board size */
10706            }
10707       } else snprintf(b, MSG_SIZ,"%s", variant);
10708       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10709       p = StrStr(list, b);
10710       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10711       if(p == NULL) {
10712           // occurs not at all in list, or only as sub-string
10713           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10714           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10715               int l = strlen(variantError);
10716               char *q;
10717               while(p != list && p[-1] != ',') p--;
10718               q = strchr(p, ',');
10719               if(q) *q = NULLCHAR;
10720               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10721               if(q) *q= ',';
10722           }
10723           return NULL;
10724       }
10725       return b;
10726 }
10727
10728 void
10729 InitChessProgram (ChessProgramState *cps, int setup)
10730 /* setup needed to setup FRC opening position */
10731 {
10732     char buf[MSG_SIZ], *b;
10733     if (appData.noChessProgram) return;
10734     hintRequested = FALSE;
10735     bookRequested = FALSE;
10736
10737     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10738     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10739     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10740     if(cps->memSize) { /* [HGM] memory */
10741       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10742         SendToProgram(buf, cps);
10743     }
10744     SendEgtPath(cps); /* [HGM] EGT */
10745     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10746       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10747         SendToProgram(buf, cps);
10748     }
10749
10750     setboardSpoiledMachineBlack = FALSE;
10751     SendToProgram(cps->initString, cps);
10752     if (gameInfo.variant != VariantNormal &&
10753         gameInfo.variant != VariantLoadable
10754         /* [HGM] also send variant if board size non-standard */
10755         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10756
10757       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10758                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10759       if (b == NULL) {
10760         VariantClass v;
10761         char c, *q = cps->variants, *p = strchr(q, ',');
10762         if(p) *p = NULLCHAR;
10763         v = StringToVariant(q);
10764         DisplayError(variantError, 0);
10765         if(v != VariantUnknown && cps == &first) {
10766             int w, h, s;
10767             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10768                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10769             ASSIGN(appData.variant, q);
10770             Reset(TRUE, FALSE);
10771         }
10772         if(p) *p = ',';
10773         return;
10774       }
10775
10776       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10777       SendToProgram(buf, cps);
10778     }
10779     currentlyInitializedVariant = gameInfo.variant;
10780
10781     /* [HGM] send opening position in FRC to first engine */
10782     if(setup) {
10783           SendToProgram("force\n", cps);
10784           SendBoard(cps, 0);
10785           /* engine is now in force mode! Set flag to wake it up after first move. */
10786           setboardSpoiledMachineBlack = 1;
10787     }
10788
10789     if (cps->sendICS) {
10790       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10791       SendToProgram(buf, cps);
10792     }
10793     cps->maybeThinking = FALSE;
10794     cps->offeredDraw = 0;
10795     if (!appData.icsActive) {
10796         SendTimeControl(cps, movesPerSession, timeControl,
10797                         timeIncrement, appData.searchDepth,
10798                         searchTime);
10799     }
10800     if (appData.showThinking
10801         // [HGM] thinking: four options require thinking output to be sent
10802         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10803                                 ) {
10804         SendToProgram("post\n", cps);
10805     }
10806     SendToProgram("hard\n", cps);
10807     if (!appData.ponderNextMove) {
10808         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10809            it without being sure what state we are in first.  "hard"
10810            is not a toggle, so that one is OK.
10811          */
10812         SendToProgram("easy\n", cps);
10813     }
10814     if (cps->usePing) {
10815       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10816       SendToProgram(buf, cps);
10817     }
10818     cps->initDone = TRUE;
10819     ClearEngineOutputPane(cps == &second);
10820 }
10821
10822
10823 void
10824 ResendOptions (ChessProgramState *cps)
10825 { // send the stored value of the options
10826   int i;
10827   char buf[MSG_SIZ];
10828   Option *opt = cps->option;
10829   for(i=0; i<cps->nrOptions; i++, opt++) {
10830       switch(opt->type) {
10831         case Spin:
10832         case Slider:
10833         case CheckBox:
10834             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10835           break;
10836         case ComboBox:
10837           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10838           break;
10839         default:
10840             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10841           break;
10842         case Button:
10843         case SaveButton:
10844           continue;
10845       }
10846       SendToProgram(buf, cps);
10847   }
10848 }
10849
10850 void
10851 StartChessProgram (ChessProgramState *cps)
10852 {
10853     char buf[MSG_SIZ];
10854     int err;
10855
10856     if (appData.noChessProgram) return;
10857     cps->initDone = FALSE;
10858
10859     if (strcmp(cps->host, "localhost") == 0) {
10860         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10861     } else if (*appData.remoteShell == NULLCHAR) {
10862         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10863     } else {
10864         if (*appData.remoteUser == NULLCHAR) {
10865           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10866                     cps->program);
10867         } else {
10868           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10869                     cps->host, appData.remoteUser, cps->program);
10870         }
10871         err = StartChildProcess(buf, "", &cps->pr);
10872     }
10873
10874     if (err != 0) {
10875       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10876         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10877         if(cps != &first) return;
10878         appData.noChessProgram = TRUE;
10879         ThawUI();
10880         SetNCPMode();
10881 //      DisplayFatalError(buf, err, 1);
10882 //      cps->pr = NoProc;
10883 //      cps->isr = NULL;
10884         return;
10885     }
10886
10887     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10888     if (cps->protocolVersion > 1) {
10889       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10890       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10891         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10892         cps->comboCnt = 0;  //                and values of combo boxes
10893       }
10894       SendToProgram(buf, cps);
10895       if(cps->reload) ResendOptions(cps);
10896     } else {
10897       SendToProgram("xboard\n", cps);
10898     }
10899 }
10900
10901 void
10902 TwoMachinesEventIfReady P((void))
10903 {
10904   static int curMess = 0;
10905   if (first.lastPing != first.lastPong) {
10906     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10907     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10908     return;
10909   }
10910   if (second.lastPing != second.lastPong) {
10911     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10912     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10913     return;
10914   }
10915   DisplayMessage("", ""); curMess = 0;
10916   TwoMachinesEvent();
10917 }
10918
10919 char *
10920 MakeName (char *template)
10921 {
10922     time_t clock;
10923     struct tm *tm;
10924     static char buf[MSG_SIZ];
10925     char *p = buf;
10926     int i;
10927
10928     clock = time((time_t *)NULL);
10929     tm = localtime(&clock);
10930
10931     while(*p++ = *template++) if(p[-1] == '%') {
10932         switch(*template++) {
10933           case 0:   *p = 0; return buf;
10934           case 'Y': i = tm->tm_year+1900; break;
10935           case 'y': i = tm->tm_year-100; break;
10936           case 'M': i = tm->tm_mon+1; break;
10937           case 'd': i = tm->tm_mday; break;
10938           case 'h': i = tm->tm_hour; break;
10939           case 'm': i = tm->tm_min; break;
10940           case 's': i = tm->tm_sec; break;
10941           default:  i = 0;
10942         }
10943         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10944     }
10945     return buf;
10946 }
10947
10948 int
10949 CountPlayers (char *p)
10950 {
10951     int n = 0;
10952     while(p = strchr(p, '\n')) p++, n++; // count participants
10953     return n;
10954 }
10955
10956 FILE *
10957 WriteTourneyFile (char *results, FILE *f)
10958 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10959     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10960     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10961         // create a file with tournament description
10962         fprintf(f, "-participants {%s}\n", appData.participants);
10963         fprintf(f, "-seedBase %d\n", appData.seedBase);
10964         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10965         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10966         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10967         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10968         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10969         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10970         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10971         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10972         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10973         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10974         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10975         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10976         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10977         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10978         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10979         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10980         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10981         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10982         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10983         fprintf(f, "-smpCores %d\n", appData.smpCores);
10984         if(searchTime > 0)
10985                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10986         else {
10987                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10988                 fprintf(f, "-tc %s\n", appData.timeControl);
10989                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10990         }
10991         fprintf(f, "-results \"%s\"\n", results);
10992     }
10993     return f;
10994 }
10995
10996 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10997
10998 void
10999 Substitute (char *participants, int expunge)
11000 {
11001     int i, changed, changes=0, nPlayers=0;
11002     char *p, *q, *r, buf[MSG_SIZ];
11003     if(participants == NULL) return;
11004     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11005     r = p = participants; q = appData.participants;
11006     while(*p && *p == *q) {
11007         if(*p == '\n') r = p+1, nPlayers++;
11008         p++; q++;
11009     }
11010     if(*p) { // difference
11011         while(*p && *p++ != '\n');
11012         while(*q && *q++ != '\n');
11013       changed = nPlayers;
11014         changes = 1 + (strcmp(p, q) != 0);
11015     }
11016     if(changes == 1) { // a single engine mnemonic was changed
11017         q = r; while(*q) nPlayers += (*q++ == '\n');
11018         p = buf; while(*r && (*p = *r++) != '\n') p++;
11019         *p = NULLCHAR;
11020         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11021         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11022         if(mnemonic[i]) { // The substitute is valid
11023             FILE *f;
11024             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11025                 flock(fileno(f), LOCK_EX);
11026                 ParseArgsFromFile(f);
11027                 fseek(f, 0, SEEK_SET);
11028                 FREE(appData.participants); appData.participants = participants;
11029                 if(expunge) { // erase results of replaced engine
11030                     int len = strlen(appData.results), w, b, dummy;
11031                     for(i=0; i<len; i++) {
11032                         Pairing(i, nPlayers, &w, &b, &dummy);
11033                         if((w == changed || b == changed) && appData.results[i] == '*') {
11034                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11035                             fclose(f);
11036                             return;
11037                         }
11038                     }
11039                     for(i=0; i<len; i++) {
11040                         Pairing(i, nPlayers, &w, &b, &dummy);
11041                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11042                     }
11043                 }
11044                 WriteTourneyFile(appData.results, f);
11045                 fclose(f); // release lock
11046                 return;
11047             }
11048         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11049     }
11050     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11051     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11052     free(participants);
11053     return;
11054 }
11055
11056 int
11057 CheckPlayers (char *participants)
11058 {
11059         int i;
11060         char buf[MSG_SIZ], *p;
11061         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11062         while(p = strchr(participants, '\n')) {
11063             *p = NULLCHAR;
11064             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11065             if(!mnemonic[i]) {
11066                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11067                 *p = '\n';
11068                 DisplayError(buf, 0);
11069                 return 1;
11070             }
11071             *p = '\n';
11072             participants = p + 1;
11073         }
11074         return 0;
11075 }
11076
11077 int
11078 CreateTourney (char *name)
11079 {
11080         FILE *f;
11081         if(matchMode && strcmp(name, appData.tourneyFile)) {
11082              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11083         }
11084         if(name[0] == NULLCHAR) {
11085             if(appData.participants[0])
11086                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11087             return 0;
11088         }
11089         f = fopen(name, "r");
11090         if(f) { // file exists
11091             ASSIGN(appData.tourneyFile, name);
11092             ParseArgsFromFile(f); // parse it
11093         } else {
11094             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11095             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11096                 DisplayError(_("Not enough participants"), 0);
11097                 return 0;
11098             }
11099             if(CheckPlayers(appData.participants)) return 0;
11100             ASSIGN(appData.tourneyFile, name);
11101             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11102             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11103         }
11104         fclose(f);
11105         appData.noChessProgram = FALSE;
11106         appData.clockMode = TRUE;
11107         SetGNUMode();
11108         return 1;
11109 }
11110
11111 int
11112 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11113 {
11114     char buf[MSG_SIZ], *p, *q;
11115     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11116     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11117     skip = !all && group[0]; // if group requested, we start in skip mode
11118     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11119         p = names; q = buf; header = 0;
11120         while(*p && *p != '\n') *q++ = *p++;
11121         *q = 0;
11122         if(*p == '\n') p++;
11123         if(buf[0] == '#') {
11124             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11125             depth++; // we must be entering a new group
11126             if(all) continue; // suppress printing group headers when complete list requested
11127             header = 1;
11128             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11129         }
11130         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11131         if(engineList[i]) free(engineList[i]);
11132         engineList[i] = strdup(buf);
11133         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11134         if(engineMnemonic[i]) free(engineMnemonic[i]);
11135         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11136             strcat(buf, " (");
11137             sscanf(q + 8, "%s", buf + strlen(buf));
11138             strcat(buf, ")");
11139         }
11140         engineMnemonic[i] = strdup(buf);
11141         i++;
11142     }
11143     engineList[i] = engineMnemonic[i] = NULL;
11144     return i;
11145 }
11146
11147 // following implemented as macro to avoid type limitations
11148 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11149
11150 void
11151 SwapEngines (int n)
11152 {   // swap settings for first engine and other engine (so far only some selected options)
11153     int h;
11154     char *p;
11155     if(n == 0) return;
11156     SWAP(directory, p)
11157     SWAP(chessProgram, p)
11158     SWAP(isUCI, h)
11159     SWAP(hasOwnBookUCI, h)
11160     SWAP(protocolVersion, h)
11161     SWAP(reuse, h)
11162     SWAP(scoreIsAbsolute, h)
11163     SWAP(timeOdds, h)
11164     SWAP(logo, p)
11165     SWAP(pgnName, p)
11166     SWAP(pvSAN, h)
11167     SWAP(engOptions, p)
11168     SWAP(engInitString, p)
11169     SWAP(computerString, p)
11170     SWAP(features, p)
11171     SWAP(fenOverride, p)
11172     SWAP(NPS, h)
11173     SWAP(accumulateTC, h)
11174     SWAP(drawDepth, h)
11175     SWAP(host, p)
11176     SWAP(pseudo, h)
11177 }
11178
11179 int
11180 GetEngineLine (char *s, int n)
11181 {
11182     int i;
11183     char buf[MSG_SIZ];
11184     extern char *icsNames;
11185     if(!s || !*s) return 0;
11186     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11187     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11188     if(!mnemonic[i]) return 0;
11189     if(n == 11) return 1; // just testing if there was a match
11190     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11191     if(n == 1) SwapEngines(n);
11192     ParseArgsFromString(buf);
11193     if(n == 1) SwapEngines(n);
11194     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11195         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11196         ParseArgsFromString(buf);
11197     }
11198     return 1;
11199 }
11200
11201 int
11202 SetPlayer (int player, char *p)
11203 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11204     int i;
11205     char buf[MSG_SIZ], *engineName;
11206     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11207     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11208     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11209     if(mnemonic[i]) {
11210         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11211         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11212         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11213         ParseArgsFromString(buf);
11214     } else { // no engine with this nickname is installed!
11215         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11216         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11217         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11218         ModeHighlight();
11219         DisplayError(buf, 0);
11220         return 0;
11221     }
11222     free(engineName);
11223     return i;
11224 }
11225
11226 char *recentEngines;
11227
11228 void
11229 RecentEngineEvent (int nr)
11230 {
11231     int n;
11232 //    SwapEngines(1); // bump first to second
11233 //    ReplaceEngine(&second, 1); // and load it there
11234     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11235     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11236     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11237         ReplaceEngine(&first, 0);
11238         FloatToFront(&appData.recentEngineList, command[n]);
11239     }
11240 }
11241
11242 int
11243 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11244 {   // determine players from game number
11245     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11246
11247     if(appData.tourneyType == 0) {
11248         roundsPerCycle = (nPlayers - 1) | 1;
11249         pairingsPerRound = nPlayers / 2;
11250     } else if(appData.tourneyType > 0) {
11251         roundsPerCycle = nPlayers - appData.tourneyType;
11252         pairingsPerRound = appData.tourneyType;
11253     }
11254     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11255     gamesPerCycle = gamesPerRound * roundsPerCycle;
11256     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11257     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11258     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11259     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11260     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11261     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11262
11263     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11264     if(appData.roundSync) *syncInterval = gamesPerRound;
11265
11266     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11267
11268     if(appData.tourneyType == 0) {
11269         if(curPairing == (nPlayers-1)/2 ) {
11270             *whitePlayer = curRound;
11271             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11272         } else {
11273             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11274             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11275             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11276             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11277         }
11278     } else if(appData.tourneyType > 1) {
11279         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11280         *whitePlayer = curRound + appData.tourneyType;
11281     } else if(appData.tourneyType > 0) {
11282         *whitePlayer = curPairing;
11283         *blackPlayer = curRound + appData.tourneyType;
11284     }
11285
11286     // take care of white/black alternation per round.
11287     // For cycles and games this is already taken care of by default, derived from matchGame!
11288     return curRound & 1;
11289 }
11290
11291 int
11292 NextTourneyGame (int nr, int *swapColors)
11293 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11294     char *p, *q;
11295     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11296     FILE *tf;
11297     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11298     tf = fopen(appData.tourneyFile, "r");
11299     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11300     ParseArgsFromFile(tf); fclose(tf);
11301     InitTimeControls(); // TC might be altered from tourney file
11302
11303     nPlayers = CountPlayers(appData.participants); // count participants
11304     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11305     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11306
11307     if(syncInterval) {
11308         p = q = appData.results;
11309         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11310         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11311             DisplayMessage(_("Waiting for other game(s)"),"");
11312             waitingForGame = TRUE;
11313             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11314             return 0;
11315         }
11316         waitingForGame = FALSE;
11317     }
11318
11319     if(appData.tourneyType < 0) {
11320         if(nr>=0 && !pairingReceived) {
11321             char buf[1<<16];
11322             if(pairing.pr == NoProc) {
11323                 if(!appData.pairingEngine[0]) {
11324                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11325                     return 0;
11326                 }
11327                 StartChessProgram(&pairing); // starts the pairing engine
11328             }
11329             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11330             SendToProgram(buf, &pairing);
11331             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11332             SendToProgram(buf, &pairing);
11333             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11334         }
11335         pairingReceived = 0;                              // ... so we continue here
11336         *swapColors = 0;
11337         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11338         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11339         matchGame = 1; roundNr = nr / syncInterval + 1;
11340     }
11341
11342     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11343
11344     // redefine engines, engine dir, etc.
11345     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11346     if(first.pr == NoProc) {
11347       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11348       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11349     }
11350     if(second.pr == NoProc) {
11351       SwapEngines(1);
11352       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11353       SwapEngines(1);         // and make that valid for second engine by swapping
11354       InitEngine(&second, 1);
11355     }
11356     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11357     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11358     return OK;
11359 }
11360
11361 void
11362 NextMatchGame ()
11363 {   // performs game initialization that does not invoke engines, and then tries to start the game
11364     int res, firstWhite, swapColors = 0;
11365     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11366     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
11367         char buf[MSG_SIZ];
11368         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11369         if(strcmp(buf, currentDebugFile)) { // name has changed
11370             FILE *f = fopen(buf, "w");
11371             if(f) { // if opening the new file failed, just keep using the old one
11372                 ASSIGN(currentDebugFile, buf);
11373                 fclose(debugFP);
11374                 debugFP = f;
11375             }
11376             if(appData.serverFileName) {
11377                 if(serverFP) fclose(serverFP);
11378                 serverFP = fopen(appData.serverFileName, "w");
11379                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11380                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11381             }
11382         }
11383     }
11384     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11385     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11386     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11387     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11388     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11389     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11390     Reset(FALSE, first.pr != NoProc);
11391     res = LoadGameOrPosition(matchGame); // setup game
11392     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11393     if(!res) return; // abort when bad game/pos file
11394     TwoMachinesEvent();
11395 }
11396
11397 void
11398 UserAdjudicationEvent (int result)
11399 {
11400     ChessMove gameResult = GameIsDrawn;
11401
11402     if( result > 0 ) {
11403         gameResult = WhiteWins;
11404     }
11405     else if( result < 0 ) {
11406         gameResult = BlackWins;
11407     }
11408
11409     if( gameMode == TwoMachinesPlay ) {
11410         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11411     }
11412 }
11413
11414
11415 // [HGM] save: calculate checksum of game to make games easily identifiable
11416 int
11417 StringCheckSum (char *s)
11418 {
11419         int i = 0;
11420         if(s==NULL) return 0;
11421         while(*s) i = i*259 + *s++;
11422         return i;
11423 }
11424
11425 int
11426 GameCheckSum ()
11427 {
11428         int i, sum=0;
11429         for(i=backwardMostMove; i<forwardMostMove; i++) {
11430                 sum += pvInfoList[i].depth;
11431                 sum += StringCheckSum(parseList[i]);
11432                 sum += StringCheckSum(commentList[i]);
11433                 sum *= 261;
11434         }
11435         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11436         return sum + StringCheckSum(commentList[i]);
11437 } // end of save patch
11438
11439 void
11440 GameEnds (ChessMove result, char *resultDetails, int whosays)
11441 {
11442     GameMode nextGameMode;
11443     int isIcsGame;
11444     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11445
11446     if(endingGame) return; /* [HGM] crash: forbid recursion */
11447     endingGame = 1;
11448     if(twoBoards) { // [HGM] dual: switch back to one board
11449         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11450         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11451     }
11452     if (appData.debugMode) {
11453       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11454               result, resultDetails ? resultDetails : "(null)", whosays);
11455     }
11456
11457     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11458
11459     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11460
11461     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11462         /* If we are playing on ICS, the server decides when the
11463            game is over, but the engine can offer to draw, claim
11464            a draw, or resign.
11465          */
11466 #if ZIPPY
11467         if (appData.zippyPlay && first.initDone) {
11468             if (result == GameIsDrawn) {
11469                 /* In case draw still needs to be claimed */
11470                 SendToICS(ics_prefix);
11471                 SendToICS("draw\n");
11472             } else if (StrCaseStr(resultDetails, "resign")) {
11473                 SendToICS(ics_prefix);
11474                 SendToICS("resign\n");
11475             }
11476         }
11477 #endif
11478         endingGame = 0; /* [HGM] crash */
11479         return;
11480     }
11481
11482     /* If we're loading the game from a file, stop */
11483     if (whosays == GE_FILE) {
11484       (void) StopLoadGameTimer();
11485       gameFileFP = NULL;
11486     }
11487
11488     /* Cancel draw offers */
11489     first.offeredDraw = second.offeredDraw = 0;
11490
11491     /* If this is an ICS game, only ICS can really say it's done;
11492        if not, anyone can. */
11493     isIcsGame = (gameMode == IcsPlayingWhite ||
11494                  gameMode == IcsPlayingBlack ||
11495                  gameMode == IcsObserving    ||
11496                  gameMode == IcsExamining);
11497
11498     if (!isIcsGame || whosays == GE_ICS) {
11499         /* OK -- not an ICS game, or ICS said it was done */
11500         StopClocks();
11501         if (!isIcsGame && !appData.noChessProgram)
11502           SetUserThinkingEnables();
11503
11504         /* [HGM] if a machine claims the game end we verify this claim */
11505         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11506             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11507                 char claimer;
11508                 ChessMove trueResult = (ChessMove) -1;
11509
11510                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11511                                             first.twoMachinesColor[0] :
11512                                             second.twoMachinesColor[0] ;
11513
11514                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11515                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11516                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11517                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11518                 } else
11519                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11520                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11521                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11522                 } else
11523                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11524                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11525                 }
11526
11527                 // now verify win claims, but not in drop games, as we don't understand those yet
11528                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11529                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11530                     (result == WhiteWins && claimer == 'w' ||
11531                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11532                       if (appData.debugMode) {
11533                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11534                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11535                       }
11536                       if(result != trueResult) {
11537                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11538                               result = claimer == 'w' ? BlackWins : WhiteWins;
11539                               resultDetails = buf;
11540                       }
11541                 } else
11542                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11543                     && (forwardMostMove <= backwardMostMove ||
11544                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11545                         (claimer=='b')==(forwardMostMove&1))
11546                                                                                   ) {
11547                       /* [HGM] verify: draws that were not flagged are false claims */
11548                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11549                       result = claimer == 'w' ? BlackWins : WhiteWins;
11550                       resultDetails = buf;
11551                 }
11552                 /* (Claiming a loss is accepted no questions asked!) */
11553             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11554                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11555                 result = GameUnfinished;
11556                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11557             }
11558             /* [HGM] bare: don't allow bare King to win */
11559             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11560                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11561                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11562                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11563                && result != GameIsDrawn)
11564             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11565                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11566                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11567                         if(p >= 0 && p <= (int)WhiteKing) k++;
11568                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11569                 }
11570                 if (appData.debugMode) {
11571                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11572                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11573                 }
11574                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11575                         result = GameIsDrawn;
11576                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11577                         resultDetails = buf;
11578                 }
11579             }
11580         }
11581
11582
11583         if(serverMoves != NULL && !loadFlag) { char c = '=';
11584             if(result==WhiteWins) c = '+';
11585             if(result==BlackWins) c = '-';
11586             if(resultDetails != NULL)
11587                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11588         }
11589         if (resultDetails != NULL) {
11590             gameInfo.result = result;
11591             gameInfo.resultDetails = StrSave(resultDetails);
11592
11593             /* display last move only if game was not loaded from file */
11594             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11595                 DisplayMove(currentMove - 1);
11596
11597             if (forwardMostMove != 0) {
11598                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11599                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11600                                                                 ) {
11601                     if (*appData.saveGameFile != NULLCHAR) {
11602                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11603                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11604                         else
11605                         SaveGameToFile(appData.saveGameFile, TRUE);
11606                     } else if (appData.autoSaveGames) {
11607                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11608                     }
11609                     if (*appData.savePositionFile != NULLCHAR) {
11610                         SavePositionToFile(appData.savePositionFile);
11611                     }
11612                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11613                 }
11614             }
11615
11616             /* Tell program how game ended in case it is learning */
11617             /* [HGM] Moved this to after saving the PGN, just in case */
11618             /* engine died and we got here through time loss. In that */
11619             /* case we will get a fatal error writing the pipe, which */
11620             /* would otherwise lose us the PGN.                       */
11621             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11622             /* output during GameEnds should never be fatal anymore   */
11623             if (gameMode == MachinePlaysWhite ||
11624                 gameMode == MachinePlaysBlack ||
11625                 gameMode == TwoMachinesPlay ||
11626                 gameMode == IcsPlayingWhite ||
11627                 gameMode == IcsPlayingBlack ||
11628                 gameMode == BeginningOfGame) {
11629                 char buf[MSG_SIZ];
11630                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11631                         resultDetails);
11632                 if (first.pr != NoProc) {
11633                     SendToProgram(buf, &first);
11634                 }
11635                 if (second.pr != NoProc &&
11636                     gameMode == TwoMachinesPlay) {
11637                     SendToProgram(buf, &second);
11638                 }
11639             }
11640         }
11641
11642         if (appData.icsActive) {
11643             if (appData.quietPlay &&
11644                 (gameMode == IcsPlayingWhite ||
11645                  gameMode == IcsPlayingBlack)) {
11646                 SendToICS(ics_prefix);
11647                 SendToICS("set shout 1\n");
11648             }
11649             nextGameMode = IcsIdle;
11650             ics_user_moved = FALSE;
11651             /* clean up premove.  It's ugly when the game has ended and the
11652              * premove highlights are still on the board.
11653              */
11654             if (gotPremove) {
11655               gotPremove = FALSE;
11656               ClearPremoveHighlights();
11657               DrawPosition(FALSE, boards[currentMove]);
11658             }
11659             if (whosays == GE_ICS) {
11660                 switch (result) {
11661                 case WhiteWins:
11662                     if (gameMode == IcsPlayingWhite)
11663                         PlayIcsWinSound();
11664                     else if(gameMode == IcsPlayingBlack)
11665                         PlayIcsLossSound();
11666                     break;
11667                 case BlackWins:
11668                     if (gameMode == IcsPlayingBlack)
11669                         PlayIcsWinSound();
11670                     else if(gameMode == IcsPlayingWhite)
11671                         PlayIcsLossSound();
11672                     break;
11673                 case GameIsDrawn:
11674                     PlayIcsDrawSound();
11675                     break;
11676                 default:
11677                     PlayIcsUnfinishedSound();
11678                 }
11679             }
11680             if(appData.quitNext) { ExitEvent(0); return; }
11681         } else if (gameMode == EditGame ||
11682                    gameMode == PlayFromGameFile ||
11683                    gameMode == AnalyzeMode ||
11684                    gameMode == AnalyzeFile) {
11685             nextGameMode = gameMode;
11686         } else {
11687             nextGameMode = EndOfGame;
11688         }
11689         pausing = FALSE;
11690         ModeHighlight();
11691     } else {
11692         nextGameMode = gameMode;
11693     }
11694
11695     if (appData.noChessProgram) {
11696         gameMode = nextGameMode;
11697         ModeHighlight();
11698         endingGame = 0; /* [HGM] crash */
11699         return;
11700     }
11701
11702     if (first.reuse) {
11703         /* Put first chess program into idle state */
11704         if (first.pr != NoProc &&
11705             (gameMode == MachinePlaysWhite ||
11706              gameMode == MachinePlaysBlack ||
11707              gameMode == TwoMachinesPlay ||
11708              gameMode == IcsPlayingWhite ||
11709              gameMode == IcsPlayingBlack ||
11710              gameMode == BeginningOfGame)) {
11711             SendToProgram("force\n", &first);
11712             if (first.usePing) {
11713               char buf[MSG_SIZ];
11714               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11715               SendToProgram(buf, &first);
11716             }
11717         }
11718     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11719         /* Kill off first chess program */
11720         if (first.isr != NULL)
11721           RemoveInputSource(first.isr);
11722         first.isr = NULL;
11723
11724         if (first.pr != NoProc) {
11725             ExitAnalyzeMode();
11726             DoSleep( appData.delayBeforeQuit );
11727             SendToProgram("quit\n", &first);
11728             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11729             first.reload = TRUE;
11730         }
11731         first.pr = NoProc;
11732     }
11733     if (second.reuse) {
11734         /* Put second chess program into idle state */
11735         if (second.pr != NoProc &&
11736             gameMode == TwoMachinesPlay) {
11737             SendToProgram("force\n", &second);
11738             if (second.usePing) {
11739               char buf[MSG_SIZ];
11740               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11741               SendToProgram(buf, &second);
11742             }
11743         }
11744     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11745         /* Kill off second chess program */
11746         if (second.isr != NULL)
11747           RemoveInputSource(second.isr);
11748         second.isr = NULL;
11749
11750         if (second.pr != NoProc) {
11751             DoSleep( appData.delayBeforeQuit );
11752             SendToProgram("quit\n", &second);
11753             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11754             second.reload = TRUE;
11755         }
11756         second.pr = NoProc;
11757     }
11758
11759     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11760         char resChar = '=';
11761         switch (result) {
11762         case WhiteWins:
11763           resChar = '+';
11764           if (first.twoMachinesColor[0] == 'w') {
11765             first.matchWins++;
11766           } else {
11767             second.matchWins++;
11768           }
11769           break;
11770         case BlackWins:
11771           resChar = '-';
11772           if (first.twoMachinesColor[0] == 'b') {
11773             first.matchWins++;
11774           } else {
11775             second.matchWins++;
11776           }
11777           break;
11778         case GameUnfinished:
11779           resChar = ' ';
11780         default:
11781           break;
11782         }
11783
11784         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11785         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11786             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11787             ReserveGame(nextGame, resChar); // sets nextGame
11788             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11789             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11790         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11791
11792         if (nextGame <= appData.matchGames && !abortMatch) {
11793             gameMode = nextGameMode;
11794             matchGame = nextGame; // this will be overruled in tourney mode!
11795             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11796             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11797             endingGame = 0; /* [HGM] crash */
11798             return;
11799         } else {
11800             gameMode = nextGameMode;
11801             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11802                      first.tidy, second.tidy,
11803                      first.matchWins, second.matchWins,
11804                      appData.matchGames - (first.matchWins + second.matchWins));
11805             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11806             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11807             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11808             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11809                 first.twoMachinesColor = "black\n";
11810                 second.twoMachinesColor = "white\n";
11811             } else {
11812                 first.twoMachinesColor = "white\n";
11813                 second.twoMachinesColor = "black\n";
11814             }
11815         }
11816     }
11817     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11818         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11819       ExitAnalyzeMode();
11820     gameMode = nextGameMode;
11821     ModeHighlight();
11822     endingGame = 0;  /* [HGM] crash */
11823     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11824         if(matchMode == TRUE) { // match through command line: exit with or without popup
11825             if(ranking) {
11826                 ToNrEvent(forwardMostMove);
11827                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11828                 else ExitEvent(0);
11829             } else DisplayFatalError(buf, 0, 0);
11830         } else { // match through menu; just stop, with or without popup
11831             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11832             ModeHighlight();
11833             if(ranking){
11834                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11835             } else DisplayNote(buf);
11836       }
11837       if(ranking) free(ranking);
11838     }
11839 }
11840
11841 /* Assumes program was just initialized (initString sent).
11842    Leaves program in force mode. */
11843 void
11844 FeedMovesToProgram (ChessProgramState *cps, int upto)
11845 {
11846     int i;
11847
11848     if (appData.debugMode)
11849       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11850               startedFromSetupPosition ? "position and " : "",
11851               backwardMostMove, upto, cps->which);
11852     if(currentlyInitializedVariant != gameInfo.variant) {
11853       char buf[MSG_SIZ];
11854         // [HGM] variantswitch: make engine aware of new variant
11855         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11856                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11857                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11858         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11859         SendToProgram(buf, cps);
11860         currentlyInitializedVariant = gameInfo.variant;
11861     }
11862     SendToProgram("force\n", cps);
11863     if (startedFromSetupPosition) {
11864         SendBoard(cps, backwardMostMove);
11865     if (appData.debugMode) {
11866         fprintf(debugFP, "feedMoves\n");
11867     }
11868     }
11869     for (i = backwardMostMove; i < upto; i++) {
11870         SendMoveToProgram(i, cps);
11871     }
11872 }
11873
11874
11875 int
11876 ResurrectChessProgram ()
11877 {
11878      /* The chess program may have exited.
11879         If so, restart it and feed it all the moves made so far. */
11880     static int doInit = 0;
11881
11882     if (appData.noChessProgram) return 1;
11883
11884     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11885         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11886         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11887         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11888     } else {
11889         if (first.pr != NoProc) return 1;
11890         StartChessProgram(&first);
11891     }
11892     InitChessProgram(&first, FALSE);
11893     FeedMovesToProgram(&first, currentMove);
11894
11895     if (!first.sendTime) {
11896         /* can't tell gnuchess what its clock should read,
11897            so we bow to its notion. */
11898         ResetClocks();
11899         timeRemaining[0][currentMove] = whiteTimeRemaining;
11900         timeRemaining[1][currentMove] = blackTimeRemaining;
11901     }
11902
11903     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11904                 appData.icsEngineAnalyze) && first.analysisSupport) {
11905       SendToProgram("analyze\n", &first);
11906       first.analyzing = TRUE;
11907     }
11908     return 1;
11909 }
11910
11911 /*
11912  * Button procedures
11913  */
11914 void
11915 Reset (int redraw, int init)
11916 {
11917     int i;
11918
11919     if (appData.debugMode) {
11920         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11921                 redraw, init, gameMode);
11922     }
11923     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11924     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11925     CleanupTail(); // [HGM] vari: delete any stored variations
11926     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11927     pausing = pauseExamInvalid = FALSE;
11928     startedFromSetupPosition = blackPlaysFirst = FALSE;
11929     firstMove = TRUE;
11930     whiteFlag = blackFlag = FALSE;
11931     userOfferedDraw = FALSE;
11932     hintRequested = bookRequested = FALSE;
11933     first.maybeThinking = FALSE;
11934     second.maybeThinking = FALSE;
11935     first.bookSuspend = FALSE; // [HGM] book
11936     second.bookSuspend = FALSE;
11937     thinkOutput[0] = NULLCHAR;
11938     lastHint[0] = NULLCHAR;
11939     ClearGameInfo(&gameInfo);
11940     gameInfo.variant = StringToVariant(appData.variant);
11941     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11942     ics_user_moved = ics_clock_paused = FALSE;
11943     ics_getting_history = H_FALSE;
11944     ics_gamenum = -1;
11945     white_holding[0] = black_holding[0] = NULLCHAR;
11946     ClearProgramStats();
11947     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11948
11949     ResetFrontEnd();
11950     ClearHighlights();
11951     flipView = appData.flipView;
11952     ClearPremoveHighlights();
11953     gotPremove = FALSE;
11954     alarmSounded = FALSE;
11955     killX = killY = -1; // [HGM] lion
11956
11957     GameEnds(EndOfFile, NULL, GE_PLAYER);
11958     if(appData.serverMovesName != NULL) {
11959         /* [HGM] prepare to make moves file for broadcasting */
11960         clock_t t = clock();
11961         if(serverMoves != NULL) fclose(serverMoves);
11962         serverMoves = fopen(appData.serverMovesName, "r");
11963         if(serverMoves != NULL) {
11964             fclose(serverMoves);
11965             /* delay 15 sec before overwriting, so all clients can see end */
11966             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11967         }
11968         serverMoves = fopen(appData.serverMovesName, "w");
11969     }
11970
11971     ExitAnalyzeMode();
11972     gameMode = BeginningOfGame;
11973     ModeHighlight();
11974     if(appData.icsActive) gameInfo.variant = VariantNormal;
11975     currentMove = forwardMostMove = backwardMostMove = 0;
11976     MarkTargetSquares(1);
11977     InitPosition(redraw);
11978     for (i = 0; i < MAX_MOVES; i++) {
11979         if (commentList[i] != NULL) {
11980             free(commentList[i]);
11981             commentList[i] = NULL;
11982         }
11983     }
11984     ResetClocks();
11985     timeRemaining[0][0] = whiteTimeRemaining;
11986     timeRemaining[1][0] = blackTimeRemaining;
11987
11988     if (first.pr == NoProc) {
11989         StartChessProgram(&first);
11990     }
11991     if (init) {
11992             InitChessProgram(&first, startedFromSetupPosition);
11993     }
11994     DisplayTitle("");
11995     DisplayMessage("", "");
11996     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11997     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11998     ClearMap();        // [HGM] exclude: invalidate map
11999 }
12000
12001 void
12002 AutoPlayGameLoop ()
12003 {
12004     for (;;) {
12005         if (!AutoPlayOneMove())
12006           return;
12007         if (matchMode || appData.timeDelay == 0)
12008           continue;
12009         if (appData.timeDelay < 0)
12010           return;
12011         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12012         break;
12013     }
12014 }
12015
12016 void
12017 AnalyzeNextGame()
12018 {
12019     ReloadGame(1); // next game
12020 }
12021
12022 int
12023 AutoPlayOneMove ()
12024 {
12025     int fromX, fromY, toX, toY;
12026
12027     if (appData.debugMode) {
12028       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12029     }
12030
12031     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12032       return FALSE;
12033
12034     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12035       pvInfoList[currentMove].depth = programStats.depth;
12036       pvInfoList[currentMove].score = programStats.score;
12037       pvInfoList[currentMove].time  = 0;
12038       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12039       else { // append analysis of final position as comment
12040         char buf[MSG_SIZ];
12041         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12042         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12043       }
12044       programStats.depth = 0;
12045     }
12046
12047     if (currentMove >= forwardMostMove) {
12048       if(gameMode == AnalyzeFile) {
12049           if(appData.loadGameIndex == -1) {
12050             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12051           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12052           } else {
12053           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12054         }
12055       }
12056 //      gameMode = EndOfGame;
12057 //      ModeHighlight();
12058
12059       /* [AS] Clear current move marker at the end of a game */
12060       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12061
12062       return FALSE;
12063     }
12064
12065     toX = moveList[currentMove][2] - AAA;
12066     toY = moveList[currentMove][3] - ONE;
12067
12068     if (moveList[currentMove][1] == '@') {
12069         if (appData.highlightLastMove) {
12070             SetHighlights(-1, -1, toX, toY);
12071         }
12072     } else {
12073         int viaX = moveList[currentMove][5] - AAA;
12074         int viaY = moveList[currentMove][6] - ONE;
12075         fromX = moveList[currentMove][0] - AAA;
12076         fromY = moveList[currentMove][1] - ONE;
12077
12078         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12079
12080         if(moveList[currentMove][4] == ';') { // multi-leg
12081             ChessSquare piece = boards[currentMove][viaY][viaX];
12082             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12083             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12084             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12085             boards[currentMove][viaY][viaX] = piece;
12086         } else
12087         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12088
12089         if (appData.highlightLastMove) {
12090             SetHighlights(fromX, fromY, toX, toY);
12091         }
12092     }
12093     DisplayMove(currentMove);
12094     SendMoveToProgram(currentMove++, &first);
12095     DisplayBothClocks();
12096     DrawPosition(FALSE, boards[currentMove]);
12097     // [HGM] PV info: always display, routine tests if empty
12098     DisplayComment(currentMove - 1, commentList[currentMove]);
12099     return TRUE;
12100 }
12101
12102
12103 int
12104 LoadGameOneMove (ChessMove readAhead)
12105 {
12106     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12107     char promoChar = NULLCHAR;
12108     ChessMove moveType;
12109     char move[MSG_SIZ];
12110     char *p, *q;
12111
12112     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12113         gameMode != AnalyzeMode && gameMode != Training) {
12114         gameFileFP = NULL;
12115         return FALSE;
12116     }
12117
12118     yyboardindex = forwardMostMove;
12119     if (readAhead != EndOfFile) {
12120       moveType = readAhead;
12121     } else {
12122       if (gameFileFP == NULL)
12123           return FALSE;
12124       moveType = (ChessMove) Myylex();
12125     }
12126
12127     done = FALSE;
12128     switch (moveType) {
12129       case Comment:
12130         if (appData.debugMode)
12131           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12132         p = yy_text;
12133
12134         /* append the comment but don't display it */
12135         AppendComment(currentMove, p, FALSE);
12136         return TRUE;
12137
12138       case WhiteCapturesEnPassant:
12139       case BlackCapturesEnPassant:
12140       case WhitePromotion:
12141       case BlackPromotion:
12142       case WhiteNonPromotion:
12143       case BlackNonPromotion:
12144       case NormalMove:
12145       case FirstLeg:
12146       case WhiteKingSideCastle:
12147       case WhiteQueenSideCastle:
12148       case BlackKingSideCastle:
12149       case BlackQueenSideCastle:
12150       case WhiteKingSideCastleWild:
12151       case WhiteQueenSideCastleWild:
12152       case BlackKingSideCastleWild:
12153       case BlackQueenSideCastleWild:
12154       /* PUSH Fabien */
12155       case WhiteHSideCastleFR:
12156       case WhiteASideCastleFR:
12157       case BlackHSideCastleFR:
12158       case BlackASideCastleFR:
12159       /* POP Fabien */
12160         if (appData.debugMode)
12161           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12162         fromX = currentMoveString[0] - AAA;
12163         fromY = currentMoveString[1] - ONE;
12164         toX = currentMoveString[2] - AAA;
12165         toY = currentMoveString[3] - ONE;
12166         promoChar = currentMoveString[4];
12167         if(promoChar == ';') promoChar = currentMoveString[7];
12168         break;
12169
12170       case WhiteDrop:
12171       case BlackDrop:
12172         if (appData.debugMode)
12173           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12174         fromX = moveType == WhiteDrop ?
12175           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12176         (int) CharToPiece(ToLower(currentMoveString[0]));
12177         fromY = DROP_RANK;
12178         toX = currentMoveString[2] - AAA;
12179         toY = currentMoveString[3] - ONE;
12180         break;
12181
12182       case WhiteWins:
12183       case BlackWins:
12184       case GameIsDrawn:
12185       case GameUnfinished:
12186         if (appData.debugMode)
12187           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12188         p = strchr(yy_text, '{');
12189         if (p == NULL) p = strchr(yy_text, '(');
12190         if (p == NULL) {
12191             p = yy_text;
12192             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12193         } else {
12194             q = strchr(p, *p == '{' ? '}' : ')');
12195             if (q != NULL) *q = NULLCHAR;
12196             p++;
12197         }
12198         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12199         GameEnds(moveType, p, GE_FILE);
12200         done = TRUE;
12201         if (cmailMsgLoaded) {
12202             ClearHighlights();
12203             flipView = WhiteOnMove(currentMove);
12204             if (moveType == GameUnfinished) flipView = !flipView;
12205             if (appData.debugMode)
12206               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12207         }
12208         break;
12209
12210       case EndOfFile:
12211         if (appData.debugMode)
12212           fprintf(debugFP, "Parser hit end of file\n");
12213         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12214           case MT_NONE:
12215           case MT_CHECK:
12216             break;
12217           case MT_CHECKMATE:
12218           case MT_STAINMATE:
12219             if (WhiteOnMove(currentMove)) {
12220                 GameEnds(BlackWins, "Black mates", GE_FILE);
12221             } else {
12222                 GameEnds(WhiteWins, "White mates", GE_FILE);
12223             }
12224             break;
12225           case MT_STALEMATE:
12226             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12227             break;
12228         }
12229         done = TRUE;
12230         break;
12231
12232       case MoveNumberOne:
12233         if (lastLoadGameStart == GNUChessGame) {
12234             /* GNUChessGames have numbers, but they aren't move numbers */
12235             if (appData.debugMode)
12236               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12237                       yy_text, (int) moveType);
12238             return LoadGameOneMove(EndOfFile); /* tail recursion */
12239         }
12240         /* else fall thru */
12241
12242       case XBoardGame:
12243       case GNUChessGame:
12244       case PGNTag:
12245         /* Reached start of next game in file */
12246         if (appData.debugMode)
12247           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12248         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12249           case MT_NONE:
12250           case MT_CHECK:
12251             break;
12252           case MT_CHECKMATE:
12253           case MT_STAINMATE:
12254             if (WhiteOnMove(currentMove)) {
12255                 GameEnds(BlackWins, "Black mates", GE_FILE);
12256             } else {
12257                 GameEnds(WhiteWins, "White mates", GE_FILE);
12258             }
12259             break;
12260           case MT_STALEMATE:
12261             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12262             break;
12263         }
12264         done = TRUE;
12265         break;
12266
12267       case PositionDiagram:     /* should not happen; ignore */
12268       case ElapsedTime:         /* ignore */
12269       case NAG:                 /* ignore */
12270         if (appData.debugMode)
12271           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12272                   yy_text, (int) moveType);
12273         return LoadGameOneMove(EndOfFile); /* tail recursion */
12274
12275       case IllegalMove:
12276         if (appData.testLegality) {
12277             if (appData.debugMode)
12278               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12279             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12280                     (forwardMostMove / 2) + 1,
12281                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12282             DisplayError(move, 0);
12283             done = TRUE;
12284         } else {
12285             if (appData.debugMode)
12286               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12287                       yy_text, currentMoveString);
12288             if(currentMoveString[1] == '@') {
12289                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12290                 fromY = DROP_RANK;
12291             } else {
12292                 fromX = currentMoveString[0] - AAA;
12293                 fromY = currentMoveString[1] - ONE;
12294             }
12295             toX = currentMoveString[2] - AAA;
12296             toY = currentMoveString[3] - ONE;
12297             promoChar = currentMoveString[4];
12298         }
12299         break;
12300
12301       case AmbiguousMove:
12302         if (appData.debugMode)
12303           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12304         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12305                 (forwardMostMove / 2) + 1,
12306                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12307         DisplayError(move, 0);
12308         done = TRUE;
12309         break;
12310
12311       default:
12312       case ImpossibleMove:
12313         if (appData.debugMode)
12314           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12315         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12316                 (forwardMostMove / 2) + 1,
12317                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12318         DisplayError(move, 0);
12319         done = TRUE;
12320         break;
12321     }
12322
12323     if (done) {
12324         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12325             DrawPosition(FALSE, boards[currentMove]);
12326             DisplayBothClocks();
12327             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12328               DisplayComment(currentMove - 1, commentList[currentMove]);
12329         }
12330         (void) StopLoadGameTimer();
12331         gameFileFP = NULL;
12332         cmailOldMove = forwardMostMove;
12333         return FALSE;
12334     } else {
12335         /* currentMoveString is set as a side-effect of yylex */
12336
12337         thinkOutput[0] = NULLCHAR;
12338         MakeMove(fromX, fromY, toX, toY, promoChar);
12339         killX = killY = -1; // [HGM] lion: used up
12340         currentMove = forwardMostMove;
12341         return TRUE;
12342     }
12343 }
12344
12345 /* Load the nth game from the given file */
12346 int
12347 LoadGameFromFile (char *filename, int n, char *title, int useList)
12348 {
12349     FILE *f;
12350     char buf[MSG_SIZ];
12351
12352     if (strcmp(filename, "-") == 0) {
12353         f = stdin;
12354         title = "stdin";
12355     } else {
12356         f = fopen(filename, "rb");
12357         if (f == NULL) {
12358           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12359             DisplayError(buf, errno);
12360             return FALSE;
12361         }
12362     }
12363     if (fseek(f, 0, 0) == -1) {
12364         /* f is not seekable; probably a pipe */
12365         useList = FALSE;
12366     }
12367     if (useList && n == 0) {
12368         int error = GameListBuild(f);
12369         if (error) {
12370             DisplayError(_("Cannot build game list"), error);
12371         } else if (!ListEmpty(&gameList) &&
12372                    ((ListGame *) gameList.tailPred)->number > 1) {
12373             GameListPopUp(f, title);
12374             return TRUE;
12375         }
12376         GameListDestroy();
12377         n = 1;
12378     }
12379     if (n == 0) n = 1;
12380     return LoadGame(f, n, title, FALSE);
12381 }
12382
12383
12384 void
12385 MakeRegisteredMove ()
12386 {
12387     int fromX, fromY, toX, toY;
12388     char promoChar;
12389     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12390         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12391           case CMAIL_MOVE:
12392           case CMAIL_DRAW:
12393             if (appData.debugMode)
12394               fprintf(debugFP, "Restoring %s for game %d\n",
12395                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12396
12397             thinkOutput[0] = NULLCHAR;
12398             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12399             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12400             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12401             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12402             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12403             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12404             MakeMove(fromX, fromY, toX, toY, promoChar);
12405             ShowMove(fromX, fromY, toX, toY);
12406
12407             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12408               case MT_NONE:
12409               case MT_CHECK:
12410                 break;
12411
12412               case MT_CHECKMATE:
12413               case MT_STAINMATE:
12414                 if (WhiteOnMove(currentMove)) {
12415                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12416                 } else {
12417                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12418                 }
12419                 break;
12420
12421               case MT_STALEMATE:
12422                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12423                 break;
12424             }
12425
12426             break;
12427
12428           case CMAIL_RESIGN:
12429             if (WhiteOnMove(currentMove)) {
12430                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12431             } else {
12432                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12433             }
12434             break;
12435
12436           case CMAIL_ACCEPT:
12437             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12438             break;
12439
12440           default:
12441             break;
12442         }
12443     }
12444
12445     return;
12446 }
12447
12448 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12449 int
12450 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12451 {
12452     int retVal;
12453
12454     if (gameNumber > nCmailGames) {
12455         DisplayError(_("No more games in this message"), 0);
12456         return FALSE;
12457     }
12458     if (f == lastLoadGameFP) {
12459         int offset = gameNumber - lastLoadGameNumber;
12460         if (offset == 0) {
12461             cmailMsg[0] = NULLCHAR;
12462             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12463                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12464                 nCmailMovesRegistered--;
12465             }
12466             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12467             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12468                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12469             }
12470         } else {
12471             if (! RegisterMove()) return FALSE;
12472         }
12473     }
12474
12475     retVal = LoadGame(f, gameNumber, title, useList);
12476
12477     /* Make move registered during previous look at this game, if any */
12478     MakeRegisteredMove();
12479
12480     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12481         commentList[currentMove]
12482           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12483         DisplayComment(currentMove - 1, commentList[currentMove]);
12484     }
12485
12486     return retVal;
12487 }
12488
12489 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12490 int
12491 ReloadGame (int offset)
12492 {
12493     int gameNumber = lastLoadGameNumber + offset;
12494     if (lastLoadGameFP == NULL) {
12495         DisplayError(_("No game has been loaded yet"), 0);
12496         return FALSE;
12497     }
12498     if (gameNumber <= 0) {
12499         DisplayError(_("Can't back up any further"), 0);
12500         return FALSE;
12501     }
12502     if (cmailMsgLoaded) {
12503         return CmailLoadGame(lastLoadGameFP, gameNumber,
12504                              lastLoadGameTitle, lastLoadGameUseList);
12505     } else {
12506         return LoadGame(lastLoadGameFP, gameNumber,
12507                         lastLoadGameTitle, lastLoadGameUseList);
12508     }
12509 }
12510
12511 int keys[EmptySquare+1];
12512
12513 int
12514 PositionMatches (Board b1, Board b2)
12515 {
12516     int r, f, sum=0;
12517     switch(appData.searchMode) {
12518         case 1: return CompareWithRights(b1, b2);
12519         case 2:
12520             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12521                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12522             }
12523             return TRUE;
12524         case 3:
12525             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12526               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12527                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12528             }
12529             return sum==0;
12530         case 4:
12531             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12532                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12533             }
12534             return sum==0;
12535     }
12536     return TRUE;
12537 }
12538
12539 #define Q_PROMO  4
12540 #define Q_EP     3
12541 #define Q_BCASTL 2
12542 #define Q_WCASTL 1
12543
12544 int pieceList[256], quickBoard[256];
12545 ChessSquare pieceType[256] = { EmptySquare };
12546 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12547 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12548 int soughtTotal, turn;
12549 Boolean epOK, flipSearch;
12550
12551 typedef struct {
12552     unsigned char piece, to;
12553 } Move;
12554
12555 #define DSIZE (250000)
12556
12557 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12558 Move *moveDatabase = initialSpace;
12559 unsigned int movePtr, dataSize = DSIZE;
12560
12561 int
12562 MakePieceList (Board board, int *counts)
12563 {
12564     int r, f, n=Q_PROMO, total=0;
12565     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12566     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12567         int sq = f + (r<<4);
12568         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12569             quickBoard[sq] = ++n;
12570             pieceList[n] = sq;
12571             pieceType[n] = board[r][f];
12572             counts[board[r][f]]++;
12573             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12574             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12575             total++;
12576         }
12577     }
12578     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12579     return total;
12580 }
12581
12582 void
12583 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12584 {
12585     int sq = fromX + (fromY<<4);
12586     int piece = quickBoard[sq], rook;
12587     quickBoard[sq] = 0;
12588     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12589     if(piece == pieceList[1] && fromY == toY) {
12590       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12591         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12592         moveDatabase[movePtr++].piece = Q_WCASTL;
12593         quickBoard[sq] = piece;
12594         piece = quickBoard[from]; quickBoard[from] = 0;
12595         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12596       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12597         quickBoard[sq] = 0; // remove Rook
12598         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12599         moveDatabase[movePtr++].piece = Q_WCASTL;
12600         quickBoard[sq] = pieceList[1]; // put King
12601         piece = rook;
12602         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12603       }
12604     } else
12605     if(piece == pieceList[2] && fromY == toY) {
12606       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12607         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12608         moveDatabase[movePtr++].piece = Q_BCASTL;
12609         quickBoard[sq] = piece;
12610         piece = quickBoard[from]; quickBoard[from] = 0;
12611         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12612       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12613         quickBoard[sq] = 0; // remove Rook
12614         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12615         moveDatabase[movePtr++].piece = Q_BCASTL;
12616         quickBoard[sq] = pieceList[2]; // put King
12617         piece = rook;
12618         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12619       }
12620     } else
12621     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12622         quickBoard[(fromY<<4)+toX] = 0;
12623         moveDatabase[movePtr].piece = Q_EP;
12624         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12625         moveDatabase[movePtr].to = sq;
12626     } else
12627     if(promoPiece != pieceType[piece]) {
12628         moveDatabase[movePtr++].piece = Q_PROMO;
12629         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12630     }
12631     moveDatabase[movePtr].piece = piece;
12632     quickBoard[sq] = piece;
12633     movePtr++;
12634 }
12635
12636 int
12637 PackGame (Board board)
12638 {
12639     Move *newSpace = NULL;
12640     moveDatabase[movePtr].piece = 0; // terminate previous game
12641     if(movePtr > dataSize) {
12642         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12643         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12644         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12645         if(newSpace) {
12646             int i;
12647             Move *p = moveDatabase, *q = newSpace;
12648             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12649             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12650             moveDatabase = newSpace;
12651         } else { // calloc failed, we must be out of memory. Too bad...
12652             dataSize = 0; // prevent calloc events for all subsequent games
12653             return 0;     // and signal this one isn't cached
12654         }
12655     }
12656     movePtr++;
12657     MakePieceList(board, counts);
12658     return movePtr;
12659 }
12660
12661 int
12662 QuickCompare (Board board, int *minCounts, int *maxCounts)
12663 {   // compare according to search mode
12664     int r, f;
12665     switch(appData.searchMode)
12666     {
12667       case 1: // exact position match
12668         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12669         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12670             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12671         }
12672         break;
12673       case 2: // can have extra material on empty squares
12674         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12675             if(board[r][f] == EmptySquare) continue;
12676             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12677         }
12678         break;
12679       case 3: // material with exact Pawn structure
12680         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12681             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12682             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12683         } // fall through to material comparison
12684       case 4: // exact material
12685         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12686         break;
12687       case 6: // material range with given imbalance
12688         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12689         // fall through to range comparison
12690       case 5: // material range
12691         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12692     }
12693     return TRUE;
12694 }
12695
12696 int
12697 QuickScan (Board board, Move *move)
12698 {   // reconstruct game,and compare all positions in it
12699     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12700     do {
12701         int piece = move->piece;
12702         int to = move->to, from = pieceList[piece];
12703         if(found < 0) { // if already found just scan to game end for final piece count
12704           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12705            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12706            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12707                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12708             ) {
12709             static int lastCounts[EmptySquare+1];
12710             int i;
12711             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12712             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12713           } else stretch = 0;
12714           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12715           if(found >= 0 && !appData.minPieces) return found;
12716         }
12717         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12718           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12719           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12720             piece = (++move)->piece;
12721             from = pieceList[piece];
12722             counts[pieceType[piece]]--;
12723             pieceType[piece] = (ChessSquare) move->to;
12724             counts[move->to]++;
12725           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12726             counts[pieceType[quickBoard[to]]]--;
12727             quickBoard[to] = 0; total--;
12728             move++;
12729             continue;
12730           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12731             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12732             from  = pieceList[piece]; // so this must be King
12733             quickBoard[from] = 0;
12734             pieceList[piece] = to;
12735             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12736             quickBoard[from] = 0; // rook
12737             quickBoard[to] = piece;
12738             to = move->to; piece = move->piece;
12739             goto aftercastle;
12740           }
12741         }
12742         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12743         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12744         quickBoard[from] = 0;
12745       aftercastle:
12746         quickBoard[to] = piece;
12747         pieceList[piece] = to;
12748         cnt++; turn ^= 3;
12749         move++;
12750     } while(1);
12751 }
12752
12753 void
12754 InitSearch ()
12755 {
12756     int r, f;
12757     flipSearch = FALSE;
12758     CopyBoard(soughtBoard, boards[currentMove]);
12759     soughtTotal = MakePieceList(soughtBoard, maxSought);
12760     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12761     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12762     CopyBoard(reverseBoard, boards[currentMove]);
12763     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12764         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12765         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12766         reverseBoard[r][f] = piece;
12767     }
12768     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12769     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12770     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12771                  || (boards[currentMove][CASTLING][2] == NoRights ||
12772                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12773                  && (boards[currentMove][CASTLING][5] == NoRights ||
12774                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12775       ) {
12776         flipSearch = TRUE;
12777         CopyBoard(flipBoard, soughtBoard);
12778         CopyBoard(rotateBoard, reverseBoard);
12779         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12780             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12781             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12782         }
12783     }
12784     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12785     if(appData.searchMode >= 5) {
12786         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12787         MakePieceList(soughtBoard, minSought);
12788         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12789     }
12790     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12791         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12792 }
12793
12794 GameInfo dummyInfo;
12795 static int creatingBook;
12796
12797 int
12798 GameContainsPosition (FILE *f, ListGame *lg)
12799 {
12800     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12801     int fromX, fromY, toX, toY;
12802     char promoChar;
12803     static int initDone=FALSE;
12804
12805     // weed out games based on numerical tag comparison
12806     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12807     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12808     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12809     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12810     if(!initDone) {
12811         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12812         initDone = TRUE;
12813     }
12814     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12815     else CopyBoard(boards[scratch], initialPosition); // default start position
12816     if(lg->moves) {
12817         turn = btm + 1;
12818         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12819         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12820     }
12821     if(btm) plyNr++;
12822     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12823     fseek(f, lg->offset, 0);
12824     yynewfile(f);
12825     while(1) {
12826         yyboardindex = scratch;
12827         quickFlag = plyNr+1;
12828         next = Myylex();
12829         quickFlag = 0;
12830         switch(next) {
12831             case PGNTag:
12832                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12833             default:
12834                 continue;
12835
12836             case XBoardGame:
12837             case GNUChessGame:
12838                 if(plyNr) return -1; // after we have seen moves, this is for new game
12839               continue;
12840
12841             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12842             case ImpossibleMove:
12843             case WhiteWins: // game ends here with these four
12844             case BlackWins:
12845             case GameIsDrawn:
12846             case GameUnfinished:
12847                 return -1;
12848
12849             case IllegalMove:
12850                 if(appData.testLegality) return -1;
12851             case WhiteCapturesEnPassant:
12852             case BlackCapturesEnPassant:
12853             case WhitePromotion:
12854             case BlackPromotion:
12855             case WhiteNonPromotion:
12856             case BlackNonPromotion:
12857             case NormalMove:
12858             case FirstLeg:
12859             case WhiteKingSideCastle:
12860             case WhiteQueenSideCastle:
12861             case BlackKingSideCastle:
12862             case BlackQueenSideCastle:
12863             case WhiteKingSideCastleWild:
12864             case WhiteQueenSideCastleWild:
12865             case BlackKingSideCastleWild:
12866             case BlackQueenSideCastleWild:
12867             case WhiteHSideCastleFR:
12868             case WhiteASideCastleFR:
12869             case BlackHSideCastleFR:
12870             case BlackASideCastleFR:
12871                 fromX = currentMoveString[0] - AAA;
12872                 fromY = currentMoveString[1] - ONE;
12873                 toX = currentMoveString[2] - AAA;
12874                 toY = currentMoveString[3] - ONE;
12875                 promoChar = currentMoveString[4];
12876                 break;
12877             case WhiteDrop:
12878             case BlackDrop:
12879                 fromX = next == WhiteDrop ?
12880                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12881                   (int) CharToPiece(ToLower(currentMoveString[0]));
12882                 fromY = DROP_RANK;
12883                 toX = currentMoveString[2] - AAA;
12884                 toY = currentMoveString[3] - ONE;
12885                 promoChar = 0;
12886                 break;
12887         }
12888         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12889         plyNr++;
12890         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12891         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12892         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12893         if(appData.findMirror) {
12894             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12895             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12896         }
12897     }
12898 }
12899
12900 /* Load the nth game from open file f */
12901 int
12902 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12903 {
12904     ChessMove cm;
12905     char buf[MSG_SIZ];
12906     int gn = gameNumber;
12907     ListGame *lg = NULL;
12908     int numPGNTags = 0;
12909     int err, pos = -1;
12910     GameMode oldGameMode;
12911     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12912     char oldName[MSG_SIZ];
12913
12914     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12915
12916     if (appData.debugMode)
12917         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12918
12919     if (gameMode == Training )
12920         SetTrainingModeOff();
12921
12922     oldGameMode = gameMode;
12923     if (gameMode != BeginningOfGame) {
12924       Reset(FALSE, TRUE);
12925     }
12926     killX = killY = -1; // [HGM] lion: in case we did not Reset
12927
12928     gameFileFP = f;
12929     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12930         fclose(lastLoadGameFP);
12931     }
12932
12933     if (useList) {
12934         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12935
12936         if (lg) {
12937             fseek(f, lg->offset, 0);
12938             GameListHighlight(gameNumber);
12939             pos = lg->position;
12940             gn = 1;
12941         }
12942         else {
12943             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12944               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12945             else
12946             DisplayError(_("Game number out of range"), 0);
12947             return FALSE;
12948         }
12949     } else {
12950         GameListDestroy();
12951         if (fseek(f, 0, 0) == -1) {
12952             if (f == lastLoadGameFP ?
12953                 gameNumber == lastLoadGameNumber + 1 :
12954                 gameNumber == 1) {
12955                 gn = 1;
12956             } else {
12957                 DisplayError(_("Can't seek on game file"), 0);
12958                 return FALSE;
12959             }
12960         }
12961     }
12962     lastLoadGameFP = f;
12963     lastLoadGameNumber = gameNumber;
12964     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12965     lastLoadGameUseList = useList;
12966
12967     yynewfile(f);
12968
12969     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12970       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12971                 lg->gameInfo.black);
12972             DisplayTitle(buf);
12973     } else if (*title != NULLCHAR) {
12974         if (gameNumber > 1) {
12975           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12976             DisplayTitle(buf);
12977         } else {
12978             DisplayTitle(title);
12979         }
12980     }
12981
12982     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12983         gameMode = PlayFromGameFile;
12984         ModeHighlight();
12985     }
12986
12987     currentMove = forwardMostMove = backwardMostMove = 0;
12988     CopyBoard(boards[0], initialPosition);
12989     StopClocks();
12990
12991     /*
12992      * Skip the first gn-1 games in the file.
12993      * Also skip over anything that precedes an identifiable
12994      * start of game marker, to avoid being confused by
12995      * garbage at the start of the file.  Currently
12996      * recognized start of game markers are the move number "1",
12997      * the pattern "gnuchess .* game", the pattern
12998      * "^[#;%] [^ ]* game file", and a PGN tag block.
12999      * A game that starts with one of the latter two patterns
13000      * will also have a move number 1, possibly
13001      * following a position diagram.
13002      * 5-4-02: Let's try being more lenient and allowing a game to
13003      * start with an unnumbered move.  Does that break anything?
13004      */
13005     cm = lastLoadGameStart = EndOfFile;
13006     while (gn > 0) {
13007         yyboardindex = forwardMostMove;
13008         cm = (ChessMove) Myylex();
13009         switch (cm) {
13010           case EndOfFile:
13011             if (cmailMsgLoaded) {
13012                 nCmailGames = CMAIL_MAX_GAMES - gn;
13013             } else {
13014                 Reset(TRUE, TRUE);
13015                 DisplayError(_("Game not found in file"), 0);
13016             }
13017             return FALSE;
13018
13019           case GNUChessGame:
13020           case XBoardGame:
13021             gn--;
13022             lastLoadGameStart = cm;
13023             break;
13024
13025           case MoveNumberOne:
13026             switch (lastLoadGameStart) {
13027               case GNUChessGame:
13028               case XBoardGame:
13029               case PGNTag:
13030                 break;
13031               case MoveNumberOne:
13032               case EndOfFile:
13033                 gn--;           /* count this game */
13034                 lastLoadGameStart = cm;
13035                 break;
13036               default:
13037                 /* impossible */
13038                 break;
13039             }
13040             break;
13041
13042           case PGNTag:
13043             switch (lastLoadGameStart) {
13044               case GNUChessGame:
13045               case PGNTag:
13046               case MoveNumberOne:
13047               case EndOfFile:
13048                 gn--;           /* count this game */
13049                 lastLoadGameStart = cm;
13050                 break;
13051               case XBoardGame:
13052                 lastLoadGameStart = cm; /* game counted already */
13053                 break;
13054               default:
13055                 /* impossible */
13056                 break;
13057             }
13058             if (gn > 0) {
13059                 do {
13060                     yyboardindex = forwardMostMove;
13061                     cm = (ChessMove) Myylex();
13062                 } while (cm == PGNTag || cm == Comment);
13063             }
13064             break;
13065
13066           case WhiteWins:
13067           case BlackWins:
13068           case GameIsDrawn:
13069             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13070                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13071                     != CMAIL_OLD_RESULT) {
13072                     nCmailResults ++ ;
13073                     cmailResult[  CMAIL_MAX_GAMES
13074                                 - gn - 1] = CMAIL_OLD_RESULT;
13075                 }
13076             }
13077             break;
13078
13079           case NormalMove:
13080           case FirstLeg:
13081             /* Only a NormalMove can be at the start of a game
13082              * without a position diagram. */
13083             if (lastLoadGameStart == EndOfFile ) {
13084               gn--;
13085               lastLoadGameStart = MoveNumberOne;
13086             }
13087             break;
13088
13089           default:
13090             break;
13091         }
13092     }
13093
13094     if (appData.debugMode)
13095       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13096
13097     if (cm == XBoardGame) {
13098         /* Skip any header junk before position diagram and/or move 1 */
13099         for (;;) {
13100             yyboardindex = forwardMostMove;
13101             cm = (ChessMove) Myylex();
13102
13103             if (cm == EndOfFile ||
13104                 cm == GNUChessGame || cm == XBoardGame) {
13105                 /* Empty game; pretend end-of-file and handle later */
13106                 cm = EndOfFile;
13107                 break;
13108             }
13109
13110             if (cm == MoveNumberOne || cm == PositionDiagram ||
13111                 cm == PGNTag || cm == Comment)
13112               break;
13113         }
13114     } else if (cm == GNUChessGame) {
13115         if (gameInfo.event != NULL) {
13116             free(gameInfo.event);
13117         }
13118         gameInfo.event = StrSave(yy_text);
13119     }
13120
13121     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13122     while (cm == PGNTag) {
13123         if (appData.debugMode)
13124           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13125         err = ParsePGNTag(yy_text, &gameInfo);
13126         if (!err) numPGNTags++;
13127
13128         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13129         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13130             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13131             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13132             InitPosition(TRUE);
13133             oldVariant = gameInfo.variant;
13134             if (appData.debugMode)
13135               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13136         }
13137
13138
13139         if (gameInfo.fen != NULL) {
13140           Board initial_position;
13141           startedFromSetupPosition = TRUE;
13142           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13143             Reset(TRUE, TRUE);
13144             DisplayError(_("Bad FEN position in file"), 0);
13145             return FALSE;
13146           }
13147           CopyBoard(boards[0], initial_position);
13148           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13149             CopyBoard(initialPosition, initial_position);
13150           if (blackPlaysFirst) {
13151             currentMove = forwardMostMove = backwardMostMove = 1;
13152             CopyBoard(boards[1], initial_position);
13153             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13154             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13155             timeRemaining[0][1] = whiteTimeRemaining;
13156             timeRemaining[1][1] = blackTimeRemaining;
13157             if (commentList[0] != NULL) {
13158               commentList[1] = commentList[0];
13159               commentList[0] = NULL;
13160             }
13161           } else {
13162             currentMove = forwardMostMove = backwardMostMove = 0;
13163           }
13164           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13165           {   int i;
13166               initialRulePlies = FENrulePlies;
13167               for( i=0; i< nrCastlingRights; i++ )
13168                   initialRights[i] = initial_position[CASTLING][i];
13169           }
13170           yyboardindex = forwardMostMove;
13171           free(gameInfo.fen);
13172           gameInfo.fen = NULL;
13173         }
13174
13175         yyboardindex = forwardMostMove;
13176         cm = (ChessMove) Myylex();
13177
13178         /* Handle comments interspersed among the tags */
13179         while (cm == Comment) {
13180             char *p;
13181             if (appData.debugMode)
13182               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13183             p = yy_text;
13184             AppendComment(currentMove, p, FALSE);
13185             yyboardindex = forwardMostMove;
13186             cm = (ChessMove) Myylex();
13187         }
13188     }
13189
13190     /* don't rely on existence of Event tag since if game was
13191      * pasted from clipboard the Event tag may not exist
13192      */
13193     if (numPGNTags > 0){
13194         char *tags;
13195         if (gameInfo.variant == VariantNormal) {
13196           VariantClass v = StringToVariant(gameInfo.event);
13197           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13198           if(v < VariantShogi) gameInfo.variant = v;
13199         }
13200         if (!matchMode) {
13201           if( appData.autoDisplayTags ) {
13202             tags = PGNTags(&gameInfo);
13203             TagsPopUp(tags, CmailMsg());
13204             free(tags);
13205           }
13206         }
13207     } else {
13208         /* Make something up, but don't display it now */
13209         SetGameInfo();
13210         TagsPopDown();
13211     }
13212
13213     if (cm == PositionDiagram) {
13214         int i, j;
13215         char *p;
13216         Board initial_position;
13217
13218         if (appData.debugMode)
13219           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13220
13221         if (!startedFromSetupPosition) {
13222             p = yy_text;
13223             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13224               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13225                 switch (*p) {
13226                   case '{':
13227                   case '[':
13228                   case '-':
13229                   case ' ':
13230                   case '\t':
13231                   case '\n':
13232                   case '\r':
13233                     break;
13234                   default:
13235                     initial_position[i][j++] = CharToPiece(*p);
13236                     break;
13237                 }
13238             while (*p == ' ' || *p == '\t' ||
13239                    *p == '\n' || *p == '\r') p++;
13240
13241             if (strncmp(p, "black", strlen("black"))==0)
13242               blackPlaysFirst = TRUE;
13243             else
13244               blackPlaysFirst = FALSE;
13245             startedFromSetupPosition = TRUE;
13246
13247             CopyBoard(boards[0], initial_position);
13248             if (blackPlaysFirst) {
13249                 currentMove = forwardMostMove = backwardMostMove = 1;
13250                 CopyBoard(boards[1], initial_position);
13251                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13252                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13253                 timeRemaining[0][1] = whiteTimeRemaining;
13254                 timeRemaining[1][1] = blackTimeRemaining;
13255                 if (commentList[0] != NULL) {
13256                     commentList[1] = commentList[0];
13257                     commentList[0] = NULL;
13258                 }
13259             } else {
13260                 currentMove = forwardMostMove = backwardMostMove = 0;
13261             }
13262         }
13263         yyboardindex = forwardMostMove;
13264         cm = (ChessMove) Myylex();
13265     }
13266
13267   if(!creatingBook) {
13268     if (first.pr == NoProc) {
13269         StartChessProgram(&first);
13270     }
13271     InitChessProgram(&first, FALSE);
13272     if(gameInfo.variant == VariantUnknown && *oldName) {
13273         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13274         gameInfo.variant = v;
13275     }
13276     SendToProgram("force\n", &first);
13277     if (startedFromSetupPosition) {
13278         SendBoard(&first, forwardMostMove);
13279     if (appData.debugMode) {
13280         fprintf(debugFP, "Load Game\n");
13281     }
13282         DisplayBothClocks();
13283     }
13284   }
13285
13286     /* [HGM] server: flag to write setup moves in broadcast file as one */
13287     loadFlag = appData.suppressLoadMoves;
13288
13289     while (cm == Comment) {
13290         char *p;
13291         if (appData.debugMode)
13292           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13293         p = yy_text;
13294         AppendComment(currentMove, p, FALSE);
13295         yyboardindex = forwardMostMove;
13296         cm = (ChessMove) Myylex();
13297     }
13298
13299     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13300         cm == WhiteWins || cm == BlackWins ||
13301         cm == GameIsDrawn || cm == GameUnfinished) {
13302         DisplayMessage("", _("No moves in game"));
13303         if (cmailMsgLoaded) {
13304             if (appData.debugMode)
13305               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13306             ClearHighlights();
13307             flipView = FALSE;
13308         }
13309         DrawPosition(FALSE, boards[currentMove]);
13310         DisplayBothClocks();
13311         gameMode = EditGame;
13312         ModeHighlight();
13313         gameFileFP = NULL;
13314         cmailOldMove = 0;
13315         return TRUE;
13316     }
13317
13318     // [HGM] PV info: routine tests if comment empty
13319     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13320         DisplayComment(currentMove - 1, commentList[currentMove]);
13321     }
13322     if (!matchMode && appData.timeDelay != 0)
13323       DrawPosition(FALSE, boards[currentMove]);
13324
13325     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13326       programStats.ok_to_send = 1;
13327     }
13328
13329     /* if the first token after the PGN tags is a move
13330      * and not move number 1, retrieve it from the parser
13331      */
13332     if (cm != MoveNumberOne)
13333         LoadGameOneMove(cm);
13334
13335     /* load the remaining moves from the file */
13336     while (LoadGameOneMove(EndOfFile)) {
13337       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13338       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13339     }
13340
13341     /* rewind to the start of the game */
13342     currentMove = backwardMostMove;
13343
13344     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13345
13346     if (oldGameMode == AnalyzeFile) {
13347       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13348       AnalyzeFileEvent();
13349     } else
13350     if (oldGameMode == AnalyzeMode) {
13351       AnalyzeFileEvent();
13352     }
13353
13354     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13355         long int w, b; // [HGM] adjourn: restore saved clock times
13356         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13357         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13358             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13359             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13360         }
13361     }
13362
13363     if(creatingBook) return TRUE;
13364     if (!matchMode && pos > 0) {
13365         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13366     } else
13367     if (matchMode || appData.timeDelay == 0) {
13368       ToEndEvent();
13369     } else if (appData.timeDelay > 0) {
13370       AutoPlayGameLoop();
13371     }
13372
13373     if (appData.debugMode)
13374         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13375
13376     loadFlag = 0; /* [HGM] true game starts */
13377     return TRUE;
13378 }
13379
13380 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13381 int
13382 ReloadPosition (int offset)
13383 {
13384     int positionNumber = lastLoadPositionNumber + offset;
13385     if (lastLoadPositionFP == NULL) {
13386         DisplayError(_("No position has been loaded yet"), 0);
13387         return FALSE;
13388     }
13389     if (positionNumber <= 0) {
13390         DisplayError(_("Can't back up any further"), 0);
13391         return FALSE;
13392     }
13393     return LoadPosition(lastLoadPositionFP, positionNumber,
13394                         lastLoadPositionTitle);
13395 }
13396
13397 /* Load the nth position from the given file */
13398 int
13399 LoadPositionFromFile (char *filename, int n, char *title)
13400 {
13401     FILE *f;
13402     char buf[MSG_SIZ];
13403
13404     if (strcmp(filename, "-") == 0) {
13405         return LoadPosition(stdin, n, "stdin");
13406     } else {
13407         f = fopen(filename, "rb");
13408         if (f == NULL) {
13409             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13410             DisplayError(buf, errno);
13411             return FALSE;
13412         } else {
13413             return LoadPosition(f, n, title);
13414         }
13415     }
13416 }
13417
13418 /* Load the nth position from the given open file, and close it */
13419 int
13420 LoadPosition (FILE *f, int positionNumber, char *title)
13421 {
13422     char *p, line[MSG_SIZ];
13423     Board initial_position;
13424     int i, j, fenMode, pn;
13425
13426     if (gameMode == Training )
13427         SetTrainingModeOff();
13428
13429     if (gameMode != BeginningOfGame) {
13430         Reset(FALSE, TRUE);
13431     }
13432     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13433         fclose(lastLoadPositionFP);
13434     }
13435     if (positionNumber == 0) positionNumber = 1;
13436     lastLoadPositionFP = f;
13437     lastLoadPositionNumber = positionNumber;
13438     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13439     if (first.pr == NoProc && !appData.noChessProgram) {
13440       StartChessProgram(&first);
13441       InitChessProgram(&first, FALSE);
13442     }
13443     pn = positionNumber;
13444     if (positionNumber < 0) {
13445         /* Negative position number means to seek to that byte offset */
13446         if (fseek(f, -positionNumber, 0) == -1) {
13447             DisplayError(_("Can't seek on position file"), 0);
13448             return FALSE;
13449         };
13450         pn = 1;
13451     } else {
13452         if (fseek(f, 0, 0) == -1) {
13453             if (f == lastLoadPositionFP ?
13454                 positionNumber == lastLoadPositionNumber + 1 :
13455                 positionNumber == 1) {
13456                 pn = 1;
13457             } else {
13458                 DisplayError(_("Can't seek on position file"), 0);
13459                 return FALSE;
13460             }
13461         }
13462     }
13463     /* See if this file is FEN or old-style xboard */
13464     if (fgets(line, MSG_SIZ, f) == NULL) {
13465         DisplayError(_("Position not found in file"), 0);
13466         return FALSE;
13467     }
13468     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13469     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13470
13471     if (pn >= 2) {
13472         if (fenMode || line[0] == '#') pn--;
13473         while (pn > 0) {
13474             /* skip positions before number pn */
13475             if (fgets(line, MSG_SIZ, f) == NULL) {
13476                 Reset(TRUE, TRUE);
13477                 DisplayError(_("Position not found in file"), 0);
13478                 return FALSE;
13479             }
13480             if (fenMode || line[0] == '#') pn--;
13481         }
13482     }
13483
13484     if (fenMode) {
13485         char *p;
13486         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13487             DisplayError(_("Bad FEN position in file"), 0);
13488             return FALSE;
13489         }
13490         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13491             sscanf(p+3, "%s", bestMove);
13492         } else *bestMove = NULLCHAR;
13493     } else {
13494         (void) fgets(line, MSG_SIZ, f);
13495         (void) fgets(line, MSG_SIZ, f);
13496
13497         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13498             (void) fgets(line, MSG_SIZ, f);
13499             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13500                 if (*p == ' ')
13501                   continue;
13502                 initial_position[i][j++] = CharToPiece(*p);
13503             }
13504         }
13505
13506         blackPlaysFirst = FALSE;
13507         if (!feof(f)) {
13508             (void) fgets(line, MSG_SIZ, f);
13509             if (strncmp(line, "black", strlen("black"))==0)
13510               blackPlaysFirst = TRUE;
13511         }
13512     }
13513     startedFromSetupPosition = TRUE;
13514
13515     CopyBoard(boards[0], initial_position);
13516     if (blackPlaysFirst) {
13517         currentMove = forwardMostMove = backwardMostMove = 1;
13518         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13519         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13520         CopyBoard(boards[1], initial_position);
13521         DisplayMessage("", _("Black to play"));
13522     } else {
13523         currentMove = forwardMostMove = backwardMostMove = 0;
13524         DisplayMessage("", _("White to play"));
13525     }
13526     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13527     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13528         SendToProgram("force\n", &first);
13529         SendBoard(&first, forwardMostMove);
13530     }
13531     if (appData.debugMode) {
13532 int i, j;
13533   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13534   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13535         fprintf(debugFP, "Load Position\n");
13536     }
13537
13538     if (positionNumber > 1) {
13539       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13540         DisplayTitle(line);
13541     } else {
13542         DisplayTitle(title);
13543     }
13544     gameMode = EditGame;
13545     ModeHighlight();
13546     ResetClocks();
13547     timeRemaining[0][1] = whiteTimeRemaining;
13548     timeRemaining[1][1] = blackTimeRemaining;
13549     DrawPosition(FALSE, boards[currentMove]);
13550
13551     return TRUE;
13552 }
13553
13554
13555 void
13556 CopyPlayerNameIntoFileName (char **dest, char *src)
13557 {
13558     while (*src != NULLCHAR && *src != ',') {
13559         if (*src == ' ') {
13560             *(*dest)++ = '_';
13561             src++;
13562         } else {
13563             *(*dest)++ = *src++;
13564         }
13565     }
13566 }
13567
13568 char *
13569 DefaultFileName (char *ext)
13570 {
13571     static char def[MSG_SIZ];
13572     char *p;
13573
13574     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13575         p = def;
13576         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13577         *p++ = '-';
13578         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13579         *p++ = '.';
13580         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13581     } else {
13582         def[0] = NULLCHAR;
13583     }
13584     return def;
13585 }
13586
13587 /* Save the current game to the given file */
13588 int
13589 SaveGameToFile (char *filename, int append)
13590 {
13591     FILE *f;
13592     char buf[MSG_SIZ];
13593     int result, i, t,tot=0;
13594
13595     if (strcmp(filename, "-") == 0) {
13596         return SaveGame(stdout, 0, NULL);
13597     } else {
13598         for(i=0; i<10; i++) { // upto 10 tries
13599              f = fopen(filename, append ? "a" : "w");
13600              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13601              if(f || errno != 13) break;
13602              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13603              tot += t;
13604         }
13605         if (f == NULL) {
13606             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13607             DisplayError(buf, errno);
13608             return FALSE;
13609         } else {
13610             safeStrCpy(buf, lastMsg, MSG_SIZ);
13611             DisplayMessage(_("Waiting for access to save file"), "");
13612             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13613             DisplayMessage(_("Saving game"), "");
13614             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13615             result = SaveGame(f, 0, NULL);
13616             DisplayMessage(buf, "");
13617             return result;
13618         }
13619     }
13620 }
13621
13622 char *
13623 SavePart (char *str)
13624 {
13625     static char buf[MSG_SIZ];
13626     char *p;
13627
13628     p = strchr(str, ' ');
13629     if (p == NULL) return str;
13630     strncpy(buf, str, p - str);
13631     buf[p - str] = NULLCHAR;
13632     return buf;
13633 }
13634
13635 #define PGN_MAX_LINE 75
13636
13637 #define PGN_SIDE_WHITE  0
13638 #define PGN_SIDE_BLACK  1
13639
13640 static int
13641 FindFirstMoveOutOfBook (int side)
13642 {
13643     int result = -1;
13644
13645     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13646         int index = backwardMostMove;
13647         int has_book_hit = 0;
13648
13649         if( (index % 2) != side ) {
13650             index++;
13651         }
13652
13653         while( index < forwardMostMove ) {
13654             /* Check to see if engine is in book */
13655             int depth = pvInfoList[index].depth;
13656             int score = pvInfoList[index].score;
13657             int in_book = 0;
13658
13659             if( depth <= 2 ) {
13660                 in_book = 1;
13661             }
13662             else if( score == 0 && depth == 63 ) {
13663                 in_book = 1; /* Zappa */
13664             }
13665             else if( score == 2 && depth == 99 ) {
13666                 in_book = 1; /* Abrok */
13667             }
13668
13669             has_book_hit += in_book;
13670
13671             if( ! in_book ) {
13672                 result = index;
13673
13674                 break;
13675             }
13676
13677             index += 2;
13678         }
13679     }
13680
13681     return result;
13682 }
13683
13684 void
13685 GetOutOfBookInfo (char * buf)
13686 {
13687     int oob[2];
13688     int i;
13689     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13690
13691     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13692     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13693
13694     *buf = '\0';
13695
13696     if( oob[0] >= 0 || oob[1] >= 0 ) {
13697         for( i=0; i<2; i++ ) {
13698             int idx = oob[i];
13699
13700             if( idx >= 0 ) {
13701                 if( i > 0 && oob[0] >= 0 ) {
13702                     strcat( buf, "   " );
13703                 }
13704
13705                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13706                 sprintf( buf+strlen(buf), "%s%.2f",
13707                     pvInfoList[idx].score >= 0 ? "+" : "",
13708                     pvInfoList[idx].score / 100.0 );
13709             }
13710         }
13711     }
13712 }
13713
13714 /* Save game in PGN style */
13715 static void
13716 SaveGamePGN2 (FILE *f)
13717 {
13718     int i, offset, linelen, newblock;
13719 //    char *movetext;
13720     char numtext[32];
13721     int movelen, numlen, blank;
13722     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13723
13724     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13725
13726     PrintPGNTags(f, &gameInfo);
13727
13728     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13729
13730     if (backwardMostMove > 0 || startedFromSetupPosition) {
13731         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13732         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13733         fprintf(f, "\n{--------------\n");
13734         PrintPosition(f, backwardMostMove);
13735         fprintf(f, "--------------}\n");
13736         free(fen);
13737     }
13738     else {
13739         /* [AS] Out of book annotation */
13740         if( appData.saveOutOfBookInfo ) {
13741             char buf[64];
13742
13743             GetOutOfBookInfo( buf );
13744
13745             if( buf[0] != '\0' ) {
13746                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13747             }
13748         }
13749
13750         fprintf(f, "\n");
13751     }
13752
13753     i = backwardMostMove;
13754     linelen = 0;
13755     newblock = TRUE;
13756
13757     while (i < forwardMostMove) {
13758         /* Print comments preceding this move */
13759         if (commentList[i] != NULL) {
13760             if (linelen > 0) fprintf(f, "\n");
13761             fprintf(f, "%s", commentList[i]);
13762             linelen = 0;
13763             newblock = TRUE;
13764         }
13765
13766         /* Format move number */
13767         if ((i % 2) == 0)
13768           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13769         else
13770           if (newblock)
13771             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13772           else
13773             numtext[0] = NULLCHAR;
13774
13775         numlen = strlen(numtext);
13776         newblock = FALSE;
13777
13778         /* Print move number */
13779         blank = linelen > 0 && numlen > 0;
13780         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13781             fprintf(f, "\n");
13782             linelen = 0;
13783             blank = 0;
13784         }
13785         if (blank) {
13786             fprintf(f, " ");
13787             linelen++;
13788         }
13789         fprintf(f, "%s", numtext);
13790         linelen += numlen;
13791
13792         /* Get move */
13793         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13794         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13795
13796         /* Print move */
13797         blank = linelen > 0 && movelen > 0;
13798         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13799             fprintf(f, "\n");
13800             linelen = 0;
13801             blank = 0;
13802         }
13803         if (blank) {
13804             fprintf(f, " ");
13805             linelen++;
13806         }
13807         fprintf(f, "%s", move_buffer);
13808         linelen += movelen;
13809
13810         /* [AS] Add PV info if present */
13811         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13812             /* [HGM] add time */
13813             char buf[MSG_SIZ]; int seconds;
13814
13815             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13816
13817             if( seconds <= 0)
13818               buf[0] = 0;
13819             else
13820               if( seconds < 30 )
13821                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13822               else
13823                 {
13824                   seconds = (seconds + 4)/10; // round to full seconds
13825                   if( seconds < 60 )
13826                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13827                   else
13828                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13829                 }
13830
13831             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13832                       pvInfoList[i].score >= 0 ? "+" : "",
13833                       pvInfoList[i].score / 100.0,
13834                       pvInfoList[i].depth,
13835                       buf );
13836
13837             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13838
13839             /* Print score/depth */
13840             blank = linelen > 0 && movelen > 0;
13841             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13842                 fprintf(f, "\n");
13843                 linelen = 0;
13844                 blank = 0;
13845             }
13846             if (blank) {
13847                 fprintf(f, " ");
13848                 linelen++;
13849             }
13850             fprintf(f, "%s", move_buffer);
13851             linelen += movelen;
13852         }
13853
13854         i++;
13855     }
13856
13857     /* Start a new line */
13858     if (linelen > 0) fprintf(f, "\n");
13859
13860     /* Print comments after last move */
13861     if (commentList[i] != NULL) {
13862         fprintf(f, "%s\n", commentList[i]);
13863     }
13864
13865     /* Print result */
13866     if (gameInfo.resultDetails != NULL &&
13867         gameInfo.resultDetails[0] != NULLCHAR) {
13868         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13869         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13870            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13871             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13872         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13873     } else {
13874         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13875     }
13876 }
13877
13878 /* Save game in PGN style and close the file */
13879 int
13880 SaveGamePGN (FILE *f)
13881 {
13882     SaveGamePGN2(f);
13883     fclose(f);
13884     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13885     return TRUE;
13886 }
13887
13888 /* Save game in old style and close the file */
13889 int
13890 SaveGameOldStyle (FILE *f)
13891 {
13892     int i, offset;
13893     time_t tm;
13894
13895     tm = time((time_t *) NULL);
13896
13897     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13898     PrintOpponents(f);
13899
13900     if (backwardMostMove > 0 || startedFromSetupPosition) {
13901         fprintf(f, "\n[--------------\n");
13902         PrintPosition(f, backwardMostMove);
13903         fprintf(f, "--------------]\n");
13904     } else {
13905         fprintf(f, "\n");
13906     }
13907
13908     i = backwardMostMove;
13909     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13910
13911     while (i < forwardMostMove) {
13912         if (commentList[i] != NULL) {
13913             fprintf(f, "[%s]\n", commentList[i]);
13914         }
13915
13916         if ((i % 2) == 1) {
13917             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13918             i++;
13919         } else {
13920             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13921             i++;
13922             if (commentList[i] != NULL) {
13923                 fprintf(f, "\n");
13924                 continue;
13925             }
13926             if (i >= forwardMostMove) {
13927                 fprintf(f, "\n");
13928                 break;
13929             }
13930             fprintf(f, "%s\n", parseList[i]);
13931             i++;
13932         }
13933     }
13934
13935     if (commentList[i] != NULL) {
13936         fprintf(f, "[%s]\n", commentList[i]);
13937     }
13938
13939     /* This isn't really the old style, but it's close enough */
13940     if (gameInfo.resultDetails != NULL &&
13941         gameInfo.resultDetails[0] != NULLCHAR) {
13942         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13943                 gameInfo.resultDetails);
13944     } else {
13945         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13946     }
13947
13948     fclose(f);
13949     return TRUE;
13950 }
13951
13952 /* Save the current game to open file f and close the file */
13953 int
13954 SaveGame (FILE *f, int dummy, char *dummy2)
13955 {
13956     if (gameMode == EditPosition) EditPositionDone(TRUE);
13957     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13958     if (appData.oldSaveStyle)
13959       return SaveGameOldStyle(f);
13960     else
13961       return SaveGamePGN(f);
13962 }
13963
13964 /* Save the current position to the given file */
13965 int
13966 SavePositionToFile (char *filename)
13967 {
13968     FILE *f;
13969     char buf[MSG_SIZ];
13970
13971     if (strcmp(filename, "-") == 0) {
13972         return SavePosition(stdout, 0, NULL);
13973     } else {
13974         f = fopen(filename, "a");
13975         if (f == NULL) {
13976             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13977             DisplayError(buf, errno);
13978             return FALSE;
13979         } else {
13980             safeStrCpy(buf, lastMsg, MSG_SIZ);
13981             DisplayMessage(_("Waiting for access to save file"), "");
13982             flock(fileno(f), LOCK_EX); // [HGM] lock
13983             DisplayMessage(_("Saving position"), "");
13984             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13985             SavePosition(f, 0, NULL);
13986             DisplayMessage(buf, "");
13987             return TRUE;
13988         }
13989     }
13990 }
13991
13992 /* Save the current position to the given open file and close the file */
13993 int
13994 SavePosition (FILE *f, int dummy, char *dummy2)
13995 {
13996     time_t tm;
13997     char *fen;
13998
13999     if (gameMode == EditPosition) EditPositionDone(TRUE);
14000     if (appData.oldSaveStyle) {
14001         tm = time((time_t *) NULL);
14002
14003         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14004         PrintOpponents(f);
14005         fprintf(f, "[--------------\n");
14006         PrintPosition(f, currentMove);
14007         fprintf(f, "--------------]\n");
14008     } else {
14009         fen = PositionToFEN(currentMove, NULL, 1);
14010         fprintf(f, "%s\n", fen);
14011         free(fen);
14012     }
14013     fclose(f);
14014     return TRUE;
14015 }
14016
14017 void
14018 ReloadCmailMsgEvent (int unregister)
14019 {
14020 #if !WIN32
14021     static char *inFilename = NULL;
14022     static char *outFilename;
14023     int i;
14024     struct stat inbuf, outbuf;
14025     int status;
14026
14027     /* Any registered moves are unregistered if unregister is set, */
14028     /* i.e. invoked by the signal handler */
14029     if (unregister) {
14030         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14031             cmailMoveRegistered[i] = FALSE;
14032             if (cmailCommentList[i] != NULL) {
14033                 free(cmailCommentList[i]);
14034                 cmailCommentList[i] = NULL;
14035             }
14036         }
14037         nCmailMovesRegistered = 0;
14038     }
14039
14040     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14041         cmailResult[i] = CMAIL_NOT_RESULT;
14042     }
14043     nCmailResults = 0;
14044
14045     if (inFilename == NULL) {
14046         /* Because the filenames are static they only get malloced once  */
14047         /* and they never get freed                                      */
14048         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14049         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14050
14051         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14052         sprintf(outFilename, "%s.out", appData.cmailGameName);
14053     }
14054
14055     status = stat(outFilename, &outbuf);
14056     if (status < 0) {
14057         cmailMailedMove = FALSE;
14058     } else {
14059         status = stat(inFilename, &inbuf);
14060         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14061     }
14062
14063     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14064        counts the games, notes how each one terminated, etc.
14065
14066        It would be nice to remove this kludge and instead gather all
14067        the information while building the game list.  (And to keep it
14068        in the game list nodes instead of having a bunch of fixed-size
14069        parallel arrays.)  Note this will require getting each game's
14070        termination from the PGN tags, as the game list builder does
14071        not process the game moves.  --mann
14072        */
14073     cmailMsgLoaded = TRUE;
14074     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14075
14076     /* Load first game in the file or popup game menu */
14077     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14078
14079 #endif /* !WIN32 */
14080     return;
14081 }
14082
14083 int
14084 RegisterMove ()
14085 {
14086     FILE *f;
14087     char string[MSG_SIZ];
14088
14089     if (   cmailMailedMove
14090         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14091         return TRUE;            /* Allow free viewing  */
14092     }
14093
14094     /* Unregister move to ensure that we don't leave RegisterMove        */
14095     /* with the move registered when the conditions for registering no   */
14096     /* longer hold                                                       */
14097     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14098         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14099         nCmailMovesRegistered --;
14100
14101         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14102           {
14103               free(cmailCommentList[lastLoadGameNumber - 1]);
14104               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14105           }
14106     }
14107
14108     if (cmailOldMove == -1) {
14109         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14110         return FALSE;
14111     }
14112
14113     if (currentMove > cmailOldMove + 1) {
14114         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14115         return FALSE;
14116     }
14117
14118     if (currentMove < cmailOldMove) {
14119         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14120         return FALSE;
14121     }
14122
14123     if (forwardMostMove > currentMove) {
14124         /* Silently truncate extra moves */
14125         TruncateGame();
14126     }
14127
14128     if (   (currentMove == cmailOldMove + 1)
14129         || (   (currentMove == cmailOldMove)
14130             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14131                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14132         if (gameInfo.result != GameUnfinished) {
14133             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14134         }
14135
14136         if (commentList[currentMove] != NULL) {
14137             cmailCommentList[lastLoadGameNumber - 1]
14138               = StrSave(commentList[currentMove]);
14139         }
14140         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14141
14142         if (appData.debugMode)
14143           fprintf(debugFP, "Saving %s for game %d\n",
14144                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14145
14146         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14147
14148         f = fopen(string, "w");
14149         if (appData.oldSaveStyle) {
14150             SaveGameOldStyle(f); /* also closes the file */
14151
14152             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14153             f = fopen(string, "w");
14154             SavePosition(f, 0, NULL); /* also closes the file */
14155         } else {
14156             fprintf(f, "{--------------\n");
14157             PrintPosition(f, currentMove);
14158             fprintf(f, "--------------}\n\n");
14159
14160             SaveGame(f, 0, NULL); /* also closes the file*/
14161         }
14162
14163         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14164         nCmailMovesRegistered ++;
14165     } else if (nCmailGames == 1) {
14166         DisplayError(_("You have not made a move yet"), 0);
14167         return FALSE;
14168     }
14169
14170     return TRUE;
14171 }
14172
14173 void
14174 MailMoveEvent ()
14175 {
14176 #if !WIN32
14177     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14178     FILE *commandOutput;
14179     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14180     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14181     int nBuffers;
14182     int i;
14183     int archived;
14184     char *arcDir;
14185
14186     if (! cmailMsgLoaded) {
14187         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14188         return;
14189     }
14190
14191     if (nCmailGames == nCmailResults) {
14192         DisplayError(_("No unfinished games"), 0);
14193         return;
14194     }
14195
14196 #if CMAIL_PROHIBIT_REMAIL
14197     if (cmailMailedMove) {
14198       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);
14199         DisplayError(msg, 0);
14200         return;
14201     }
14202 #endif
14203
14204     if (! (cmailMailedMove || RegisterMove())) return;
14205
14206     if (   cmailMailedMove
14207         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14208       snprintf(string, MSG_SIZ, partCommandString,
14209                appData.debugMode ? " -v" : "", appData.cmailGameName);
14210         commandOutput = popen(string, "r");
14211
14212         if (commandOutput == NULL) {
14213             DisplayError(_("Failed to invoke cmail"), 0);
14214         } else {
14215             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14216                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14217             }
14218             if (nBuffers > 1) {
14219                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14220                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14221                 nBytes = MSG_SIZ - 1;
14222             } else {
14223                 (void) memcpy(msg, buffer, nBytes);
14224             }
14225             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14226
14227             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14228                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14229
14230                 archived = TRUE;
14231                 for (i = 0; i < nCmailGames; i ++) {
14232                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14233                         archived = FALSE;
14234                     }
14235                 }
14236                 if (   archived
14237                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14238                         != NULL)) {
14239                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14240                            arcDir,
14241                            appData.cmailGameName,
14242                            gameInfo.date);
14243                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14244                     cmailMsgLoaded = FALSE;
14245                 }
14246             }
14247
14248             DisplayInformation(msg);
14249             pclose(commandOutput);
14250         }
14251     } else {
14252         if ((*cmailMsg) != '\0') {
14253             DisplayInformation(cmailMsg);
14254         }
14255     }
14256
14257     return;
14258 #endif /* !WIN32 */
14259 }
14260
14261 char *
14262 CmailMsg ()
14263 {
14264 #if WIN32
14265     return NULL;
14266 #else
14267     int  prependComma = 0;
14268     char number[5];
14269     char string[MSG_SIZ];       /* Space for game-list */
14270     int  i;
14271
14272     if (!cmailMsgLoaded) return "";
14273
14274     if (cmailMailedMove) {
14275       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14276     } else {
14277         /* Create a list of games left */
14278       snprintf(string, MSG_SIZ, "[");
14279         for (i = 0; i < nCmailGames; i ++) {
14280             if (! (   cmailMoveRegistered[i]
14281                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14282                 if (prependComma) {
14283                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14284                 } else {
14285                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14286                     prependComma = 1;
14287                 }
14288
14289                 strcat(string, number);
14290             }
14291         }
14292         strcat(string, "]");
14293
14294         if (nCmailMovesRegistered + nCmailResults == 0) {
14295             switch (nCmailGames) {
14296               case 1:
14297                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14298                 break;
14299
14300               case 2:
14301                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14302                 break;
14303
14304               default:
14305                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14306                          nCmailGames);
14307                 break;
14308             }
14309         } else {
14310             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14311               case 1:
14312                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14313                          string);
14314                 break;
14315
14316               case 0:
14317                 if (nCmailResults == nCmailGames) {
14318                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14319                 } else {
14320                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14321                 }
14322                 break;
14323
14324               default:
14325                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14326                          string);
14327             }
14328         }
14329     }
14330     return cmailMsg;
14331 #endif /* WIN32 */
14332 }
14333
14334 void
14335 ResetGameEvent ()
14336 {
14337     if (gameMode == Training)
14338       SetTrainingModeOff();
14339
14340     Reset(TRUE, TRUE);
14341     cmailMsgLoaded = FALSE;
14342     if (appData.icsActive) {
14343       SendToICS(ics_prefix);
14344       SendToICS("refresh\n");
14345     }
14346 }
14347
14348 void
14349 ExitEvent (int status)
14350 {
14351     exiting++;
14352     if (exiting > 2) {
14353       /* Give up on clean exit */
14354       exit(status);
14355     }
14356     if (exiting > 1) {
14357       /* Keep trying for clean exit */
14358       return;
14359     }
14360
14361     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14362     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14363
14364     if (telnetISR != NULL) {
14365       RemoveInputSource(telnetISR);
14366     }
14367     if (icsPR != NoProc) {
14368       DestroyChildProcess(icsPR, TRUE);
14369     }
14370
14371     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14372     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14373
14374     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14375     /* make sure this other one finishes before killing it!                  */
14376     if(endingGame) { int count = 0;
14377         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14378         while(endingGame && count++ < 10) DoSleep(1);
14379         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14380     }
14381
14382     /* Kill off chess programs */
14383     if (first.pr != NoProc) {
14384         ExitAnalyzeMode();
14385
14386         DoSleep( appData.delayBeforeQuit );
14387         SendToProgram("quit\n", &first);
14388         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14389     }
14390     if (second.pr != NoProc) {
14391         DoSleep( appData.delayBeforeQuit );
14392         SendToProgram("quit\n", &second);
14393         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14394     }
14395     if (first.isr != NULL) {
14396         RemoveInputSource(first.isr);
14397     }
14398     if (second.isr != NULL) {
14399         RemoveInputSource(second.isr);
14400     }
14401
14402     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14403     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14404
14405     ShutDownFrontEnd();
14406     exit(status);
14407 }
14408
14409 void
14410 PauseEngine (ChessProgramState *cps)
14411 {
14412     SendToProgram("pause\n", cps);
14413     cps->pause = 2;
14414 }
14415
14416 void
14417 UnPauseEngine (ChessProgramState *cps)
14418 {
14419     SendToProgram("resume\n", cps);
14420     cps->pause = 1;
14421 }
14422
14423 void
14424 PauseEvent ()
14425 {
14426     if (appData.debugMode)
14427         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14428     if (pausing) {
14429         pausing = FALSE;
14430         ModeHighlight();
14431         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14432             StartClocks();
14433             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14434                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14435                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14436             }
14437             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14438             HandleMachineMove(stashedInputMove, stalledEngine);
14439             stalledEngine = NULL;
14440             return;
14441         }
14442         if (gameMode == MachinePlaysWhite ||
14443             gameMode == TwoMachinesPlay   ||
14444             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14445             if(first.pause)  UnPauseEngine(&first);
14446             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14447             if(second.pause) UnPauseEngine(&second);
14448             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14449             StartClocks();
14450         } else {
14451             DisplayBothClocks();
14452         }
14453         if (gameMode == PlayFromGameFile) {
14454             if (appData.timeDelay >= 0)
14455                 AutoPlayGameLoop();
14456         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14457             Reset(FALSE, TRUE);
14458             SendToICS(ics_prefix);
14459             SendToICS("refresh\n");
14460         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14461             ForwardInner(forwardMostMove);
14462         }
14463         pauseExamInvalid = FALSE;
14464     } else {
14465         switch (gameMode) {
14466           default:
14467             return;
14468           case IcsExamining:
14469             pauseExamForwardMostMove = forwardMostMove;
14470             pauseExamInvalid = FALSE;
14471             /* fall through */
14472           case IcsObserving:
14473           case IcsPlayingWhite:
14474           case IcsPlayingBlack:
14475             pausing = TRUE;
14476             ModeHighlight();
14477             return;
14478           case PlayFromGameFile:
14479             (void) StopLoadGameTimer();
14480             pausing = TRUE;
14481             ModeHighlight();
14482             break;
14483           case BeginningOfGame:
14484             if (appData.icsActive) return;
14485             /* else fall through */
14486           case MachinePlaysWhite:
14487           case MachinePlaysBlack:
14488           case TwoMachinesPlay:
14489             if (forwardMostMove == 0)
14490               return;           /* don't pause if no one has moved */
14491             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14492                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14493                 if(onMove->pause) {           // thinking engine can be paused
14494                     PauseEngine(onMove);      // do it
14495                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14496                         PauseEngine(onMove->other);
14497                     else
14498                         SendToProgram("easy\n", onMove->other);
14499                     StopClocks();
14500                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14501             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14502                 if(first.pause) {
14503                     PauseEngine(&first);
14504                     StopClocks();
14505                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14506             } else { // human on move, pause pondering by either method
14507                 if(first.pause)
14508                     PauseEngine(&first);
14509                 else if(appData.ponderNextMove)
14510                     SendToProgram("easy\n", &first);
14511                 StopClocks();
14512             }
14513             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14514           case AnalyzeMode:
14515             pausing = TRUE;
14516             ModeHighlight();
14517             break;
14518         }
14519     }
14520 }
14521
14522 void
14523 EditCommentEvent ()
14524 {
14525     char title[MSG_SIZ];
14526
14527     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14528       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14529     } else {
14530       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14531                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14532                parseList[currentMove - 1]);
14533     }
14534
14535     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14536 }
14537
14538
14539 void
14540 EditTagsEvent ()
14541 {
14542     char *tags = PGNTags(&gameInfo);
14543     bookUp = FALSE;
14544     EditTagsPopUp(tags, NULL);
14545     free(tags);
14546 }
14547
14548 void
14549 ToggleSecond ()
14550 {
14551   if(second.analyzing) {
14552     SendToProgram("exit\n", &second);
14553     second.analyzing = FALSE;
14554   } else {
14555     if (second.pr == NoProc) StartChessProgram(&second);
14556     InitChessProgram(&second, FALSE);
14557     FeedMovesToProgram(&second, currentMove);
14558
14559     SendToProgram("analyze\n", &second);
14560     second.analyzing = TRUE;
14561   }
14562 }
14563
14564 /* Toggle ShowThinking */
14565 void
14566 ToggleShowThinking()
14567 {
14568   appData.showThinking = !appData.showThinking;
14569   ShowThinkingEvent();
14570 }
14571
14572 int
14573 AnalyzeModeEvent ()
14574 {
14575     char buf[MSG_SIZ];
14576
14577     if (!first.analysisSupport) {
14578       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14579       DisplayError(buf, 0);
14580       return 0;
14581     }
14582     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14583     if (appData.icsActive) {
14584         if (gameMode != IcsObserving) {
14585           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14586             DisplayError(buf, 0);
14587             /* secure check */
14588             if (appData.icsEngineAnalyze) {
14589                 if (appData.debugMode)
14590                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14591                 ExitAnalyzeMode();
14592                 ModeHighlight();
14593             }
14594             return 0;
14595         }
14596         /* if enable, user wants to disable icsEngineAnalyze */
14597         if (appData.icsEngineAnalyze) {
14598                 ExitAnalyzeMode();
14599                 ModeHighlight();
14600                 return 0;
14601         }
14602         appData.icsEngineAnalyze = TRUE;
14603         if (appData.debugMode)
14604             fprintf(debugFP, "ICS engine analyze starting... \n");
14605     }
14606
14607     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14608     if (appData.noChessProgram || gameMode == AnalyzeMode)
14609       return 0;
14610
14611     if (gameMode != AnalyzeFile) {
14612         if (!appData.icsEngineAnalyze) {
14613                EditGameEvent();
14614                if (gameMode != EditGame) return 0;
14615         }
14616         if (!appData.showThinking) ToggleShowThinking();
14617         ResurrectChessProgram();
14618         SendToProgram("analyze\n", &first);
14619         first.analyzing = TRUE;
14620         /*first.maybeThinking = TRUE;*/
14621         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14622         EngineOutputPopUp();
14623     }
14624     if (!appData.icsEngineAnalyze) {
14625         gameMode = AnalyzeMode;
14626         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14627     }
14628     pausing = FALSE;
14629     ModeHighlight();
14630     SetGameInfo();
14631
14632     StartAnalysisClock();
14633     GetTimeMark(&lastNodeCountTime);
14634     lastNodeCount = 0;
14635     return 1;
14636 }
14637
14638 void
14639 AnalyzeFileEvent ()
14640 {
14641     if (appData.noChessProgram || gameMode == AnalyzeFile)
14642       return;
14643
14644     if (!first.analysisSupport) {
14645       char buf[MSG_SIZ];
14646       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14647       DisplayError(buf, 0);
14648       return;
14649     }
14650
14651     if (gameMode != AnalyzeMode) {
14652         keepInfo = 1; // mere annotating should not alter PGN tags
14653         EditGameEvent();
14654         keepInfo = 0;
14655         if (gameMode != EditGame) return;
14656         if (!appData.showThinking) ToggleShowThinking();
14657         ResurrectChessProgram();
14658         SendToProgram("analyze\n", &first);
14659         first.analyzing = TRUE;
14660         /*first.maybeThinking = TRUE;*/
14661         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14662         EngineOutputPopUp();
14663     }
14664     gameMode = AnalyzeFile;
14665     pausing = FALSE;
14666     ModeHighlight();
14667
14668     StartAnalysisClock();
14669     GetTimeMark(&lastNodeCountTime);
14670     lastNodeCount = 0;
14671     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14672     AnalysisPeriodicEvent(1);
14673 }
14674
14675 void
14676 MachineWhiteEvent ()
14677 {
14678     char buf[MSG_SIZ];
14679     char *bookHit = NULL;
14680
14681     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14682       return;
14683
14684
14685     if (gameMode == PlayFromGameFile ||
14686         gameMode == TwoMachinesPlay  ||
14687         gameMode == Training         ||
14688         gameMode == AnalyzeMode      ||
14689         gameMode == EndOfGame)
14690         EditGameEvent();
14691
14692     if (gameMode == EditPosition)
14693         EditPositionDone(TRUE);
14694
14695     if (!WhiteOnMove(currentMove)) {
14696         DisplayError(_("It is not White's turn"), 0);
14697         return;
14698     }
14699
14700     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14701       ExitAnalyzeMode();
14702
14703     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14704         gameMode == AnalyzeFile)
14705         TruncateGame();
14706
14707     ResurrectChessProgram();    /* in case it isn't running */
14708     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14709         gameMode = MachinePlaysWhite;
14710         ResetClocks();
14711     } else
14712     gameMode = MachinePlaysWhite;
14713     pausing = FALSE;
14714     ModeHighlight();
14715     SetGameInfo();
14716     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14717     DisplayTitle(buf);
14718     if (first.sendName) {
14719       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14720       SendToProgram(buf, &first);
14721     }
14722     if (first.sendTime) {
14723       if (first.useColors) {
14724         SendToProgram("black\n", &first); /*gnu kludge*/
14725       }
14726       SendTimeRemaining(&first, TRUE);
14727     }
14728     if (first.useColors) {
14729       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14730     }
14731     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14732     SetMachineThinkingEnables();
14733     first.maybeThinking = TRUE;
14734     StartClocks();
14735     firstMove = FALSE;
14736
14737     if (appData.autoFlipView && !flipView) {
14738       flipView = !flipView;
14739       DrawPosition(FALSE, NULL);
14740       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14741     }
14742
14743     if(bookHit) { // [HGM] book: simulate book reply
14744         static char bookMove[MSG_SIZ]; // a bit generous?
14745
14746         programStats.nodes = programStats.depth = programStats.time =
14747         programStats.score = programStats.got_only_move = 0;
14748         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14749
14750         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14751         strcat(bookMove, bookHit);
14752         HandleMachineMove(bookMove, &first);
14753     }
14754 }
14755
14756 void
14757 MachineBlackEvent ()
14758 {
14759   char buf[MSG_SIZ];
14760   char *bookHit = NULL;
14761
14762     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
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 Black'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     gameMode = MachinePlaysBlack;
14790     pausing = FALSE;
14791     ModeHighlight();
14792     SetGameInfo();
14793     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14794     DisplayTitle(buf);
14795     if (first.sendName) {
14796       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14797       SendToProgram(buf, &first);
14798     }
14799     if (first.sendTime) {
14800       if (first.useColors) {
14801         SendToProgram("white\n", &first); /*gnu kludge*/
14802       }
14803       SendTimeRemaining(&first, FALSE);
14804     }
14805     if (first.useColors) {
14806       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14807     }
14808     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14809     SetMachineThinkingEnables();
14810     first.maybeThinking = TRUE;
14811     StartClocks();
14812
14813     if (appData.autoFlipView && flipView) {
14814       flipView = !flipView;
14815       DrawPosition(FALSE, NULL);
14816       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14817     }
14818     if(bookHit) { // [HGM] book: simulate book reply
14819         static char bookMove[MSG_SIZ]; // a bit generous?
14820
14821         programStats.nodes = programStats.depth = programStats.time =
14822         programStats.score = programStats.got_only_move = 0;
14823         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14824
14825         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14826         strcat(bookMove, bookHit);
14827         HandleMachineMove(bookMove, &first);
14828     }
14829 }
14830
14831
14832 void
14833 DisplayTwoMachinesTitle ()
14834 {
14835     char buf[MSG_SIZ];
14836     if (appData.matchGames > 0) {
14837         if(appData.tourneyFile[0]) {
14838           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14839                    gameInfo.white, _("vs."), gameInfo.black,
14840                    nextGame+1, appData.matchGames+1,
14841                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14842         } else
14843         if (first.twoMachinesColor[0] == 'w') {
14844           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14845                    gameInfo.white, _("vs."),  gameInfo.black,
14846                    first.matchWins, second.matchWins,
14847                    matchGame - 1 - (first.matchWins + second.matchWins));
14848         } else {
14849           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14850                    gameInfo.white, _("vs."), gameInfo.black,
14851                    second.matchWins, first.matchWins,
14852                    matchGame - 1 - (first.matchWins + second.matchWins));
14853         }
14854     } else {
14855       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14856     }
14857     DisplayTitle(buf);
14858 }
14859
14860 void
14861 SettingsMenuIfReady ()
14862 {
14863   if (second.lastPing != second.lastPong) {
14864     DisplayMessage("", _("Waiting for second chess program"));
14865     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14866     return;
14867   }
14868   ThawUI();
14869   DisplayMessage("", "");
14870   SettingsPopUp(&second);
14871 }
14872
14873 int
14874 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14875 {
14876     char buf[MSG_SIZ];
14877     if (cps->pr == NoProc) {
14878         StartChessProgram(cps);
14879         if (cps->protocolVersion == 1) {
14880           retry();
14881           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14882         } else {
14883           /* kludge: allow timeout for initial "feature" command */
14884           if(retry != TwoMachinesEventIfReady) FreezeUI();
14885           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14886           DisplayMessage("", buf);
14887           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14888         }
14889         return 1;
14890     }
14891     return 0;
14892 }
14893
14894 void
14895 TwoMachinesEvent P((void))
14896 {
14897     int i;
14898     char buf[MSG_SIZ];
14899     ChessProgramState *onmove;
14900     char *bookHit = NULL;
14901     static int stalling = 0;
14902     TimeMark now;
14903     long wait;
14904
14905     if (appData.noChessProgram) return;
14906
14907     switch (gameMode) {
14908       case TwoMachinesPlay:
14909         return;
14910       case MachinePlaysWhite:
14911       case MachinePlaysBlack:
14912         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14913             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14914             return;
14915         }
14916         /* fall through */
14917       case BeginningOfGame:
14918       case PlayFromGameFile:
14919       case EndOfGame:
14920         EditGameEvent();
14921         if (gameMode != EditGame) return;
14922         break;
14923       case EditPosition:
14924         EditPositionDone(TRUE);
14925         break;
14926       case AnalyzeMode:
14927       case AnalyzeFile:
14928         ExitAnalyzeMode();
14929         break;
14930       case EditGame:
14931       default:
14932         break;
14933     }
14934
14935 //    forwardMostMove = currentMove;
14936     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14937     startingEngine = TRUE;
14938
14939     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14940
14941     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14942     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14943       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14944       return;
14945     }
14946     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14947
14948     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14949                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14950         startingEngine = matchMode = FALSE;
14951         DisplayError("second engine does not play this", 0);
14952         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14953         EditGameEvent(); // switch back to EditGame mode
14954         return;
14955     }
14956
14957     if(!stalling) {
14958       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14959       SendToProgram("force\n", &second);
14960       stalling = 1;
14961       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14962       return;
14963     }
14964     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14965     if(appData.matchPause>10000 || appData.matchPause<10)
14966                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14967     wait = SubtractTimeMarks(&now, &pauseStart);
14968     if(wait < appData.matchPause) {
14969         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14970         return;
14971     }
14972     // we are now committed to starting the game
14973     stalling = 0;
14974     DisplayMessage("", "");
14975     if (startedFromSetupPosition) {
14976         SendBoard(&second, backwardMostMove);
14977     if (appData.debugMode) {
14978         fprintf(debugFP, "Two Machines\n");
14979     }
14980     }
14981     for (i = backwardMostMove; i < forwardMostMove; i++) {
14982         SendMoveToProgram(i, &second);
14983     }
14984
14985     gameMode = TwoMachinesPlay;
14986     pausing = startingEngine = FALSE;
14987     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14988     SetGameInfo();
14989     DisplayTwoMachinesTitle();
14990     firstMove = TRUE;
14991     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14992         onmove = &first;
14993     } else {
14994         onmove = &second;
14995     }
14996     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14997     SendToProgram(first.computerString, &first);
14998     if (first.sendName) {
14999       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
15000       SendToProgram(buf, &first);
15001     }
15002     SendToProgram(second.computerString, &second);
15003     if (second.sendName) {
15004       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15005       SendToProgram(buf, &second);
15006     }
15007
15008     ResetClocks();
15009     if (!first.sendTime || !second.sendTime) {
15010         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15011         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15012     }
15013     if (onmove->sendTime) {
15014       if (onmove->useColors) {
15015         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15016       }
15017       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15018     }
15019     if (onmove->useColors) {
15020       SendToProgram(onmove->twoMachinesColor, onmove);
15021     }
15022     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15023 //    SendToProgram("go\n", onmove);
15024     onmove->maybeThinking = TRUE;
15025     SetMachineThinkingEnables();
15026
15027     StartClocks();
15028
15029     if(bookHit) { // [HGM] book: simulate book reply
15030         static char bookMove[MSG_SIZ]; // a bit generous?
15031
15032         programStats.nodes = programStats.depth = programStats.time =
15033         programStats.score = programStats.got_only_move = 0;
15034         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15035
15036         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15037         strcat(bookMove, bookHit);
15038         savedMessage = bookMove; // args for deferred call
15039         savedState = onmove;
15040         ScheduleDelayedEvent(DeferredBookMove, 1);
15041     }
15042 }
15043
15044 void
15045 TrainingEvent ()
15046 {
15047     if (gameMode == Training) {
15048       SetTrainingModeOff();
15049       gameMode = PlayFromGameFile;
15050       DisplayMessage("", _("Training mode off"));
15051     } else {
15052       gameMode = Training;
15053       animateTraining = appData.animate;
15054
15055       /* make sure we are not already at the end of the game */
15056       if (currentMove < forwardMostMove) {
15057         SetTrainingModeOn();
15058         DisplayMessage("", _("Training mode on"));
15059       } else {
15060         gameMode = PlayFromGameFile;
15061         DisplayError(_("Already at end of game"), 0);
15062       }
15063     }
15064     ModeHighlight();
15065 }
15066
15067 void
15068 IcsClientEvent ()
15069 {
15070     if (!appData.icsActive) return;
15071     switch (gameMode) {
15072       case IcsPlayingWhite:
15073       case IcsPlayingBlack:
15074       case IcsObserving:
15075       case IcsIdle:
15076       case BeginningOfGame:
15077       case IcsExamining:
15078         return;
15079
15080       case EditGame:
15081         break;
15082
15083       case EditPosition:
15084         EditPositionDone(TRUE);
15085         break;
15086
15087       case AnalyzeMode:
15088       case AnalyzeFile:
15089         ExitAnalyzeMode();
15090         break;
15091
15092       default:
15093         EditGameEvent();
15094         break;
15095     }
15096
15097     gameMode = IcsIdle;
15098     ModeHighlight();
15099     return;
15100 }
15101
15102 void
15103 EditGameEvent ()
15104 {
15105     int i;
15106
15107     switch (gameMode) {
15108       case Training:
15109         SetTrainingModeOff();
15110         break;
15111       case MachinePlaysWhite:
15112       case MachinePlaysBlack:
15113       case BeginningOfGame:
15114         SendToProgram("force\n", &first);
15115         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15116             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15117                 char buf[MSG_SIZ];
15118                 abortEngineThink = TRUE;
15119                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15120                 SendToProgram(buf, &first);
15121                 DisplayMessage("Aborting engine think", "");
15122                 FreezeUI();
15123             }
15124         }
15125         SetUserThinkingEnables();
15126         break;
15127       case PlayFromGameFile:
15128         (void) StopLoadGameTimer();
15129         if (gameFileFP != NULL) {
15130             gameFileFP = NULL;
15131         }
15132         break;
15133       case EditPosition:
15134         EditPositionDone(TRUE);
15135         break;
15136       case AnalyzeMode:
15137       case AnalyzeFile:
15138         ExitAnalyzeMode();
15139         SendToProgram("force\n", &first);
15140         break;
15141       case TwoMachinesPlay:
15142         GameEnds(EndOfFile, NULL, GE_PLAYER);
15143         ResurrectChessProgram();
15144         SetUserThinkingEnables();
15145         break;
15146       case EndOfGame:
15147         ResurrectChessProgram();
15148         break;
15149       case IcsPlayingBlack:
15150       case IcsPlayingWhite:
15151         DisplayError(_("Warning: You are still playing a game"), 0);
15152         break;
15153       case IcsObserving:
15154         DisplayError(_("Warning: You are still observing a game"), 0);
15155         break;
15156       case IcsExamining:
15157         DisplayError(_("Warning: You are still examining a game"), 0);
15158         break;
15159       case IcsIdle:
15160         break;
15161       case EditGame:
15162       default:
15163         return;
15164     }
15165
15166     pausing = FALSE;
15167     StopClocks();
15168     first.offeredDraw = second.offeredDraw = 0;
15169
15170     if (gameMode == PlayFromGameFile) {
15171         whiteTimeRemaining = timeRemaining[0][currentMove];
15172         blackTimeRemaining = timeRemaining[1][currentMove];
15173         DisplayTitle("");
15174     }
15175
15176     if (gameMode == MachinePlaysWhite ||
15177         gameMode == MachinePlaysBlack ||
15178         gameMode == TwoMachinesPlay ||
15179         gameMode == EndOfGame) {
15180         i = forwardMostMove;
15181         while (i > currentMove) {
15182             SendToProgram("undo\n", &first);
15183             i--;
15184         }
15185         if(!adjustedClock) {
15186         whiteTimeRemaining = timeRemaining[0][currentMove];
15187         blackTimeRemaining = timeRemaining[1][currentMove];
15188         DisplayBothClocks();
15189         }
15190         if (whiteFlag || blackFlag) {
15191             whiteFlag = blackFlag = 0;
15192         }
15193         DisplayTitle("");
15194     }
15195
15196     gameMode = EditGame;
15197     ModeHighlight();
15198     SetGameInfo();
15199 }
15200
15201
15202 void
15203 EditPositionEvent ()
15204 {
15205     if (gameMode == EditPosition) {
15206         EditGameEvent();
15207         return;
15208     }
15209
15210     EditGameEvent();
15211     if (gameMode != EditGame) return;
15212
15213     gameMode = EditPosition;
15214     ModeHighlight();
15215     SetGameInfo();
15216     if (currentMove > 0)
15217       CopyBoard(boards[0], boards[currentMove]);
15218
15219     blackPlaysFirst = !WhiteOnMove(currentMove);
15220     ResetClocks();
15221     currentMove = forwardMostMove = backwardMostMove = 0;
15222     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15223     DisplayMove(-1);
15224     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15225 }
15226
15227 void
15228 ExitAnalyzeMode ()
15229 {
15230     /* [DM] icsEngineAnalyze - possible call from other functions */
15231     if (appData.icsEngineAnalyze) {
15232         appData.icsEngineAnalyze = FALSE;
15233
15234         DisplayMessage("",_("Close ICS engine analyze..."));
15235     }
15236     if (first.analysisSupport && first.analyzing) {
15237       SendToBoth("exit\n");
15238       first.analyzing = second.analyzing = FALSE;
15239     }
15240     thinkOutput[0] = NULLCHAR;
15241 }
15242
15243 void
15244 EditPositionDone (Boolean fakeRights)
15245 {
15246     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15247
15248     startedFromSetupPosition = TRUE;
15249     InitChessProgram(&first, FALSE);
15250     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15251       boards[0][EP_STATUS] = EP_NONE;
15252       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15253       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15254         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15255         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15256       } else boards[0][CASTLING][2] = NoRights;
15257       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15258         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15259         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15260       } else boards[0][CASTLING][5] = NoRights;
15261       if(gameInfo.variant == VariantSChess) {
15262         int i;
15263         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15264           boards[0][VIRGIN][i] = 0;
15265           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15266           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15267         }
15268       }
15269     }
15270     SendToProgram("force\n", &first);
15271     if (blackPlaysFirst) {
15272         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15273         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15274         currentMove = forwardMostMove = backwardMostMove = 1;
15275         CopyBoard(boards[1], boards[0]);
15276     } else {
15277         currentMove = forwardMostMove = backwardMostMove = 0;
15278     }
15279     SendBoard(&first, forwardMostMove);
15280     if (appData.debugMode) {
15281         fprintf(debugFP, "EditPosDone\n");
15282     }
15283     DisplayTitle("");
15284     DisplayMessage("", "");
15285     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15286     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15287     gameMode = EditGame;
15288     ModeHighlight();
15289     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15290     ClearHighlights(); /* [AS] */
15291 }
15292
15293 /* Pause for `ms' milliseconds */
15294 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15295 void
15296 TimeDelay (long ms)
15297 {
15298     TimeMark m1, m2;
15299
15300     GetTimeMark(&m1);
15301     do {
15302         GetTimeMark(&m2);
15303     } while (SubtractTimeMarks(&m2, &m1) < ms);
15304 }
15305
15306 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15307 void
15308 SendMultiLineToICS (char *buf)
15309 {
15310     char temp[MSG_SIZ+1], *p;
15311     int len;
15312
15313     len = strlen(buf);
15314     if (len > MSG_SIZ)
15315       len = MSG_SIZ;
15316
15317     strncpy(temp, buf, len);
15318     temp[len] = 0;
15319
15320     p = temp;
15321     while (*p) {
15322         if (*p == '\n' || *p == '\r')
15323           *p = ' ';
15324         ++p;
15325     }
15326
15327     strcat(temp, "\n");
15328     SendToICS(temp);
15329     SendToPlayer(temp, strlen(temp));
15330 }
15331
15332 void
15333 SetWhiteToPlayEvent ()
15334 {
15335     if (gameMode == EditPosition) {
15336         blackPlaysFirst = FALSE;
15337         DisplayBothClocks();    /* works because currentMove is 0 */
15338     } else if (gameMode == IcsExamining) {
15339         SendToICS(ics_prefix);
15340         SendToICS("tomove white\n");
15341     }
15342 }
15343
15344 void
15345 SetBlackToPlayEvent ()
15346 {
15347     if (gameMode == EditPosition) {
15348         blackPlaysFirst = TRUE;
15349         currentMove = 1;        /* kludge */
15350         DisplayBothClocks();
15351         currentMove = 0;
15352     } else if (gameMode == IcsExamining) {
15353         SendToICS(ics_prefix);
15354         SendToICS("tomove black\n");
15355     }
15356 }
15357
15358 void
15359 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15360 {
15361     char buf[MSG_SIZ];
15362     ChessSquare piece = boards[0][y][x];
15363     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15364     static int lastVariant;
15365
15366     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15367
15368     switch (selection) {
15369       case ClearBoard:
15370         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15371         MarkTargetSquares(1);
15372         CopyBoard(currentBoard, boards[0]);
15373         CopyBoard(menuBoard, initialPosition);
15374         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15375             SendToICS(ics_prefix);
15376             SendToICS("bsetup clear\n");
15377         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15378             SendToICS(ics_prefix);
15379             SendToICS("clearboard\n");
15380         } else {
15381             int nonEmpty = 0;
15382             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15383                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15384                 for (y = 0; y < BOARD_HEIGHT; y++) {
15385                     if (gameMode == IcsExamining) {
15386                         if (boards[currentMove][y][x] != EmptySquare) {
15387                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15388                                     AAA + x, ONE + y);
15389                             SendToICS(buf);
15390                         }
15391                     } else if(boards[0][y][x] != DarkSquare) {
15392                         if(boards[0][y][x] != p) nonEmpty++;
15393                         boards[0][y][x] = p;
15394                     }
15395                 }
15396             }
15397             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15398                 int r;
15399                 for(r = 0; r < BOARD_HEIGHT; r++) {
15400                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15401                     ChessSquare p = menuBoard[r][x];
15402                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15403                   }
15404                 }
15405                 DisplayMessage("Clicking clock again restores position", "");
15406                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15407                 if(!nonEmpty) { // asked to clear an empty board
15408                     CopyBoard(boards[0], menuBoard);
15409                 } else
15410                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15411                     CopyBoard(boards[0], initialPosition);
15412                 } else
15413                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15414                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15415                     CopyBoard(boards[0], erasedBoard);
15416                 } else
15417                     CopyBoard(erasedBoard, currentBoard);
15418
15419             }
15420         }
15421         if (gameMode == EditPosition) {
15422             DrawPosition(FALSE, boards[0]);
15423         }
15424         break;
15425
15426       case WhitePlay:
15427         SetWhiteToPlayEvent();
15428         break;
15429
15430       case BlackPlay:
15431         SetBlackToPlayEvent();
15432         break;
15433
15434       case EmptySquare:
15435         if (gameMode == IcsExamining) {
15436             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15437             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15438             SendToICS(buf);
15439         } else {
15440             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15441                 if(x == BOARD_LEFT-2) {
15442                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15443                     boards[0][y][1] = 0;
15444                 } else
15445                 if(x == BOARD_RGHT+1) {
15446                     if(y >= gameInfo.holdingsSize) break;
15447                     boards[0][y][BOARD_WIDTH-2] = 0;
15448                 } else break;
15449             }
15450             boards[0][y][x] = EmptySquare;
15451             DrawPosition(FALSE, boards[0]);
15452         }
15453         break;
15454
15455       case PromotePiece:
15456         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15457            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15458             selection = (ChessSquare) (PROMOTED(piece));
15459         } else if(piece == EmptySquare) selection = WhiteSilver;
15460         else selection = (ChessSquare)((int)piece - 1);
15461         goto defaultlabel;
15462
15463       case DemotePiece:
15464         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15465            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15466             selection = (ChessSquare) (DEMOTED(piece));
15467         } else if(piece == EmptySquare) selection = BlackSilver;
15468         else selection = (ChessSquare)((int)piece + 1);
15469         goto defaultlabel;
15470
15471       case WhiteQueen:
15472       case BlackQueen:
15473         if(gameInfo.variant == VariantShatranj ||
15474            gameInfo.variant == VariantXiangqi  ||
15475            gameInfo.variant == VariantCourier  ||
15476            gameInfo.variant == VariantASEAN    ||
15477            gameInfo.variant == VariantMakruk     )
15478             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15479         goto defaultlabel;
15480
15481       case WhiteKing:
15482       case BlackKing:
15483         if(gameInfo.variant == VariantXiangqi)
15484             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15485         if(gameInfo.variant == VariantKnightmate)
15486             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15487       default:
15488         defaultlabel:
15489         if (gameMode == IcsExamining) {
15490             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15491             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15492                      PieceToChar(selection), AAA + x, ONE + y);
15493             SendToICS(buf);
15494         } else {
15495             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15496                 int n;
15497                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15498                     n = PieceToNumber(selection - BlackPawn);
15499                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15500                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15501                     boards[0][BOARD_HEIGHT-1-n][1]++;
15502                 } else
15503                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15504                     n = PieceToNumber(selection);
15505                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15506                     boards[0][n][BOARD_WIDTH-1] = selection;
15507                     boards[0][n][BOARD_WIDTH-2]++;
15508                 }
15509             } else
15510             boards[0][y][x] = selection;
15511             DrawPosition(TRUE, boards[0]);
15512             ClearHighlights();
15513             fromX = fromY = -1;
15514         }
15515         break;
15516     }
15517 }
15518
15519
15520 void
15521 DropMenuEvent (ChessSquare selection, int x, int y)
15522 {
15523     ChessMove moveType;
15524
15525     switch (gameMode) {
15526       case IcsPlayingWhite:
15527       case MachinePlaysBlack:
15528         if (!WhiteOnMove(currentMove)) {
15529             DisplayMoveError(_("It is Black's turn"));
15530             return;
15531         }
15532         moveType = WhiteDrop;
15533         break;
15534       case IcsPlayingBlack:
15535       case MachinePlaysWhite:
15536         if (WhiteOnMove(currentMove)) {
15537             DisplayMoveError(_("It is White's turn"));
15538             return;
15539         }
15540         moveType = BlackDrop;
15541         break;
15542       case EditGame:
15543         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15544         break;
15545       default:
15546         return;
15547     }
15548
15549     if (moveType == BlackDrop && selection < BlackPawn) {
15550       selection = (ChessSquare) ((int) selection
15551                                  + (int) BlackPawn - (int) WhitePawn);
15552     }
15553     if (boards[currentMove][y][x] != EmptySquare) {
15554         DisplayMoveError(_("That square is occupied"));
15555         return;
15556     }
15557
15558     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15559 }
15560
15561 void
15562 AcceptEvent ()
15563 {
15564     /* Accept a pending offer of any kind from opponent */
15565
15566     if (appData.icsActive) {
15567         SendToICS(ics_prefix);
15568         SendToICS("accept\n");
15569     } else if (cmailMsgLoaded) {
15570         if (currentMove == cmailOldMove &&
15571             commentList[cmailOldMove] != NULL &&
15572             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15573                    "Black offers a draw" : "White offers a draw")) {
15574             TruncateGame();
15575             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15576             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15577         } else {
15578             DisplayError(_("There is no pending offer on this move"), 0);
15579             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15580         }
15581     } else {
15582         /* Not used for offers from chess program */
15583     }
15584 }
15585
15586 void
15587 DeclineEvent ()
15588 {
15589     /* Decline a pending offer of any kind from opponent */
15590
15591     if (appData.icsActive) {
15592         SendToICS(ics_prefix);
15593         SendToICS("decline\n");
15594     } else if (cmailMsgLoaded) {
15595         if (currentMove == cmailOldMove &&
15596             commentList[cmailOldMove] != NULL &&
15597             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15598                    "Black offers a draw" : "White offers a draw")) {
15599 #ifdef NOTDEF
15600             AppendComment(cmailOldMove, "Draw declined", TRUE);
15601             DisplayComment(cmailOldMove - 1, "Draw declined");
15602 #endif /*NOTDEF*/
15603         } else {
15604             DisplayError(_("There is no pending offer on this move"), 0);
15605         }
15606     } else {
15607         /* Not used for offers from chess program */
15608     }
15609 }
15610
15611 void
15612 RematchEvent ()
15613 {
15614     /* Issue ICS rematch command */
15615     if (appData.icsActive) {
15616         SendToICS(ics_prefix);
15617         SendToICS("rematch\n");
15618     }
15619 }
15620
15621 void
15622 CallFlagEvent ()
15623 {
15624     /* Call your opponent's flag (claim a win on time) */
15625     if (appData.icsActive) {
15626         SendToICS(ics_prefix);
15627         SendToICS("flag\n");
15628     } else {
15629         switch (gameMode) {
15630           default:
15631             return;
15632           case MachinePlaysWhite:
15633             if (whiteFlag) {
15634                 if (blackFlag)
15635                   GameEnds(GameIsDrawn, "Both players ran out of time",
15636                            GE_PLAYER);
15637                 else
15638                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15639             } else {
15640                 DisplayError(_("Your opponent is not out of time"), 0);
15641             }
15642             break;
15643           case MachinePlaysBlack:
15644             if (blackFlag) {
15645                 if (whiteFlag)
15646                   GameEnds(GameIsDrawn, "Both players ran out of time",
15647                            GE_PLAYER);
15648                 else
15649                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15650             } else {
15651                 DisplayError(_("Your opponent is not out of time"), 0);
15652             }
15653             break;
15654         }
15655     }
15656 }
15657
15658 void
15659 ClockClick (int which)
15660 {       // [HGM] code moved to back-end from winboard.c
15661         if(which) { // black clock
15662           if (gameMode == EditPosition || gameMode == IcsExamining) {
15663             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15664             SetBlackToPlayEvent();
15665           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15666                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15667           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15668           } else if (shiftKey) {
15669             AdjustClock(which, -1);
15670           } else if (gameMode == IcsPlayingWhite ||
15671                      gameMode == MachinePlaysBlack) {
15672             CallFlagEvent();
15673           }
15674         } else { // white clock
15675           if (gameMode == EditPosition || gameMode == IcsExamining) {
15676             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15677             SetWhiteToPlayEvent();
15678           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15679                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15680           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15681           } else if (shiftKey) {
15682             AdjustClock(which, -1);
15683           } else if (gameMode == IcsPlayingBlack ||
15684                    gameMode == MachinePlaysWhite) {
15685             CallFlagEvent();
15686           }
15687         }
15688 }
15689
15690 void
15691 DrawEvent ()
15692 {
15693     /* Offer draw or accept pending draw offer from opponent */
15694
15695     if (appData.icsActive) {
15696         /* Note: tournament rules require draw offers to be
15697            made after you make your move but before you punch
15698            your clock.  Currently ICS doesn't let you do that;
15699            instead, you immediately punch your clock after making
15700            a move, but you can offer a draw at any time. */
15701
15702         SendToICS(ics_prefix);
15703         SendToICS("draw\n");
15704         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15705     } else if (cmailMsgLoaded) {
15706         if (currentMove == cmailOldMove &&
15707             commentList[cmailOldMove] != NULL &&
15708             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15709                    "Black offers a draw" : "White offers a draw")) {
15710             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15711             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15712         } else if (currentMove == cmailOldMove + 1) {
15713             char *offer = WhiteOnMove(cmailOldMove) ?
15714               "White offers a draw" : "Black offers a draw";
15715             AppendComment(currentMove, offer, TRUE);
15716             DisplayComment(currentMove - 1, offer);
15717             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15718         } else {
15719             DisplayError(_("You must make your move before offering a draw"), 0);
15720             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15721         }
15722     } else if (first.offeredDraw) {
15723         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15724     } else {
15725         if (first.sendDrawOffers) {
15726             SendToProgram("draw\n", &first);
15727             userOfferedDraw = TRUE;
15728         }
15729     }
15730 }
15731
15732 void
15733 AdjournEvent ()
15734 {
15735     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15736
15737     if (appData.icsActive) {
15738         SendToICS(ics_prefix);
15739         SendToICS("adjourn\n");
15740     } else {
15741         /* Currently GNU Chess doesn't offer or accept Adjourns */
15742     }
15743 }
15744
15745
15746 void
15747 AbortEvent ()
15748 {
15749     /* Offer Abort or accept pending Abort offer from opponent */
15750
15751     if (appData.icsActive) {
15752         SendToICS(ics_prefix);
15753         SendToICS("abort\n");
15754     } else {
15755         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15756     }
15757 }
15758
15759 void
15760 ResignEvent ()
15761 {
15762     /* Resign.  You can do this even if it's not your turn. */
15763
15764     if (appData.icsActive) {
15765         SendToICS(ics_prefix);
15766         SendToICS("resign\n");
15767     } else {
15768         switch (gameMode) {
15769           case MachinePlaysWhite:
15770             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15771             break;
15772           case MachinePlaysBlack:
15773             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15774             break;
15775           case EditGame:
15776             if (cmailMsgLoaded) {
15777                 TruncateGame();
15778                 if (WhiteOnMove(cmailOldMove)) {
15779                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15780                 } else {
15781                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15782                 }
15783                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15784             }
15785             break;
15786           default:
15787             break;
15788         }
15789     }
15790 }
15791
15792
15793 void
15794 StopObservingEvent ()
15795 {
15796     /* Stop observing current games */
15797     SendToICS(ics_prefix);
15798     SendToICS("unobserve\n");
15799 }
15800
15801 void
15802 StopExaminingEvent ()
15803 {
15804     /* Stop observing current game */
15805     SendToICS(ics_prefix);
15806     SendToICS("unexamine\n");
15807 }
15808
15809 void
15810 ForwardInner (int target)
15811 {
15812     int limit; int oldSeekGraphUp = seekGraphUp;
15813
15814     if (appData.debugMode)
15815         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15816                 target, currentMove, forwardMostMove);
15817
15818     if (gameMode == EditPosition)
15819       return;
15820
15821     seekGraphUp = FALSE;
15822     MarkTargetSquares(1);
15823     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15824
15825     if (gameMode == PlayFromGameFile && !pausing)
15826       PauseEvent();
15827
15828     if (gameMode == IcsExamining && pausing)
15829       limit = pauseExamForwardMostMove;
15830     else
15831       limit = forwardMostMove;
15832
15833     if (target > limit) target = limit;
15834
15835     if (target > 0 && moveList[target - 1][0]) {
15836         int fromX, fromY, toX, toY;
15837         toX = moveList[target - 1][2] - AAA;
15838         toY = moveList[target - 1][3] - ONE;
15839         if (moveList[target - 1][1] == '@') {
15840             if (appData.highlightLastMove) {
15841                 SetHighlights(-1, -1, toX, toY);
15842             }
15843         } else {
15844             int viaX = moveList[target - 1][5] - AAA;
15845             int viaY = moveList[target - 1][6] - ONE;
15846             fromX = moveList[target - 1][0] - AAA;
15847             fromY = moveList[target - 1][1] - ONE;
15848             if (target == currentMove + 1) {
15849                 if(moveList[target - 1][4] == ';') { // multi-leg
15850                     ChessSquare piece = boards[currentMove][viaY][viaX];
15851                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15852                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15853                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15854                     boards[currentMove][viaY][viaX] = piece;
15855                 } else
15856                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15857             }
15858             if (appData.highlightLastMove) {
15859                 SetHighlights(fromX, fromY, toX, toY);
15860             }
15861         }
15862     }
15863     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15864         gameMode == Training || gameMode == PlayFromGameFile ||
15865         gameMode == AnalyzeFile) {
15866         while (currentMove < target) {
15867             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15868             SendMoveToProgram(currentMove++, &first);
15869         }
15870     } else {
15871         currentMove = target;
15872     }
15873
15874     if (gameMode == EditGame || gameMode == EndOfGame) {
15875         whiteTimeRemaining = timeRemaining[0][currentMove];
15876         blackTimeRemaining = timeRemaining[1][currentMove];
15877     }
15878     DisplayBothClocks();
15879     DisplayMove(currentMove - 1);
15880     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15881     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15882     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15883         DisplayComment(currentMove - 1, commentList[currentMove]);
15884     }
15885     ClearMap(); // [HGM] exclude: invalidate map
15886 }
15887
15888
15889 void
15890 ForwardEvent ()
15891 {
15892     if (gameMode == IcsExamining && !pausing) {
15893         SendToICS(ics_prefix);
15894         SendToICS("forward\n");
15895     } else {
15896         ForwardInner(currentMove + 1);
15897     }
15898 }
15899
15900 void
15901 ToEndEvent ()
15902 {
15903     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15904         /* to optimze, we temporarily turn off analysis mode while we feed
15905          * the remaining moves to the engine. Otherwise we get analysis output
15906          * after each move.
15907          */
15908         if (first.analysisSupport) {
15909           SendToProgram("exit\nforce\n", &first);
15910           first.analyzing = FALSE;
15911         }
15912     }
15913
15914     if (gameMode == IcsExamining && !pausing) {
15915         SendToICS(ics_prefix);
15916         SendToICS("forward 999999\n");
15917     } else {
15918         ForwardInner(forwardMostMove);
15919     }
15920
15921     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15922         /* we have fed all the moves, so reactivate analysis mode */
15923         SendToProgram("analyze\n", &first);
15924         first.analyzing = TRUE;
15925         /*first.maybeThinking = TRUE;*/
15926         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15927     }
15928 }
15929
15930 void
15931 BackwardInner (int target)
15932 {
15933     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15934
15935     if (appData.debugMode)
15936         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15937                 target, currentMove, forwardMostMove);
15938
15939     if (gameMode == EditPosition) return;
15940     seekGraphUp = FALSE;
15941     MarkTargetSquares(1);
15942     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15943     if (currentMove <= backwardMostMove) {
15944         ClearHighlights();
15945         DrawPosition(full_redraw, boards[currentMove]);
15946         return;
15947     }
15948     if (gameMode == PlayFromGameFile && !pausing)
15949       PauseEvent();
15950
15951     if (moveList[target][0]) {
15952         int fromX, fromY, toX, toY;
15953         toX = moveList[target][2] - AAA;
15954         toY = moveList[target][3] - ONE;
15955         if (moveList[target][1] == '@') {
15956             if (appData.highlightLastMove) {
15957                 SetHighlights(-1, -1, toX, toY);
15958             }
15959         } else {
15960             fromX = moveList[target][0] - AAA;
15961             fromY = moveList[target][1] - ONE;
15962             if (target == currentMove - 1) {
15963                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15964             }
15965             if (appData.highlightLastMove) {
15966                 SetHighlights(fromX, fromY, toX, toY);
15967             }
15968         }
15969     }
15970     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15971         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15972         while (currentMove > target) {
15973             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15974                 // null move cannot be undone. Reload program with move history before it.
15975                 int i;
15976                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15977                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15978                 }
15979                 SendBoard(&first, i);
15980               if(second.analyzing) SendBoard(&second, i);
15981                 for(currentMove=i; currentMove<target; currentMove++) {
15982                     SendMoveToProgram(currentMove, &first);
15983                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15984                 }
15985                 break;
15986             }
15987             SendToBoth("undo\n");
15988             currentMove--;
15989         }
15990     } else {
15991         currentMove = target;
15992     }
15993
15994     if (gameMode == EditGame || gameMode == EndOfGame) {
15995         whiteTimeRemaining = timeRemaining[0][currentMove];
15996         blackTimeRemaining = timeRemaining[1][currentMove];
15997     }
15998     DisplayBothClocks();
15999     DisplayMove(currentMove - 1);
16000     DrawPosition(full_redraw, boards[currentMove]);
16001     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16002     // [HGM] PV info: routine tests if comment empty
16003     DisplayComment(currentMove - 1, commentList[currentMove]);
16004     ClearMap(); // [HGM] exclude: invalidate map
16005 }
16006
16007 void
16008 BackwardEvent ()
16009 {
16010     if (gameMode == IcsExamining && !pausing) {
16011         SendToICS(ics_prefix);
16012         SendToICS("backward\n");
16013     } else {
16014         BackwardInner(currentMove - 1);
16015     }
16016 }
16017
16018 void
16019 ToStartEvent ()
16020 {
16021     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16022         /* to optimize, we temporarily turn off analysis mode while we undo
16023          * all the moves. Otherwise we get analysis output after each undo.
16024          */
16025         if (first.analysisSupport) {
16026           SendToProgram("exit\nforce\n", &first);
16027           first.analyzing = FALSE;
16028         }
16029     }
16030
16031     if (gameMode == IcsExamining && !pausing) {
16032         SendToICS(ics_prefix);
16033         SendToICS("backward 999999\n");
16034     } else {
16035         BackwardInner(backwardMostMove);
16036     }
16037
16038     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16039         /* we have fed all the moves, so reactivate analysis mode */
16040         SendToProgram("analyze\n", &first);
16041         first.analyzing = TRUE;
16042         /*first.maybeThinking = TRUE;*/
16043         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16044     }
16045 }
16046
16047 void
16048 ToNrEvent (int to)
16049 {
16050   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16051   if (to >= forwardMostMove) to = forwardMostMove;
16052   if (to <= backwardMostMove) to = backwardMostMove;
16053   if (to < currentMove) {
16054     BackwardInner(to);
16055   } else {
16056     ForwardInner(to);
16057   }
16058 }
16059
16060 void
16061 RevertEvent (Boolean annotate)
16062 {
16063     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16064         return;
16065     }
16066     if (gameMode != IcsExamining) {
16067         DisplayError(_("You are not examining a game"), 0);
16068         return;
16069     }
16070     if (pausing) {
16071         DisplayError(_("You can't revert while pausing"), 0);
16072         return;
16073     }
16074     SendToICS(ics_prefix);
16075     SendToICS("revert\n");
16076 }
16077
16078 void
16079 RetractMoveEvent ()
16080 {
16081     switch (gameMode) {
16082       case MachinePlaysWhite:
16083       case MachinePlaysBlack:
16084         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16085             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16086             return;
16087         }
16088         if (forwardMostMove < 2) return;
16089         currentMove = forwardMostMove = forwardMostMove - 2;
16090         whiteTimeRemaining = timeRemaining[0][currentMove];
16091         blackTimeRemaining = timeRemaining[1][currentMove];
16092         DisplayBothClocks();
16093         DisplayMove(currentMove - 1);
16094         ClearHighlights();/*!! could figure this out*/
16095         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16096         SendToProgram("remove\n", &first);
16097         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16098         break;
16099
16100       case BeginningOfGame:
16101       default:
16102         break;
16103
16104       case IcsPlayingWhite:
16105       case IcsPlayingBlack:
16106         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16107             SendToICS(ics_prefix);
16108             SendToICS("takeback 2\n");
16109         } else {
16110             SendToICS(ics_prefix);
16111             SendToICS("takeback 1\n");
16112         }
16113         break;
16114     }
16115 }
16116
16117 void
16118 MoveNowEvent ()
16119 {
16120     ChessProgramState *cps;
16121
16122     switch (gameMode) {
16123       case MachinePlaysWhite:
16124         if (!WhiteOnMove(forwardMostMove)) {
16125             DisplayError(_("It is your turn"), 0);
16126             return;
16127         }
16128         cps = &first;
16129         break;
16130       case MachinePlaysBlack:
16131         if (WhiteOnMove(forwardMostMove)) {
16132             DisplayError(_("It is your turn"), 0);
16133             return;
16134         }
16135         cps = &first;
16136         break;
16137       case TwoMachinesPlay:
16138         if (WhiteOnMove(forwardMostMove) ==
16139             (first.twoMachinesColor[0] == 'w')) {
16140             cps = &first;
16141         } else {
16142             cps = &second;
16143         }
16144         break;
16145       case BeginningOfGame:
16146       default:
16147         return;
16148     }
16149     SendToProgram("?\n", cps);
16150 }
16151
16152 void
16153 TruncateGameEvent ()
16154 {
16155     EditGameEvent();
16156     if (gameMode != EditGame) return;
16157     TruncateGame();
16158 }
16159
16160 void
16161 TruncateGame ()
16162 {
16163     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16164     if (forwardMostMove > currentMove) {
16165         if (gameInfo.resultDetails != NULL) {
16166             free(gameInfo.resultDetails);
16167             gameInfo.resultDetails = NULL;
16168             gameInfo.result = GameUnfinished;
16169         }
16170         forwardMostMove = currentMove;
16171         HistorySet(parseList, backwardMostMove, forwardMostMove,
16172                    currentMove-1);
16173     }
16174 }
16175
16176 void
16177 HintEvent ()
16178 {
16179     if (appData.noChessProgram) return;
16180     switch (gameMode) {
16181       case MachinePlaysWhite:
16182         if (WhiteOnMove(forwardMostMove)) {
16183             DisplayError(_("Wait until your turn."), 0);
16184             return;
16185         }
16186         break;
16187       case BeginningOfGame:
16188       case MachinePlaysBlack:
16189         if (!WhiteOnMove(forwardMostMove)) {
16190             DisplayError(_("Wait until your turn."), 0);
16191             return;
16192         }
16193         break;
16194       default:
16195         DisplayError(_("No hint available"), 0);
16196         return;
16197     }
16198     SendToProgram("hint\n", &first);
16199     hintRequested = TRUE;
16200 }
16201
16202 int
16203 SaveSelected (FILE *g, int dummy, char *dummy2)
16204 {
16205     ListGame * lg = (ListGame *) gameList.head;
16206     int nItem, cnt=0;
16207     FILE *f;
16208
16209     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16210         DisplayError(_("Game list not loaded or empty"), 0);
16211         return 0;
16212     }
16213
16214     creatingBook = TRUE; // suppresses stuff during load game
16215
16216     /* Get list size */
16217     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16218         if(lg->position >= 0) { // selected?
16219             LoadGame(f, nItem, "", TRUE);
16220             SaveGamePGN2(g); // leaves g open
16221             cnt++; DoEvents();
16222         }
16223         lg = (ListGame *) lg->node.succ;
16224     }
16225
16226     fclose(g);
16227     creatingBook = FALSE;
16228
16229     return cnt;
16230 }
16231
16232 void
16233 CreateBookEvent ()
16234 {
16235     ListGame * lg = (ListGame *) gameList.head;
16236     FILE *f, *g;
16237     int nItem;
16238     static int secondTime = FALSE;
16239
16240     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16241         DisplayError(_("Game list not loaded or empty"), 0);
16242         return;
16243     }
16244
16245     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16246         fclose(g);
16247         secondTime++;
16248         DisplayNote(_("Book file exists! Try again for overwrite."));
16249         return;
16250     }
16251
16252     creatingBook = TRUE;
16253     secondTime = FALSE;
16254
16255     /* Get list size */
16256     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16257         if(lg->position >= 0) {
16258             LoadGame(f, nItem, "", TRUE);
16259             AddGameToBook(TRUE);
16260             DoEvents();
16261         }
16262         lg = (ListGame *) lg->node.succ;
16263     }
16264
16265     creatingBook = FALSE;
16266     FlushBook();
16267 }
16268
16269 void
16270 BookEvent ()
16271 {
16272     if (appData.noChessProgram) return;
16273     switch (gameMode) {
16274       case MachinePlaysWhite:
16275         if (WhiteOnMove(forwardMostMove)) {
16276             DisplayError(_("Wait until your turn."), 0);
16277             return;
16278         }
16279         break;
16280       case BeginningOfGame:
16281       case MachinePlaysBlack:
16282         if (!WhiteOnMove(forwardMostMove)) {
16283             DisplayError(_("Wait until your turn."), 0);
16284             return;
16285         }
16286         break;
16287       case EditPosition:
16288         EditPositionDone(TRUE);
16289         break;
16290       case TwoMachinesPlay:
16291         return;
16292       default:
16293         break;
16294     }
16295     SendToProgram("bk\n", &first);
16296     bookOutput[0] = NULLCHAR;
16297     bookRequested = TRUE;
16298 }
16299
16300 void
16301 AboutGameEvent ()
16302 {
16303     char *tags = PGNTags(&gameInfo);
16304     TagsPopUp(tags, CmailMsg());
16305     free(tags);
16306 }
16307
16308 /* end button procedures */
16309
16310 void
16311 PrintPosition (FILE *fp, int move)
16312 {
16313     int i, j;
16314
16315     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16316         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16317             char c = PieceToChar(boards[move][i][j]);
16318             fputc(c == '?' ? '.' : c, fp);
16319             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16320         }
16321     }
16322     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16323       fprintf(fp, "white to play\n");
16324     else
16325       fprintf(fp, "black to play\n");
16326 }
16327
16328 void
16329 PrintOpponents (FILE *fp)
16330 {
16331     if (gameInfo.white != NULL) {
16332         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16333     } else {
16334         fprintf(fp, "\n");
16335     }
16336 }
16337
16338 /* Find last component of program's own name, using some heuristics */
16339 void
16340 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16341 {
16342     char *p, *q, c;
16343     int local = (strcmp(host, "localhost") == 0);
16344     while (!local && (p = strchr(prog, ';')) != NULL) {
16345         p++;
16346         while (*p == ' ') p++;
16347         prog = p;
16348     }
16349     if (*prog == '"' || *prog == '\'') {
16350         q = strchr(prog + 1, *prog);
16351     } else {
16352         q = strchr(prog, ' ');
16353     }
16354     if (q == NULL) q = prog + strlen(prog);
16355     p = q;
16356     while (p >= prog && *p != '/' && *p != '\\') p--;
16357     p++;
16358     if(p == prog && *p == '"') p++;
16359     c = *q; *q = 0;
16360     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16361     memcpy(buf, p, q - p);
16362     buf[q - p] = NULLCHAR;
16363     if (!local) {
16364         strcat(buf, "@");
16365         strcat(buf, host);
16366     }
16367 }
16368
16369 char *
16370 TimeControlTagValue ()
16371 {
16372     char buf[MSG_SIZ];
16373     if (!appData.clockMode) {
16374       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16375     } else if (movesPerSession > 0) {
16376       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16377     } else if (timeIncrement == 0) {
16378       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16379     } else {
16380       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16381     }
16382     return StrSave(buf);
16383 }
16384
16385 void
16386 SetGameInfo ()
16387 {
16388     /* This routine is used only for certain modes */
16389     VariantClass v = gameInfo.variant;
16390     ChessMove r = GameUnfinished;
16391     char *p = NULL;
16392
16393     if(keepInfo) return;
16394
16395     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16396         r = gameInfo.result;
16397         p = gameInfo.resultDetails;
16398         gameInfo.resultDetails = NULL;
16399     }
16400     ClearGameInfo(&gameInfo);
16401     gameInfo.variant = v;
16402
16403     switch (gameMode) {
16404       case MachinePlaysWhite:
16405         gameInfo.event = StrSave( appData.pgnEventHeader );
16406         gameInfo.site = StrSave(HostName());
16407         gameInfo.date = PGNDate();
16408         gameInfo.round = StrSave("-");
16409         gameInfo.white = StrSave(first.tidy);
16410         gameInfo.black = StrSave(UserName());
16411         gameInfo.timeControl = TimeControlTagValue();
16412         break;
16413
16414       case MachinePlaysBlack:
16415         gameInfo.event = StrSave( appData.pgnEventHeader );
16416         gameInfo.site = StrSave(HostName());
16417         gameInfo.date = PGNDate();
16418         gameInfo.round = StrSave("-");
16419         gameInfo.white = StrSave(UserName());
16420         gameInfo.black = StrSave(first.tidy);
16421         gameInfo.timeControl = TimeControlTagValue();
16422         break;
16423
16424       case TwoMachinesPlay:
16425         gameInfo.event = StrSave( appData.pgnEventHeader );
16426         gameInfo.site = StrSave(HostName());
16427         gameInfo.date = PGNDate();
16428         if (roundNr > 0) {
16429             char buf[MSG_SIZ];
16430             snprintf(buf, MSG_SIZ, "%d", roundNr);
16431             gameInfo.round = StrSave(buf);
16432         } else {
16433             gameInfo.round = StrSave("-");
16434         }
16435         if (first.twoMachinesColor[0] == 'w') {
16436             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16437             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16438         } else {
16439             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16440             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16441         }
16442         gameInfo.timeControl = TimeControlTagValue();
16443         break;
16444
16445       case EditGame:
16446         gameInfo.event = StrSave("Edited game");
16447         gameInfo.site = StrSave(HostName());
16448         gameInfo.date = PGNDate();
16449         gameInfo.round = StrSave("-");
16450         gameInfo.white = StrSave("-");
16451         gameInfo.black = StrSave("-");
16452         gameInfo.result = r;
16453         gameInfo.resultDetails = p;
16454         break;
16455
16456       case EditPosition:
16457         gameInfo.event = StrSave("Edited position");
16458         gameInfo.site = StrSave(HostName());
16459         gameInfo.date = PGNDate();
16460         gameInfo.round = StrSave("-");
16461         gameInfo.white = StrSave("-");
16462         gameInfo.black = StrSave("-");
16463         break;
16464
16465       case IcsPlayingWhite:
16466       case IcsPlayingBlack:
16467       case IcsObserving:
16468       case IcsExamining:
16469         break;
16470
16471       case PlayFromGameFile:
16472         gameInfo.event = StrSave("Game from non-PGN file");
16473         gameInfo.site = StrSave(HostName());
16474         gameInfo.date = PGNDate();
16475         gameInfo.round = StrSave("-");
16476         gameInfo.white = StrSave("?");
16477         gameInfo.black = StrSave("?");
16478         break;
16479
16480       default:
16481         break;
16482     }
16483 }
16484
16485 void
16486 ReplaceComment (int index, char *text)
16487 {
16488     int len;
16489     char *p;
16490     float score;
16491
16492     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16493        pvInfoList[index-1].depth == len &&
16494        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16495        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16496     while (*text == '\n') text++;
16497     len = strlen(text);
16498     while (len > 0 && text[len - 1] == '\n') len--;
16499
16500     if (commentList[index] != NULL)
16501       free(commentList[index]);
16502
16503     if (len == 0) {
16504         commentList[index] = NULL;
16505         return;
16506     }
16507   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16508       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16509       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16510     commentList[index] = (char *) malloc(len + 2);
16511     strncpy(commentList[index], text, len);
16512     commentList[index][len] = '\n';
16513     commentList[index][len + 1] = NULLCHAR;
16514   } else {
16515     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16516     char *p;
16517     commentList[index] = (char *) malloc(len + 7);
16518     safeStrCpy(commentList[index], "{\n", 3);
16519     safeStrCpy(commentList[index]+2, text, len+1);
16520     commentList[index][len+2] = NULLCHAR;
16521     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16522     strcat(commentList[index], "\n}\n");
16523   }
16524 }
16525
16526 void
16527 CrushCRs (char *text)
16528 {
16529   char *p = text;
16530   char *q = text;
16531   char ch;
16532
16533   do {
16534     ch = *p++;
16535     if (ch == '\r') continue;
16536     *q++ = ch;
16537   } while (ch != '\0');
16538 }
16539
16540 void
16541 AppendComment (int index, char *text, Boolean addBraces)
16542 /* addBraces  tells if we should add {} */
16543 {
16544     int oldlen, len;
16545     char *old;
16546
16547 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16548     if(addBraces == 3) addBraces = 0; else // force appending literally
16549     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16550
16551     CrushCRs(text);
16552     while (*text == '\n') text++;
16553     len = strlen(text);
16554     while (len > 0 && text[len - 1] == '\n') len--;
16555     text[len] = NULLCHAR;
16556
16557     if (len == 0) return;
16558
16559     if (commentList[index] != NULL) {
16560       Boolean addClosingBrace = addBraces;
16561         old = commentList[index];
16562         oldlen = strlen(old);
16563         while(commentList[index][oldlen-1] ==  '\n')
16564           commentList[index][--oldlen] = NULLCHAR;
16565         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16566         safeStrCpy(commentList[index], old, oldlen + len + 6);
16567         free(old);
16568         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16569         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16570           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16571           while (*text == '\n') { text++; len--; }
16572           commentList[index][--oldlen] = NULLCHAR;
16573       }
16574         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16575         else          strcat(commentList[index], "\n");
16576         strcat(commentList[index], text);
16577         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16578         else          strcat(commentList[index], "\n");
16579     } else {
16580         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16581         if(addBraces)
16582           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16583         else commentList[index][0] = NULLCHAR;
16584         strcat(commentList[index], text);
16585         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16586         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16587     }
16588 }
16589
16590 static char *
16591 FindStr (char * text, char * sub_text)
16592 {
16593     char * result = strstr( text, sub_text );
16594
16595     if( result != NULL ) {
16596         result += strlen( sub_text );
16597     }
16598
16599     return result;
16600 }
16601
16602 /* [AS] Try to extract PV info from PGN comment */
16603 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16604 char *
16605 GetInfoFromComment (int index, char * text)
16606 {
16607     char * sep = text, *p;
16608
16609     if( text != NULL && index > 0 ) {
16610         int score = 0;
16611         int depth = 0;
16612         int time = -1, sec = 0, deci;
16613         char * s_eval = FindStr( text, "[%eval " );
16614         char * s_emt = FindStr( text, "[%emt " );
16615 #if 0
16616         if( s_eval != NULL || s_emt != NULL ) {
16617 #else
16618         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16619 #endif
16620             /* New style */
16621             char delim;
16622
16623             if( s_eval != NULL ) {
16624                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16625                     return text;
16626                 }
16627
16628                 if( delim != ']' ) {
16629                     return text;
16630                 }
16631             }
16632
16633             if( s_emt != NULL ) {
16634             }
16635                 return text;
16636         }
16637         else {
16638             /* We expect something like: [+|-]nnn.nn/dd */
16639             int score_lo = 0;
16640
16641             if(*text != '{') return text; // [HGM] braces: must be normal comment
16642
16643             sep = strchr( text, '/' );
16644             if( sep == NULL || sep < (text+4) ) {
16645                 return text;
16646             }
16647
16648             p = text;
16649             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16650             if(p[1] == '(') { // comment starts with PV
16651                p = strchr(p, ')'); // locate end of PV
16652                if(p == NULL || sep < p+5) return text;
16653                // at this point we have something like "{(.*) +0.23/6 ..."
16654                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16655                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16656                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16657             }
16658             time = -1; sec = -1; deci = -1;
16659             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16660                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16661                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16662                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16663                 return text;
16664             }
16665
16666             if( score_lo < 0 || score_lo >= 100 ) {
16667                 return text;
16668             }
16669
16670             if(sec >= 0) time = 600*time + 10*sec; else
16671             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16672
16673             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16674
16675             /* [HGM] PV time: now locate end of PV info */
16676             while( *++sep >= '0' && *sep <= '9'); // strip depth
16677             if(time >= 0)
16678             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16679             if(sec >= 0)
16680             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16681             if(deci >= 0)
16682             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16683             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16684         }
16685
16686         if( depth <= 0 ) {
16687             return text;
16688         }
16689
16690         if( time < 0 ) {
16691             time = -1;
16692         }
16693
16694         pvInfoList[index-1].depth = depth;
16695         pvInfoList[index-1].score = score;
16696         pvInfoList[index-1].time  = 10*time; // centi-sec
16697         if(*sep == '}') *sep = 0; else *--sep = '{';
16698         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16699     }
16700     return sep;
16701 }
16702
16703 void
16704 SendToProgram (char *message, ChessProgramState *cps)
16705 {
16706     int count, outCount, error;
16707     char buf[MSG_SIZ];
16708
16709     if (cps->pr == NoProc) return;
16710     Attention(cps);
16711
16712     if (appData.debugMode) {
16713         TimeMark now;
16714         GetTimeMark(&now);
16715         fprintf(debugFP, "%ld >%-6s: %s",
16716                 SubtractTimeMarks(&now, &programStartTime),
16717                 cps->which, message);
16718         if(serverFP)
16719             fprintf(serverFP, "%ld >%-6s: %s",
16720                 SubtractTimeMarks(&now, &programStartTime),
16721                 cps->which, message), fflush(serverFP);
16722     }
16723
16724     count = strlen(message);
16725     outCount = OutputToProcess(cps->pr, message, count, &error);
16726     if (outCount < count && !exiting
16727                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16728       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16729       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16730         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16731             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16732                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16733                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16734                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16735             } else {
16736                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16737                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16738                 gameInfo.result = res;
16739             }
16740             gameInfo.resultDetails = StrSave(buf);
16741         }
16742         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16743         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16744     }
16745 }
16746
16747 void
16748 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16749 {
16750     char *end_str;
16751     char buf[MSG_SIZ];
16752     ChessProgramState *cps = (ChessProgramState *)closure;
16753
16754     if (isr != cps->isr) return; /* Killed intentionally */
16755     if (count <= 0) {
16756         if (count == 0) {
16757             RemoveInputSource(cps->isr);
16758             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16759                     _(cps->which), cps->program);
16760             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16761             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16762                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16763                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16764                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16765                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16766                 } else {
16767                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16768                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16769                     gameInfo.result = res;
16770                 }
16771                 gameInfo.resultDetails = StrSave(buf);
16772             }
16773             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16774             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16775         } else {
16776             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16777                     _(cps->which), cps->program);
16778             RemoveInputSource(cps->isr);
16779
16780             /* [AS] Program is misbehaving badly... kill it */
16781             if( count == -2 ) {
16782                 DestroyChildProcess( cps->pr, 9 );
16783                 cps->pr = NoProc;
16784             }
16785
16786             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16787         }
16788         return;
16789     }
16790
16791     if ((end_str = strchr(message, '\r')) != NULL)
16792       *end_str = NULLCHAR;
16793     if ((end_str = strchr(message, '\n')) != NULL)
16794       *end_str = NULLCHAR;
16795
16796     if (appData.debugMode) {
16797         TimeMark now; int print = 1;
16798         char *quote = ""; char c; int i;
16799
16800         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16801                 char start = message[0];
16802                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16803                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16804                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16805                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16806                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16807                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16808                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16809                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16810                    sscanf(message, "hint: %c", &c)!=1 &&
16811                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16812                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16813                     print = (appData.engineComments >= 2);
16814                 }
16815                 message[0] = start; // restore original message
16816         }
16817         if(print) {
16818                 GetTimeMark(&now);
16819                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16820                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16821                         quote,
16822                         message);
16823                 if(serverFP)
16824                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16825                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16826                         quote,
16827                         message), fflush(serverFP);
16828         }
16829     }
16830
16831     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16832     if (appData.icsEngineAnalyze) {
16833         if (strstr(message, "whisper") != NULL ||
16834              strstr(message, "kibitz") != NULL ||
16835             strstr(message, "tellics") != NULL) return;
16836     }
16837
16838     HandleMachineMove(message, cps);
16839 }
16840
16841
16842 void
16843 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16844 {
16845     char buf[MSG_SIZ];
16846     int seconds;
16847
16848     if( timeControl_2 > 0 ) {
16849         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16850             tc = timeControl_2;
16851         }
16852     }
16853     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16854     inc /= cps->timeOdds;
16855     st  /= cps->timeOdds;
16856
16857     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16858
16859     if (st > 0) {
16860       /* Set exact time per move, normally using st command */
16861       if (cps->stKludge) {
16862         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16863         seconds = st % 60;
16864         if (seconds == 0) {
16865           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16866         } else {
16867           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16868         }
16869       } else {
16870         snprintf(buf, MSG_SIZ, "st %d\n", st);
16871       }
16872     } else {
16873       /* Set conventional or incremental time control, using level command */
16874       if (seconds == 0) {
16875         /* Note old gnuchess bug -- minutes:seconds used to not work.
16876            Fixed in later versions, but still avoid :seconds
16877            when seconds is 0. */
16878         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16879       } else {
16880         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16881                  seconds, inc/1000.);
16882       }
16883     }
16884     SendToProgram(buf, cps);
16885
16886     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16887     /* Orthogonally, limit search to given depth */
16888     if (sd > 0) {
16889       if (cps->sdKludge) {
16890         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16891       } else {
16892         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16893       }
16894       SendToProgram(buf, cps);
16895     }
16896
16897     if(cps->nps >= 0) { /* [HGM] nps */
16898         if(cps->supportsNPS == FALSE)
16899           cps->nps = -1; // don't use if engine explicitly says not supported!
16900         else {
16901           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16902           SendToProgram(buf, cps);
16903         }
16904     }
16905 }
16906
16907 ChessProgramState *
16908 WhitePlayer ()
16909 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16910 {
16911     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16912        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16913         return &second;
16914     return &first;
16915 }
16916
16917 void
16918 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16919 {
16920     char message[MSG_SIZ];
16921     long time, otime;
16922
16923     /* Note: this routine must be called when the clocks are stopped
16924        or when they have *just* been set or switched; otherwise
16925        it will be off by the time since the current tick started.
16926     */
16927     if (machineWhite) {
16928         time = whiteTimeRemaining / 10;
16929         otime = blackTimeRemaining / 10;
16930     } else {
16931         time = blackTimeRemaining / 10;
16932         otime = whiteTimeRemaining / 10;
16933     }
16934     /* [HGM] translate opponent's time by time-odds factor */
16935     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16936
16937     if (time <= 0) time = 1;
16938     if (otime <= 0) otime = 1;
16939
16940     snprintf(message, MSG_SIZ, "time %ld\n", time);
16941     SendToProgram(message, cps);
16942
16943     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16944     SendToProgram(message, cps);
16945 }
16946
16947 char *
16948 EngineDefinedVariant (ChessProgramState *cps, int n)
16949 {   // return name of n-th unknown variant that engine supports
16950     static char buf[MSG_SIZ];
16951     char *p, *s = cps->variants;
16952     if(!s) return NULL;
16953     do { // parse string from variants feature
16954       VariantClass v;
16955         p = strchr(s, ',');
16956         if(p) *p = NULLCHAR;
16957       v = StringToVariant(s);
16958       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16959         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16960             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16961                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16962                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16963                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16964             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16965         }
16966         if(p) *p++ = ',';
16967         if(n < 0) return buf;
16968     } while(s = p);
16969     return NULL;
16970 }
16971
16972 int
16973 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16974 {
16975   char buf[MSG_SIZ];
16976   int len = strlen(name);
16977   int val;
16978
16979   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16980     (*p) += len + 1;
16981     sscanf(*p, "%d", &val);
16982     *loc = (val != 0);
16983     while (**p && **p != ' ')
16984       (*p)++;
16985     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16986     SendToProgram(buf, cps);
16987     return TRUE;
16988   }
16989   return FALSE;
16990 }
16991
16992 int
16993 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16994 {
16995   char buf[MSG_SIZ];
16996   int len = strlen(name);
16997   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16998     (*p) += len + 1;
16999     sscanf(*p, "%d", loc);
17000     while (**p && **p != ' ') (*p)++;
17001     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17002     SendToProgram(buf, cps);
17003     return TRUE;
17004   }
17005   return FALSE;
17006 }
17007
17008 int
17009 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17010 {
17011   char buf[MSG_SIZ];
17012   int len = strlen(name);
17013   if (strncmp((*p), name, len) == 0
17014       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17015     (*p) += len + 2;
17016     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17017     sscanf(*p, "%[^\"]", *loc);
17018     while (**p && **p != '\"') (*p)++;
17019     if (**p == '\"') (*p)++;
17020     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17021     SendToProgram(buf, cps);
17022     return TRUE;
17023   }
17024   return FALSE;
17025 }
17026
17027 int
17028 ParseOption (Option *opt, ChessProgramState *cps)
17029 // [HGM] options: process the string that defines an engine option, and determine
17030 // name, type, default value, and allowed value range
17031 {
17032         char *p, *q, buf[MSG_SIZ];
17033         int n, min = (-1)<<31, max = 1<<31, def;
17034
17035         opt->target = &opt->value;   // OK for spin/slider and checkbox
17036         if(p = strstr(opt->name, " -spin ")) {
17037             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17038             if(max < min) max = min; // enforce consistency
17039             if(def < min) def = min;
17040             if(def > max) def = max;
17041             opt->value = def;
17042             opt->min = min;
17043             opt->max = max;
17044             opt->type = Spin;
17045         } else if((p = strstr(opt->name, " -slider "))) {
17046             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17047             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17048             if(max < min) max = min; // enforce consistency
17049             if(def < min) def = min;
17050             if(def > max) def = max;
17051             opt->value = def;
17052             opt->min = min;
17053             opt->max = max;
17054             opt->type = Spin; // Slider;
17055         } else if((p = strstr(opt->name, " -string "))) {
17056             opt->textValue = p+9;
17057             opt->type = TextBox;
17058             opt->target = &opt->textValue;
17059         } else if((p = strstr(opt->name, " -file "))) {
17060             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17061             opt->target = opt->textValue = p+7;
17062             opt->type = FileName; // FileName;
17063             opt->target = &opt->textValue;
17064         } else if((p = strstr(opt->name, " -path "))) {
17065             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17066             opt->target = opt->textValue = p+7;
17067             opt->type = PathName; // PathName;
17068             opt->target = &opt->textValue;
17069         } else if(p = strstr(opt->name, " -check ")) {
17070             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17071             opt->value = (def != 0);
17072             opt->type = CheckBox;
17073         } else if(p = strstr(opt->name, " -combo ")) {
17074             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17075             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17076             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17077             opt->value = n = 0;
17078             while(q = StrStr(q, " /// ")) {
17079                 n++; *q = 0;    // count choices, and null-terminate each of them
17080                 q += 5;
17081                 if(*q == '*') { // remember default, which is marked with * prefix
17082                     q++;
17083                     opt->value = n;
17084                 }
17085                 cps->comboList[cps->comboCnt++] = q;
17086             }
17087             cps->comboList[cps->comboCnt++] = NULL;
17088             opt->max = n + 1;
17089             opt->type = ComboBox;
17090         } else if(p = strstr(opt->name, " -button")) {
17091             opt->type = Button;
17092         } else if(p = strstr(opt->name, " -save")) {
17093             opt->type = SaveButton;
17094         } else return FALSE;
17095         *p = 0; // terminate option name
17096         // now look if the command-line options define a setting for this engine option.
17097         if(cps->optionSettings && cps->optionSettings[0])
17098             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17099         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17100           snprintf(buf, MSG_SIZ, "option %s", p);
17101                 if(p = strstr(buf, ",")) *p = 0;
17102                 if(q = strchr(buf, '=')) switch(opt->type) {
17103                     case ComboBox:
17104                         for(n=0; n<opt->max; n++)
17105                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17106                         break;
17107                     case TextBox:
17108                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17109                         break;
17110                     case Spin:
17111                     case CheckBox:
17112                         opt->value = atoi(q+1);
17113                     default:
17114                         break;
17115                 }
17116                 strcat(buf, "\n");
17117                 SendToProgram(buf, cps);
17118         }
17119         return TRUE;
17120 }
17121
17122 void
17123 FeatureDone (ChessProgramState *cps, int val)
17124 {
17125   DelayedEventCallback cb = GetDelayedEvent();
17126   if ((cb == InitBackEnd3 && cps == &first) ||
17127       (cb == SettingsMenuIfReady && cps == &second) ||
17128       (cb == LoadEngine) ||
17129       (cb == TwoMachinesEventIfReady)) {
17130     CancelDelayedEvent();
17131     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17132   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17133   cps->initDone = val;
17134   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17135 }
17136
17137 /* Parse feature command from engine */
17138 void
17139 ParseFeatures (char *args, ChessProgramState *cps)
17140 {
17141   char *p = args;
17142   char *q = NULL;
17143   int val;
17144   char buf[MSG_SIZ];
17145
17146   for (;;) {
17147     while (*p == ' ') p++;
17148     if (*p == NULLCHAR) return;
17149
17150     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17151     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17152     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17153     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17154     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17155     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17156     if (BoolFeature(&p, "reuse", &val, cps)) {
17157       /* Engine can disable reuse, but can't enable it if user said no */
17158       if (!val) cps->reuse = FALSE;
17159       continue;
17160     }
17161     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17162     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17163       if (gameMode == TwoMachinesPlay) {
17164         DisplayTwoMachinesTitle();
17165       } else {
17166         DisplayTitle("");
17167       }
17168       continue;
17169     }
17170     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17171     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17172     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17173     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17174     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17175     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17176     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17177     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17178     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17179     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17180     if (IntFeature(&p, "done", &val, cps)) {
17181       FeatureDone(cps, val);
17182       continue;
17183     }
17184     /* Added by Tord: */
17185     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17186     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17187     /* End of additions by Tord */
17188
17189     /* [HGM] added features: */
17190     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17191     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17192     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17193     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17194     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17195     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17196     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17197     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17198         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17199         FREE(cps->option[cps->nrOptions].name);
17200         cps->option[cps->nrOptions].name = q; q = NULL;
17201         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17202           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17203             SendToProgram(buf, cps);
17204             continue;
17205         }
17206         if(cps->nrOptions >= MAX_OPTIONS) {
17207             cps->nrOptions--;
17208             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17209             DisplayError(buf, 0);
17210         }
17211         continue;
17212     }
17213     /* End of additions by HGM */
17214
17215     /* unknown feature: complain and skip */
17216     q = p;
17217     while (*q && *q != '=') q++;
17218     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17219     SendToProgram(buf, cps);
17220     p = q;
17221     if (*p == '=') {
17222       p++;
17223       if (*p == '\"') {
17224         p++;
17225         while (*p && *p != '\"') p++;
17226         if (*p == '\"') p++;
17227       } else {
17228         while (*p && *p != ' ') p++;
17229       }
17230     }
17231   }
17232
17233 }
17234
17235 void
17236 PeriodicUpdatesEvent (int newState)
17237 {
17238     if (newState == appData.periodicUpdates)
17239       return;
17240
17241     appData.periodicUpdates=newState;
17242
17243     /* Display type changes, so update it now */
17244 //    DisplayAnalysis();
17245
17246     /* Get the ball rolling again... */
17247     if (newState) {
17248         AnalysisPeriodicEvent(1);
17249         StartAnalysisClock();
17250     }
17251 }
17252
17253 void
17254 PonderNextMoveEvent (int newState)
17255 {
17256     if (newState == appData.ponderNextMove) return;
17257     if (gameMode == EditPosition) EditPositionDone(TRUE);
17258     if (newState) {
17259         SendToProgram("hard\n", &first);
17260         if (gameMode == TwoMachinesPlay) {
17261             SendToProgram("hard\n", &second);
17262         }
17263     } else {
17264         SendToProgram("easy\n", &first);
17265         thinkOutput[0] = NULLCHAR;
17266         if (gameMode == TwoMachinesPlay) {
17267             SendToProgram("easy\n", &second);
17268         }
17269     }
17270     appData.ponderNextMove = newState;
17271 }
17272
17273 void
17274 NewSettingEvent (int option, int *feature, char *command, int value)
17275 {
17276     char buf[MSG_SIZ];
17277
17278     if (gameMode == EditPosition) EditPositionDone(TRUE);
17279     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17280     if(feature == NULL || *feature) SendToProgram(buf, &first);
17281     if (gameMode == TwoMachinesPlay) {
17282         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17283     }
17284 }
17285
17286 void
17287 ShowThinkingEvent ()
17288 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17289 {
17290     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17291     int newState = appData.showThinking
17292         // [HGM] thinking: other features now need thinking output as well
17293         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17294
17295     if (oldState == newState) return;
17296     oldState = newState;
17297     if (gameMode == EditPosition) EditPositionDone(TRUE);
17298     if (oldState) {
17299         SendToProgram("post\n", &first);
17300         if (gameMode == TwoMachinesPlay) {
17301             SendToProgram("post\n", &second);
17302         }
17303     } else {
17304         SendToProgram("nopost\n", &first);
17305         thinkOutput[0] = NULLCHAR;
17306         if (gameMode == TwoMachinesPlay) {
17307             SendToProgram("nopost\n", &second);
17308         }
17309     }
17310 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17311 }
17312
17313 void
17314 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17315 {
17316   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17317   if (pr == NoProc) return;
17318   AskQuestion(title, question, replyPrefix, pr);
17319 }
17320
17321 void
17322 TypeInEvent (char firstChar)
17323 {
17324     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17325         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17326         gameMode == AnalyzeMode || gameMode == EditGame ||
17327         gameMode == EditPosition || gameMode == IcsExamining ||
17328         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17329         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17330                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17331                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17332         gameMode == Training) PopUpMoveDialog(firstChar);
17333 }
17334
17335 void
17336 TypeInDoneEvent (char *move)
17337 {
17338         Board board;
17339         int n, fromX, fromY, toX, toY;
17340         char promoChar;
17341         ChessMove moveType;
17342
17343         // [HGM] FENedit
17344         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17345                 EditPositionPasteFEN(move);
17346                 return;
17347         }
17348         // [HGM] movenum: allow move number to be typed in any mode
17349         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17350           ToNrEvent(2*n-1);
17351           return;
17352         }
17353         // undocumented kludge: allow command-line option to be typed in!
17354         // (potentially fatal, and does not implement the effect of the option.)
17355         // should only be used for options that are values on which future decisions will be made,
17356         // and definitely not on options that would be used during initialization.
17357         if(strstr(move, "!!! -") == move) {
17358             ParseArgsFromString(move+4);
17359             return;
17360         }
17361
17362       if (gameMode != EditGame && currentMove != forwardMostMove &&
17363         gameMode != Training) {
17364         DisplayMoveError(_("Displayed move is not current"));
17365       } else {
17366         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17367           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17368         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17369         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17370           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17371           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17372         } else {
17373           DisplayMoveError(_("Could not parse move"));
17374         }
17375       }
17376 }
17377
17378 void
17379 DisplayMove (int moveNumber)
17380 {
17381     char message[MSG_SIZ];
17382     char res[MSG_SIZ];
17383     char cpThinkOutput[MSG_SIZ];
17384
17385     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17386
17387     if (moveNumber == forwardMostMove - 1 ||
17388         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17389
17390         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17391
17392         if (strchr(cpThinkOutput, '\n')) {
17393             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17394         }
17395     } else {
17396         *cpThinkOutput = NULLCHAR;
17397     }
17398
17399     /* [AS] Hide thinking from human user */
17400     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17401         *cpThinkOutput = NULLCHAR;
17402         if( thinkOutput[0] != NULLCHAR ) {
17403             int i;
17404
17405             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17406                 cpThinkOutput[i] = '.';
17407             }
17408             cpThinkOutput[i] = NULLCHAR;
17409             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17410         }
17411     }
17412
17413     if (moveNumber == forwardMostMove - 1 &&
17414         gameInfo.resultDetails != NULL) {
17415         if (gameInfo.resultDetails[0] == NULLCHAR) {
17416           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17417         } else {
17418           snprintf(res, MSG_SIZ, " {%s} %s",
17419                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17420         }
17421     } else {
17422         res[0] = NULLCHAR;
17423     }
17424
17425     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17426         DisplayMessage(res, cpThinkOutput);
17427     } else {
17428       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17429                 WhiteOnMove(moveNumber) ? " " : ".. ",
17430                 parseList[moveNumber], res);
17431         DisplayMessage(message, cpThinkOutput);
17432     }
17433 }
17434
17435 void
17436 DisplayComment (int moveNumber, char *text)
17437 {
17438     char title[MSG_SIZ];
17439
17440     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17441       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17442     } else {
17443       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17444               WhiteOnMove(moveNumber) ? " " : ".. ",
17445               parseList[moveNumber]);
17446     }
17447     if (text != NULL && (appData.autoDisplayComment || commentUp))
17448         CommentPopUp(title, text);
17449 }
17450
17451 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17452  * might be busy thinking or pondering.  It can be omitted if your
17453  * gnuchess is configured to stop thinking immediately on any user
17454  * input.  However, that gnuchess feature depends on the FIONREAD
17455  * ioctl, which does not work properly on some flavors of Unix.
17456  */
17457 void
17458 Attention (ChessProgramState *cps)
17459 {
17460 #if ATTENTION
17461     if (!cps->useSigint) return;
17462     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17463     switch (gameMode) {
17464       case MachinePlaysWhite:
17465       case MachinePlaysBlack:
17466       case TwoMachinesPlay:
17467       case IcsPlayingWhite:
17468       case IcsPlayingBlack:
17469       case AnalyzeMode:
17470       case AnalyzeFile:
17471         /* Skip if we know it isn't thinking */
17472         if (!cps->maybeThinking) return;
17473         if (appData.debugMode)
17474           fprintf(debugFP, "Interrupting %s\n", cps->which);
17475         InterruptChildProcess(cps->pr);
17476         cps->maybeThinking = FALSE;
17477         break;
17478       default:
17479         break;
17480     }
17481 #endif /*ATTENTION*/
17482 }
17483
17484 int
17485 CheckFlags ()
17486 {
17487     if (whiteTimeRemaining <= 0) {
17488         if (!whiteFlag) {
17489             whiteFlag = TRUE;
17490             if (appData.icsActive) {
17491                 if (appData.autoCallFlag &&
17492                     gameMode == IcsPlayingBlack && !blackFlag) {
17493                   SendToICS(ics_prefix);
17494                   SendToICS("flag\n");
17495                 }
17496             } else {
17497                 if (blackFlag) {
17498                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17499                 } else {
17500                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17501                     if (appData.autoCallFlag) {
17502                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17503                         return TRUE;
17504                     }
17505                 }
17506             }
17507         }
17508     }
17509     if (blackTimeRemaining <= 0) {
17510         if (!blackFlag) {
17511             blackFlag = TRUE;
17512             if (appData.icsActive) {
17513                 if (appData.autoCallFlag &&
17514                     gameMode == IcsPlayingWhite && !whiteFlag) {
17515                   SendToICS(ics_prefix);
17516                   SendToICS("flag\n");
17517                 }
17518             } else {
17519                 if (whiteFlag) {
17520                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17521                 } else {
17522                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17523                     if (appData.autoCallFlag) {
17524                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17525                         return TRUE;
17526                     }
17527                 }
17528             }
17529         }
17530     }
17531     return FALSE;
17532 }
17533
17534 void
17535 CheckTimeControl ()
17536 {
17537     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17538         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17539
17540     /*
17541      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17542      */
17543     if ( !WhiteOnMove(forwardMostMove) ) {
17544         /* White made time control */
17545         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17546         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17547         /* [HGM] time odds: correct new time quota for time odds! */
17548                                             / WhitePlayer()->timeOdds;
17549         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17550     } else {
17551         lastBlack -= blackTimeRemaining;
17552         /* Black made time control */
17553         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17554                                             / WhitePlayer()->other->timeOdds;
17555         lastWhite = whiteTimeRemaining;
17556     }
17557 }
17558
17559 void
17560 DisplayBothClocks ()
17561 {
17562     int wom = gameMode == EditPosition ?
17563       !blackPlaysFirst : WhiteOnMove(currentMove);
17564     DisplayWhiteClock(whiteTimeRemaining, wom);
17565     DisplayBlackClock(blackTimeRemaining, !wom);
17566 }
17567
17568
17569 /* Timekeeping seems to be a portability nightmare.  I think everyone
17570    has ftime(), but I'm really not sure, so I'm including some ifdefs
17571    to use other calls if you don't.  Clocks will be less accurate if
17572    you have neither ftime nor gettimeofday.
17573 */
17574
17575 /* VS 2008 requires the #include outside of the function */
17576 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17577 #include <sys/timeb.h>
17578 #endif
17579
17580 /* Get the current time as a TimeMark */
17581 void
17582 GetTimeMark (TimeMark *tm)
17583 {
17584 #if HAVE_GETTIMEOFDAY
17585
17586     struct timeval timeVal;
17587     struct timezone timeZone;
17588
17589     gettimeofday(&timeVal, &timeZone);
17590     tm->sec = (long) timeVal.tv_sec;
17591     tm->ms = (int) (timeVal.tv_usec / 1000L);
17592
17593 #else /*!HAVE_GETTIMEOFDAY*/
17594 #if HAVE_FTIME
17595
17596 // include <sys/timeb.h> / moved to just above start of function
17597     struct timeb timeB;
17598
17599     ftime(&timeB);
17600     tm->sec = (long) timeB.time;
17601     tm->ms = (int) timeB.millitm;
17602
17603 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17604     tm->sec = (long) time(NULL);
17605     tm->ms = 0;
17606 #endif
17607 #endif
17608 }
17609
17610 /* Return the difference in milliseconds between two
17611    time marks.  We assume the difference will fit in a long!
17612 */
17613 long
17614 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17615 {
17616     return 1000L*(tm2->sec - tm1->sec) +
17617            (long) (tm2->ms - tm1->ms);
17618 }
17619
17620
17621 /*
17622  * Code to manage the game clocks.
17623  *
17624  * In tournament play, black starts the clock and then white makes a move.
17625  * We give the human user a slight advantage if he is playing white---the
17626  * clocks don't run until he makes his first move, so it takes zero time.
17627  * Also, we don't account for network lag, so we could get out of sync
17628  * with GNU Chess's clock -- but then, referees are always right.
17629  */
17630
17631 static TimeMark tickStartTM;
17632 static long intendedTickLength;
17633
17634 long
17635 NextTickLength (long timeRemaining)
17636 {
17637     long nominalTickLength, nextTickLength;
17638
17639     if (timeRemaining > 0L && timeRemaining <= 10000L)
17640       nominalTickLength = 100L;
17641     else
17642       nominalTickLength = 1000L;
17643     nextTickLength = timeRemaining % nominalTickLength;
17644     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17645
17646     return nextTickLength;
17647 }
17648
17649 /* Adjust clock one minute up or down */
17650 void
17651 AdjustClock (Boolean which, int dir)
17652 {
17653     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17654     if(which) blackTimeRemaining += 60000*dir;
17655     else      whiteTimeRemaining += 60000*dir;
17656     DisplayBothClocks();
17657     adjustedClock = TRUE;
17658 }
17659
17660 /* Stop clocks and reset to a fresh time control */
17661 void
17662 ResetClocks ()
17663 {
17664     (void) StopClockTimer();
17665     if (appData.icsActive) {
17666         whiteTimeRemaining = blackTimeRemaining = 0;
17667     } else if (searchTime) {
17668         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17669         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17670     } else { /* [HGM] correct new time quote for time odds */
17671         whiteTC = blackTC = fullTimeControlString;
17672         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17673         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17674     }
17675     if (whiteFlag || blackFlag) {
17676         DisplayTitle("");
17677         whiteFlag = blackFlag = FALSE;
17678     }
17679     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17680     DisplayBothClocks();
17681     adjustedClock = FALSE;
17682 }
17683
17684 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17685
17686 /* Decrement running clock by amount of time that has passed */
17687 void
17688 DecrementClocks ()
17689 {
17690     long timeRemaining;
17691     long lastTickLength, fudge;
17692     TimeMark now;
17693
17694     if (!appData.clockMode) return;
17695     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17696
17697     GetTimeMark(&now);
17698
17699     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17700
17701     /* Fudge if we woke up a little too soon */
17702     fudge = intendedTickLength - lastTickLength;
17703     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17704
17705     if (WhiteOnMove(forwardMostMove)) {
17706         if(whiteNPS >= 0) lastTickLength = 0;
17707         timeRemaining = whiteTimeRemaining -= lastTickLength;
17708         if(timeRemaining < 0 && !appData.icsActive) {
17709             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17710             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17711                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17712                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17713             }
17714         }
17715         DisplayWhiteClock(whiteTimeRemaining - fudge,
17716                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17717     } else {
17718         if(blackNPS >= 0) lastTickLength = 0;
17719         timeRemaining = blackTimeRemaining -= lastTickLength;
17720         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17721             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17722             if(suddenDeath) {
17723                 blackStartMove = forwardMostMove;
17724                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17725             }
17726         }
17727         DisplayBlackClock(blackTimeRemaining - fudge,
17728                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17729     }
17730     if (CheckFlags()) return;
17731
17732     if(twoBoards) { // count down secondary board's clocks as well
17733         activePartnerTime -= lastTickLength;
17734         partnerUp = 1;
17735         if(activePartner == 'W')
17736             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17737         else
17738             DisplayBlackClock(activePartnerTime, TRUE);
17739         partnerUp = 0;
17740     }
17741
17742     tickStartTM = now;
17743     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17744     StartClockTimer(intendedTickLength);
17745
17746     /* if the time remaining has fallen below the alarm threshold, sound the
17747      * alarm. if the alarm has sounded and (due to a takeback or time control
17748      * with increment) the time remaining has increased to a level above the
17749      * threshold, reset the alarm so it can sound again.
17750      */
17751
17752     if (appData.icsActive && appData.icsAlarm) {
17753
17754         /* make sure we are dealing with the user's clock */
17755         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17756                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17757            )) return;
17758
17759         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17760             alarmSounded = FALSE;
17761         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17762             PlayAlarmSound();
17763             alarmSounded = TRUE;
17764         }
17765     }
17766 }
17767
17768
17769 /* A player has just moved, so stop the previously running
17770    clock and (if in clock mode) start the other one.
17771    We redisplay both clocks in case we're in ICS mode, because
17772    ICS gives us an update to both clocks after every move.
17773    Note that this routine is called *after* forwardMostMove
17774    is updated, so the last fractional tick must be subtracted
17775    from the color that is *not* on move now.
17776 */
17777 void
17778 SwitchClocks (int newMoveNr)
17779 {
17780     long lastTickLength;
17781     TimeMark now;
17782     int flagged = FALSE;
17783
17784     GetTimeMark(&now);
17785
17786     if (StopClockTimer() && appData.clockMode) {
17787         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17788         if (!WhiteOnMove(forwardMostMove)) {
17789             if(blackNPS >= 0) lastTickLength = 0;
17790             blackTimeRemaining -= lastTickLength;
17791            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17792 //         if(pvInfoList[forwardMostMove].time == -1)
17793                  pvInfoList[forwardMostMove].time =               // use GUI time
17794                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17795         } else {
17796            if(whiteNPS >= 0) lastTickLength = 0;
17797            whiteTimeRemaining -= lastTickLength;
17798            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17799 //         if(pvInfoList[forwardMostMove].time == -1)
17800                  pvInfoList[forwardMostMove].time =
17801                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17802         }
17803         flagged = CheckFlags();
17804     }
17805     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17806     CheckTimeControl();
17807
17808     if (flagged || !appData.clockMode) return;
17809
17810     switch (gameMode) {
17811       case MachinePlaysBlack:
17812       case MachinePlaysWhite:
17813       case BeginningOfGame:
17814         if (pausing) return;
17815         break;
17816
17817       case EditGame:
17818       case PlayFromGameFile:
17819       case IcsExamining:
17820         return;
17821
17822       default:
17823         break;
17824     }
17825
17826     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17827         if(WhiteOnMove(forwardMostMove))
17828              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17829         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17830     }
17831
17832     tickStartTM = now;
17833     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17834       whiteTimeRemaining : blackTimeRemaining);
17835     StartClockTimer(intendedTickLength);
17836 }
17837
17838
17839 /* Stop both clocks */
17840 void
17841 StopClocks ()
17842 {
17843     long lastTickLength;
17844     TimeMark now;
17845
17846     if (!StopClockTimer()) return;
17847     if (!appData.clockMode) return;
17848
17849     GetTimeMark(&now);
17850
17851     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17852     if (WhiteOnMove(forwardMostMove)) {
17853         if(whiteNPS >= 0) lastTickLength = 0;
17854         whiteTimeRemaining -= lastTickLength;
17855         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17856     } else {
17857         if(blackNPS >= 0) lastTickLength = 0;
17858         blackTimeRemaining -= lastTickLength;
17859         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17860     }
17861     CheckFlags();
17862 }
17863
17864 /* Start clock of player on move.  Time may have been reset, so
17865    if clock is already running, stop and restart it. */
17866 void
17867 StartClocks ()
17868 {
17869     (void) StopClockTimer(); /* in case it was running already */
17870     DisplayBothClocks();
17871     if (CheckFlags()) return;
17872
17873     if (!appData.clockMode) return;
17874     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17875
17876     GetTimeMark(&tickStartTM);
17877     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17878       whiteTimeRemaining : blackTimeRemaining);
17879
17880    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17881     whiteNPS = blackNPS = -1;
17882     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17883        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17884         whiteNPS = first.nps;
17885     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17886        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17887         blackNPS = first.nps;
17888     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17889         whiteNPS = second.nps;
17890     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17891         blackNPS = second.nps;
17892     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17893
17894     StartClockTimer(intendedTickLength);
17895 }
17896
17897 char *
17898 TimeString (long ms)
17899 {
17900     long second, minute, hour, day;
17901     char *sign = "";
17902     static char buf[32];
17903
17904     if (ms > 0 && ms <= 9900) {
17905       /* convert milliseconds to tenths, rounding up */
17906       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17907
17908       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17909       return buf;
17910     }
17911
17912     /* convert milliseconds to seconds, rounding up */
17913     /* use floating point to avoid strangeness of integer division
17914        with negative dividends on many machines */
17915     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17916
17917     if (second < 0) {
17918         sign = "-";
17919         second = -second;
17920     }
17921
17922     day = second / (60 * 60 * 24);
17923     second = second % (60 * 60 * 24);
17924     hour = second / (60 * 60);
17925     second = second % (60 * 60);
17926     minute = second / 60;
17927     second = second % 60;
17928
17929     if (day > 0)
17930       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17931               sign, day, hour, minute, second);
17932     else if (hour > 0)
17933       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17934     else
17935       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17936
17937     return buf;
17938 }
17939
17940
17941 /*
17942  * This is necessary because some C libraries aren't ANSI C compliant yet.
17943  */
17944 char *
17945 StrStr (char *string, char *match)
17946 {
17947     int i, length;
17948
17949     length = strlen(match);
17950
17951     for (i = strlen(string) - length; i >= 0; i--, string++)
17952       if (!strncmp(match, string, length))
17953         return string;
17954
17955     return NULL;
17956 }
17957
17958 char *
17959 StrCaseStr (char *string, char *match)
17960 {
17961     int i, j, length;
17962
17963     length = strlen(match);
17964
17965     for (i = strlen(string) - length; i >= 0; i--, string++) {
17966         for (j = 0; j < length; j++) {
17967             if (ToLower(match[j]) != ToLower(string[j]))
17968               break;
17969         }
17970         if (j == length) return string;
17971     }
17972
17973     return NULL;
17974 }
17975
17976 #ifndef _amigados
17977 int
17978 StrCaseCmp (char *s1, char *s2)
17979 {
17980     char c1, c2;
17981
17982     for (;;) {
17983         c1 = ToLower(*s1++);
17984         c2 = ToLower(*s2++);
17985         if (c1 > c2) return 1;
17986         if (c1 < c2) return -1;
17987         if (c1 == NULLCHAR) return 0;
17988     }
17989 }
17990
17991
17992 int
17993 ToLower (int c)
17994 {
17995     return isupper(c) ? tolower(c) : c;
17996 }
17997
17998
17999 int
18000 ToUpper (int c)
18001 {
18002     return islower(c) ? toupper(c) : c;
18003 }
18004 #endif /* !_amigados    */
18005
18006 char *
18007 StrSave (char *s)
18008 {
18009   char *ret;
18010
18011   if ((ret = (char *) malloc(strlen(s) + 1)))
18012     {
18013       safeStrCpy(ret, s, strlen(s)+1);
18014     }
18015   return ret;
18016 }
18017
18018 char *
18019 StrSavePtr (char *s, char **savePtr)
18020 {
18021     if (*savePtr) {
18022         free(*savePtr);
18023     }
18024     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18025       safeStrCpy(*savePtr, s, strlen(s)+1);
18026     }
18027     return(*savePtr);
18028 }
18029
18030 char *
18031 PGNDate ()
18032 {
18033     time_t clock;
18034     struct tm *tm;
18035     char buf[MSG_SIZ];
18036
18037     clock = time((time_t *)NULL);
18038     tm = localtime(&clock);
18039     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18040             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18041     return StrSave(buf);
18042 }
18043
18044
18045 char *
18046 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18047 {
18048     int i, j, fromX, fromY, toX, toY;
18049     int whiteToPlay, haveRights = nrCastlingRights;
18050     char buf[MSG_SIZ];
18051     char *p, *q;
18052     int emptycount;
18053     ChessSquare piece;
18054
18055     whiteToPlay = (gameMode == EditPosition) ?
18056       !blackPlaysFirst : (move % 2 == 0);
18057     p = buf;
18058
18059     /* Piece placement data */
18060     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18061         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18062         emptycount = 0;
18063         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18064             if (boards[move][i][j] == EmptySquare) {
18065                 emptycount++;
18066             } else { ChessSquare piece = boards[move][i][j];
18067                 if (emptycount > 0) {
18068                     if(emptycount<10) /* [HGM] can be >= 10 */
18069                         *p++ = '0' + emptycount;
18070                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18071                     emptycount = 0;
18072                 }
18073                 if(PieceToChar(piece) == '+') {
18074                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18075                     *p++ = '+';
18076                     piece = (ChessSquare)(CHUDEMOTED(piece));
18077                 }
18078                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18079                 if(*p = PieceSuffix(piece)) p++;
18080                 if(p[-1] == '~') {
18081                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18082                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18083                     *p++ = '~';
18084                 }
18085             }
18086         }
18087         if (emptycount > 0) {
18088             if(emptycount<10) /* [HGM] can be >= 10 */
18089                 *p++ = '0' + emptycount;
18090             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18091             emptycount = 0;
18092         }
18093         *p++ = '/';
18094     }
18095     *(p - 1) = ' ';
18096
18097     /* [HGM] print Crazyhouse or Shogi holdings */
18098     if( gameInfo.holdingsWidth ) {
18099         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18100         q = p;
18101         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18102             piece = boards[move][i][BOARD_WIDTH-1];
18103             if( piece != EmptySquare )
18104               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18105                   *p++ = PieceToChar(piece);
18106         }
18107         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18108             piece = boards[move][BOARD_HEIGHT-i-1][0];
18109             if( piece != EmptySquare )
18110               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18111                   *p++ = PieceToChar(piece);
18112         }
18113
18114         if( q == p ) *p++ = '-';
18115         *p++ = ']';
18116         *p++ = ' ';
18117     }
18118
18119     /* Active color */
18120     *p++ = whiteToPlay ? 'w' : 'b';
18121     *p++ = ' ';
18122
18123   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18124     haveRights = 0; q = p;
18125     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18126       piece = boards[move][0][i];
18127       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18128         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18129       }
18130     }
18131     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18132       piece = boards[move][BOARD_HEIGHT-1][i];
18133       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18134         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18135       }
18136     }
18137     if(p == q) *p++ = '-';
18138     *p++ = ' ';
18139   }
18140
18141   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18142     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18143   } else {
18144   if(haveRights) {
18145      int handW=0, handB=0;
18146      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18147         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18148         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18149      }
18150      q = p;
18151      if(appData.fischerCastling) {
18152         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18153            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18154                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18155         } else {
18156        /* [HGM] write directly from rights */
18157            if(boards[move][CASTLING][2] != NoRights &&
18158               boards[move][CASTLING][0] != NoRights   )
18159                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18160            if(boards[move][CASTLING][2] != NoRights &&
18161               boards[move][CASTLING][1] != NoRights   )
18162                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18163         }
18164         if(handB) {
18165            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18166                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18167         } else {
18168            if(boards[move][CASTLING][5] != NoRights &&
18169               boards[move][CASTLING][3] != NoRights   )
18170                 *p++ = boards[move][CASTLING][3] + AAA;
18171            if(boards[move][CASTLING][5] != NoRights &&
18172               boards[move][CASTLING][4] != NoRights   )
18173                 *p++ = boards[move][CASTLING][4] + AAA;
18174         }
18175      } else {
18176
18177         /* [HGM] write true castling rights */
18178         if( nrCastlingRights == 6 ) {
18179             int q, k=0;
18180             if(boards[move][CASTLING][0] != NoRights &&
18181                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18182             q = (boards[move][CASTLING][1] != NoRights &&
18183                  boards[move][CASTLING][2] != NoRights  );
18184             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18185                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18186                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18187                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18188             }
18189             if(q) *p++ = 'Q';
18190             k = 0;
18191             if(boards[move][CASTLING][3] != NoRights &&
18192                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18193             q = (boards[move][CASTLING][4] != NoRights &&
18194                  boards[move][CASTLING][5] != NoRights  );
18195             if(handB) {
18196                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18197                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18198                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18199             }
18200             if(q) *p++ = 'q';
18201         }
18202      }
18203      if (q == p) *p++ = '-'; /* No castling rights */
18204      *p++ = ' ';
18205   }
18206
18207   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18208      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18209      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18210     /* En passant target square */
18211     if (move > backwardMostMove) {
18212         fromX = moveList[move - 1][0] - AAA;
18213         fromY = moveList[move - 1][1] - ONE;
18214         toX = moveList[move - 1][2] - AAA;
18215         toY = moveList[move - 1][3] - ONE;
18216         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18217             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18218             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18219             fromX == toX) {
18220             /* 2-square pawn move just happened */
18221             *p++ = toX + AAA;
18222             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18223         } else {
18224             *p++ = '-';
18225         }
18226     } else if(move == backwardMostMove) {
18227         // [HGM] perhaps we should always do it like this, and forget the above?
18228         if((signed char)boards[move][EP_STATUS] >= 0) {
18229             *p++ = boards[move][EP_STATUS] + AAA;
18230             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18231         } else {
18232             *p++ = '-';
18233         }
18234     } else {
18235         *p++ = '-';
18236     }
18237     *p++ = ' ';
18238   }
18239   }
18240
18241     if(moveCounts)
18242     {   int i = 0, j=move;
18243
18244         /* [HGM] find reversible plies */
18245         if (appData.debugMode) { int k;
18246             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18247             for(k=backwardMostMove; k<=forwardMostMove; k++)
18248                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18249
18250         }
18251
18252         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18253         if( j == backwardMostMove ) i += initialRulePlies;
18254         sprintf(p, "%d ", i);
18255         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18256
18257         /* Fullmove number */
18258         sprintf(p, "%d", (move / 2) + 1);
18259     } else *--p = NULLCHAR;
18260
18261     return StrSave(buf);
18262 }
18263
18264 Boolean
18265 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18266 {
18267     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18268     char *p, c;
18269     int emptycount, virgin[BOARD_FILES];
18270     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18271
18272     p = fen;
18273
18274     /* Piece placement data */
18275     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18276         j = 0;
18277         for (;;) {
18278             if (*p == '/' || *p == ' ' || *p == '[' ) {
18279                 if(j > w) w = j;
18280                 emptycount = gameInfo.boardWidth - j;
18281                 while (emptycount--)
18282                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18283                 if (*p == '/') p++;
18284                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18285                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18286                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18287                     }
18288                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18289                 }
18290                 break;
18291 #if(BOARD_FILES >= 10)*0
18292             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18293                 p++; emptycount=10;
18294                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18295                 while (emptycount--)
18296                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18297 #endif
18298             } else if (*p == '*') {
18299                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18300             } else if (isdigit(*p)) {
18301                 emptycount = *p++ - '0';
18302                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18303                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18304                 while (emptycount--)
18305                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18306             } else if (*p == '<') {
18307                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18308                 else if (i != 0 || !shuffle) return FALSE;
18309                 p++;
18310             } else if (shuffle && *p == '>') {
18311                 p++; // for now ignore closing shuffle range, and assume rank-end
18312             } else if (*p == '?') {
18313                 if (j >= gameInfo.boardWidth) return FALSE;
18314                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18315                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18316             } else if (*p == '+' || isalpha(*p)) {
18317                 char *q, *s = SUFFIXES;
18318                 if (j >= gameInfo.boardWidth) return FALSE;
18319                 if(*p=='+') {
18320                     char c = *++p;
18321                     if(q = strchr(s, p[1])) p++;
18322                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18323                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18324                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18325                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18326                 } else {
18327                     char c = *p++;
18328                     if(q = strchr(s, *p)) p++;
18329                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18330                 }
18331
18332                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18333                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18334                     piece = (ChessSquare) (PROMOTED(piece));
18335                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18336                     p++;
18337                 }
18338                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18339                 if(piece == king) wKingRank = i;
18340                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18341             } else {
18342                 return FALSE;
18343             }
18344         }
18345     }
18346     while (*p == '/' || *p == ' ') p++;
18347
18348     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18349
18350     /* [HGM] by default clear Crazyhouse holdings, if present */
18351     if(gameInfo.holdingsWidth) {
18352        for(i=0; i<BOARD_HEIGHT; i++) {
18353            board[i][0]             = EmptySquare; /* black holdings */
18354            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18355            board[i][1]             = (ChessSquare) 0; /* black counts */
18356            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18357        }
18358     }
18359
18360     /* [HGM] look for Crazyhouse holdings here */
18361     while(*p==' ') p++;
18362     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18363         int swap=0, wcnt=0, bcnt=0;
18364         if(*p == '[') p++;
18365         if(*p == '<') swap++, p++;
18366         if(*p == '-' ) p++; /* empty holdings */ else {
18367             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18368             /* if we would allow FEN reading to set board size, we would   */
18369             /* have to add holdings and shift the board read so far here   */
18370             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18371                 p++;
18372                 if((int) piece >= (int) BlackPawn ) {
18373                     i = (int)piece - (int)BlackPawn;
18374                     i = PieceToNumber((ChessSquare)i);
18375                     if( i >= gameInfo.holdingsSize ) return FALSE;
18376                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18377                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18378                     bcnt++;
18379                 } else {
18380                     i = (int)piece - (int)WhitePawn;
18381                     i = PieceToNumber((ChessSquare)i);
18382                     if( i >= gameInfo.holdingsSize ) return FALSE;
18383                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18384                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18385                     wcnt++;
18386                 }
18387             }
18388             if(subst) { // substitute back-rank question marks by holdings pieces
18389                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18390                     int k, m, n = bcnt + 1;
18391                     if(board[0][j] == ClearBoard) {
18392                         if(!wcnt) return FALSE;
18393                         n = rand() % wcnt;
18394                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18395                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18396                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18397                             break;
18398                         }
18399                     }
18400                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18401                         if(!bcnt) return FALSE;
18402                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18403                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18404                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18405                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18406                             break;
18407                         }
18408                     }
18409                 }
18410                 subst = 0;
18411             }
18412         }
18413         if(*p == ']') p++;
18414     }
18415
18416     if(subst) return FALSE; // substitution requested, but no holdings
18417
18418     while(*p == ' ') p++;
18419
18420     /* Active color */
18421     c = *p++;
18422     if(appData.colorNickNames) {
18423       if( c == appData.colorNickNames[0] ) c = 'w'; else
18424       if( c == appData.colorNickNames[1] ) c = 'b';
18425     }
18426     switch (c) {
18427       case 'w':
18428         *blackPlaysFirst = FALSE;
18429         break;
18430       case 'b':
18431         *blackPlaysFirst = TRUE;
18432         break;
18433       default:
18434         return FALSE;
18435     }
18436
18437     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18438     /* return the extra info in global variiables             */
18439
18440     while(*p==' ') p++;
18441
18442     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18443         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18444         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18445     }
18446
18447     /* set defaults in case FEN is incomplete */
18448     board[EP_STATUS] = EP_UNKNOWN;
18449     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18450     for(i=0; i<nrCastlingRights; i++ ) {
18451         board[CASTLING][i] =
18452             appData.fischerCastling ? NoRights : initialRights[i];
18453     }   /* assume possible unless obviously impossible */
18454     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18455     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18456     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18457                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18458     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18459     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18460     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18461                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18462     FENrulePlies = 0;
18463
18464     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18465       char *q = p;
18466       int w=0, b=0;
18467       while(isalpha(*p)) {
18468         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18469         if(islower(*p)) b |= 1 << (*p++ - 'a');
18470       }
18471       if(*p == '-') p++;
18472       if(p != q) {
18473         board[TOUCHED_W] = ~w;
18474         board[TOUCHED_B] = ~b;
18475         while(*p == ' ') p++;
18476       }
18477     } else
18478
18479     if(nrCastlingRights) {
18480       int fischer = 0;
18481       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18482       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18483           /* castling indicator present, so default becomes no castlings */
18484           for(i=0; i<nrCastlingRights; i++ ) {
18485                  board[CASTLING][i] = NoRights;
18486           }
18487       }
18488       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18489              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18490              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18491              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18492         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18493
18494         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18495             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18496             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18497         }
18498         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18499             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18500         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18501                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18502         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18503                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18504         switch(c) {
18505           case'K':
18506               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18507               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18508               board[CASTLING][2] = whiteKingFile;
18509               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18510               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18511               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18512               break;
18513           case'Q':
18514               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18515               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18516               board[CASTLING][2] = whiteKingFile;
18517               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18518               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18519               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18520               break;
18521           case'k':
18522               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18523               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18524               board[CASTLING][5] = blackKingFile;
18525               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18526               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18527               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18528               break;
18529           case'q':
18530               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18531               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18532               board[CASTLING][5] = blackKingFile;
18533               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18534               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18535               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18536           case '-':
18537               break;
18538           default: /* FRC castlings */
18539               if(c >= 'a') { /* black rights */
18540                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18541                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18542                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18543                   if(i == BOARD_RGHT) break;
18544                   board[CASTLING][5] = i;
18545                   c -= AAA;
18546                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18547                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18548                   if(c > i)
18549                       board[CASTLING][3] = c;
18550                   else
18551                       board[CASTLING][4] = c;
18552               } else { /* white rights */
18553                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18554                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18555                     if(board[0][i] == WhiteKing) break;
18556                   if(i == BOARD_RGHT) break;
18557                   board[CASTLING][2] = i;
18558                   c -= AAA - 'a' + 'A';
18559                   if(board[0][c] >= WhiteKing) break;
18560                   if(c > i)
18561                       board[CASTLING][0] = c;
18562                   else
18563                       board[CASTLING][1] = c;
18564               }
18565         }
18566       }
18567       for(i=0; i<nrCastlingRights; i++)
18568         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18569       if(gameInfo.variant == VariantSChess)
18570         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18571       if(fischer && shuffle) appData.fischerCastling = TRUE;
18572     if (appData.debugMode) {
18573         fprintf(debugFP, "FEN castling rights:");
18574         for(i=0; i<nrCastlingRights; i++)
18575         fprintf(debugFP, " %d", board[CASTLING][i]);
18576         fprintf(debugFP, "\n");
18577     }
18578
18579       while(*p==' ') p++;
18580     }
18581
18582     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18583
18584     /* read e.p. field in games that know e.p. capture */
18585     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18586        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18587        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18588       if(*p=='-') {
18589         p++; board[EP_STATUS] = EP_NONE;
18590       } else {
18591          char c = *p++ - AAA;
18592
18593          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18594          if(*p >= '0' && *p <='9') p++;
18595          board[EP_STATUS] = c;
18596       }
18597     }
18598
18599
18600     if(sscanf(p, "%d", &i) == 1) {
18601         FENrulePlies = i; /* 50-move ply counter */
18602         /* (The move number is still ignored)    */
18603     }
18604
18605     return TRUE;
18606 }
18607
18608 void
18609 EditPositionPasteFEN (char *fen)
18610 {
18611   if (fen != NULL) {
18612     Board initial_position;
18613
18614     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18615       DisplayError(_("Bad FEN position in clipboard"), 0);
18616       return ;
18617     } else {
18618       int savedBlackPlaysFirst = blackPlaysFirst;
18619       EditPositionEvent();
18620       blackPlaysFirst = savedBlackPlaysFirst;
18621       CopyBoard(boards[0], initial_position);
18622       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18623       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18624       DisplayBothClocks();
18625       DrawPosition(FALSE, boards[currentMove]);
18626     }
18627   }
18628 }
18629
18630 static char cseq[12] = "\\   ";
18631
18632 Boolean
18633 set_cont_sequence (char *new_seq)
18634 {
18635     int len;
18636     Boolean ret;
18637
18638     // handle bad attempts to set the sequence
18639         if (!new_seq)
18640                 return 0; // acceptable error - no debug
18641
18642     len = strlen(new_seq);
18643     ret = (len > 0) && (len < sizeof(cseq));
18644     if (ret)
18645       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18646     else if (appData.debugMode)
18647       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18648     return ret;
18649 }
18650
18651 /*
18652     reformat a source message so words don't cross the width boundary.  internal
18653     newlines are not removed.  returns the wrapped size (no null character unless
18654     included in source message).  If dest is NULL, only calculate the size required
18655     for the dest buffer.  lp argument indicats line position upon entry, and it's
18656     passed back upon exit.
18657 */
18658 int
18659 wrap (char *dest, char *src, int count, int width, int *lp)
18660 {
18661     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18662
18663     cseq_len = strlen(cseq);
18664     old_line = line = *lp;
18665     ansi = len = clen = 0;
18666
18667     for (i=0; i < count; i++)
18668     {
18669         if (src[i] == '\033')
18670             ansi = 1;
18671
18672         // if we hit the width, back up
18673         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18674         {
18675             // store i & len in case the word is too long
18676             old_i = i, old_len = len;
18677
18678             // find the end of the last word
18679             while (i && src[i] != ' ' && src[i] != '\n')
18680             {
18681                 i--;
18682                 len--;
18683             }
18684
18685             // word too long?  restore i & len before splitting it
18686             if ((old_i-i+clen) >= width)
18687             {
18688                 i = old_i;
18689                 len = old_len;
18690             }
18691
18692             // extra space?
18693             if (i && src[i-1] == ' ')
18694                 len--;
18695
18696             if (src[i] != ' ' && src[i] != '\n')
18697             {
18698                 i--;
18699                 if (len)
18700                     len--;
18701             }
18702
18703             // now append the newline and continuation sequence
18704             if (dest)
18705                 dest[len] = '\n';
18706             len++;
18707             if (dest)
18708                 strncpy(dest+len, cseq, cseq_len);
18709             len += cseq_len;
18710             line = cseq_len;
18711             clen = cseq_len;
18712             continue;
18713         }
18714
18715         if (dest)
18716             dest[len] = src[i];
18717         len++;
18718         if (!ansi)
18719             line++;
18720         if (src[i] == '\n')
18721             line = 0;
18722         if (src[i] == 'm')
18723             ansi = 0;
18724     }
18725     if (dest && appData.debugMode)
18726     {
18727         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18728             count, width, line, len, *lp);
18729         show_bytes(debugFP, src, count);
18730         fprintf(debugFP, "\ndest: ");
18731         show_bytes(debugFP, dest, len);
18732         fprintf(debugFP, "\n");
18733     }
18734     *lp = dest ? line : old_line;
18735
18736     return len;
18737 }
18738
18739 // [HGM] vari: routines for shelving variations
18740 Boolean modeRestore = FALSE;
18741
18742 void
18743 PushInner (int firstMove, int lastMove)
18744 {
18745         int i, j, nrMoves = lastMove - firstMove;
18746
18747         // push current tail of game on stack
18748         savedResult[storedGames] = gameInfo.result;
18749         savedDetails[storedGames] = gameInfo.resultDetails;
18750         gameInfo.resultDetails = NULL;
18751         savedFirst[storedGames] = firstMove;
18752         savedLast [storedGames] = lastMove;
18753         savedFramePtr[storedGames] = framePtr;
18754         framePtr -= nrMoves; // reserve space for the boards
18755         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18756             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18757             for(j=0; j<MOVE_LEN; j++)
18758                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18759             for(j=0; j<2*MOVE_LEN; j++)
18760                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18761             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18762             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18763             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18764             pvInfoList[firstMove+i-1].depth = 0;
18765             commentList[framePtr+i] = commentList[firstMove+i];
18766             commentList[firstMove+i] = NULL;
18767         }
18768
18769         storedGames++;
18770         forwardMostMove = firstMove; // truncate game so we can start variation
18771 }
18772
18773 void
18774 PushTail (int firstMove, int lastMove)
18775 {
18776         if(appData.icsActive) { // only in local mode
18777                 forwardMostMove = currentMove; // mimic old ICS behavior
18778                 return;
18779         }
18780         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18781
18782         PushInner(firstMove, lastMove);
18783         if(storedGames == 1) GreyRevert(FALSE);
18784         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18785 }
18786
18787 void
18788 PopInner (Boolean annotate)
18789 {
18790         int i, j, nrMoves;
18791         char buf[8000], moveBuf[20];
18792
18793         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18794         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18795         nrMoves = savedLast[storedGames] - currentMove;
18796         if(annotate) {
18797                 int cnt = 10;
18798                 if(!WhiteOnMove(currentMove))
18799                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18800                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18801                 for(i=currentMove; i<forwardMostMove; i++) {
18802                         if(WhiteOnMove(i))
18803                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18804                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18805                         strcat(buf, moveBuf);
18806                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18807                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18808                 }
18809                 strcat(buf, ")");
18810         }
18811         for(i=1; i<=nrMoves; i++) { // copy last variation back
18812             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18813             for(j=0; j<MOVE_LEN; j++)
18814                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18815             for(j=0; j<2*MOVE_LEN; j++)
18816                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18817             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18818             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18819             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18820             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18821             commentList[currentMove+i] = commentList[framePtr+i];
18822             commentList[framePtr+i] = NULL;
18823         }
18824         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18825         framePtr = savedFramePtr[storedGames];
18826         gameInfo.result = savedResult[storedGames];
18827         if(gameInfo.resultDetails != NULL) {
18828             free(gameInfo.resultDetails);
18829       }
18830         gameInfo.resultDetails = savedDetails[storedGames];
18831         forwardMostMove = currentMove + nrMoves;
18832 }
18833
18834 Boolean
18835 PopTail (Boolean annotate)
18836 {
18837         if(appData.icsActive) return FALSE; // only in local mode
18838         if(!storedGames) return FALSE; // sanity
18839         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18840
18841         PopInner(annotate);
18842         if(currentMove < forwardMostMove) ForwardEvent(); else
18843         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18844
18845         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18846         return TRUE;
18847 }
18848
18849 void
18850 CleanupTail ()
18851 {       // remove all shelved variations
18852         int i;
18853         for(i=0; i<storedGames; i++) {
18854             if(savedDetails[i])
18855                 free(savedDetails[i]);
18856             savedDetails[i] = NULL;
18857         }
18858         for(i=framePtr; i<MAX_MOVES; i++) {
18859                 if(commentList[i]) free(commentList[i]);
18860                 commentList[i] = NULL;
18861         }
18862         framePtr = MAX_MOVES-1;
18863         storedGames = 0;
18864 }
18865
18866 void
18867 LoadVariation (int index, char *text)
18868 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18869         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18870         int level = 0, move;
18871
18872         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18873         // first find outermost bracketing variation
18874         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18875             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18876                 if(*p == '{') wait = '}'; else
18877                 if(*p == '[') wait = ']'; else
18878                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18879                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18880             }
18881             if(*p == wait) wait = NULLCHAR; // closing ]} found
18882             p++;
18883         }
18884         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18885         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18886         end[1] = NULLCHAR; // clip off comment beyond variation
18887         ToNrEvent(currentMove-1);
18888         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18889         // kludge: use ParsePV() to append variation to game
18890         move = currentMove;
18891         ParsePV(start, TRUE, TRUE);
18892         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18893         ClearPremoveHighlights();
18894         CommentPopDown();
18895         ToNrEvent(currentMove+1);
18896 }
18897
18898 void
18899 LoadTheme ()
18900 {
18901     char *p, *q, buf[MSG_SIZ];
18902     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18903         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18904         ParseArgsFromString(buf);
18905         ActivateTheme(TRUE); // also redo colors
18906         return;
18907     }
18908     p = nickName;
18909     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18910     {
18911         int len;
18912         q = appData.themeNames;
18913         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18914       if(appData.useBitmaps) {
18915         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18916                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18917                 appData.liteBackTextureMode,
18918                 appData.darkBackTextureMode );
18919       } else {
18920         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18921                 Col2Text(2),   // lightSquareColor
18922                 Col2Text(3) ); // darkSquareColor
18923       }
18924       if(appData.useBorder) {
18925         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18926                 appData.border);
18927       } else {
18928         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18929       }
18930       if(appData.useFont) {
18931         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18932                 appData.renderPiecesWithFont,
18933                 appData.fontToPieceTable,
18934                 Col2Text(9),    // appData.fontBackColorWhite
18935                 Col2Text(10) ); // appData.fontForeColorBlack
18936       } else {
18937         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18938                 appData.pieceDirectory);
18939         if(!appData.pieceDirectory[0])
18940           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18941                 Col2Text(0),   // whitePieceColor
18942                 Col2Text(1) ); // blackPieceColor
18943       }
18944       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18945                 Col2Text(4),   // highlightSquareColor
18946                 Col2Text(5) ); // premoveHighlightColor
18947         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18948         if(insert != q) insert[-1] = NULLCHAR;
18949         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18950         if(q)   free(q);
18951     }
18952     ActivateTheme(FALSE);
18953 }