Fix disappearance of premoved piece
[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(curscore >= MATE_SCORE) 
9770                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9771                 else if(curscore <= -MATE_SCORE) 
9772                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9773                 else
9774                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9775                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9776                          plylev,
9777                          (gameMode == TwoMachinesPlay ?
9778                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9779                          score_buf,
9780                          prefixHint ? lastHint : "",
9781                          prefixHint ? " " : "" );
9782
9783                 if( buf1[0] != NULLCHAR ) {
9784                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9785
9786                     if( strlen(pv) > max_len ) {
9787                         if( appData.debugMode) {
9788                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9789                         }
9790                         pv[max_len+1] = '\0';
9791                     }
9792
9793                     strcat( thinkOutput, pv);
9794                 }
9795
9796                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9797                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9798                     DisplayMove(currentMove - 1);
9799                 }
9800                 return;
9801
9802             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9803                 /* crafty (9.25+) says "(only move) <move>"
9804                  * if there is only 1 legal move
9805                  */
9806                 sscanf(p, "(only move) %s", buf1);
9807                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9808                 sprintf(programStats.movelist, "%s (only move)", buf1);
9809                 programStats.depth = 1;
9810                 programStats.nr_moves = 1;
9811                 programStats.moves_left = 1;
9812                 programStats.nodes = 1;
9813                 programStats.time = 1;
9814                 programStats.got_only_move = 1;
9815
9816                 /* Not really, but we also use this member to
9817                    mean "line isn't going to change" (Crafty
9818                    isn't searching, so stats won't change) */
9819                 programStats.line_is_book = 1;
9820
9821                 SendProgramStatsToFrontend( cps, &programStats );
9822
9823                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9824                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9825                     DisplayMove(currentMove - 1);
9826                 }
9827                 return;
9828             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9829                               &time, &nodes, &plylev, &mvleft,
9830                               &mvtot, mvname) >= 5) {
9831                 /* The stat01: line is from Crafty (9.29+) in response
9832                    to the "." command */
9833                 programStats.seen_stat = 1;
9834                 cps->maybeThinking = TRUE;
9835
9836                 if (programStats.got_only_move || !appData.periodicUpdates)
9837                   return;
9838
9839                 programStats.depth = plylev;
9840                 programStats.time = time;
9841                 programStats.nodes = nodes;
9842                 programStats.moves_left = mvleft;
9843                 programStats.nr_moves = mvtot;
9844                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9845                 programStats.ok_to_send = 1;
9846                 programStats.movelist[0] = '\0';
9847
9848                 SendProgramStatsToFrontend( cps, &programStats );
9849
9850                 return;
9851
9852             } else if (strncmp(message,"++",2) == 0) {
9853                 /* Crafty 9.29+ outputs this */
9854                 programStats.got_fail = 2;
9855                 return;
9856
9857             } else if (strncmp(message,"--",2) == 0) {
9858                 /* Crafty 9.29+ outputs this */
9859                 programStats.got_fail = 1;
9860                 return;
9861
9862             } else if (thinkOutput[0] != NULLCHAR &&
9863                        strncmp(message, "    ", 4) == 0) {
9864                 unsigned message_len;
9865
9866                 p = message;
9867                 while (*p && *p == ' ') p++;
9868
9869                 message_len = strlen( p );
9870
9871                 /* [AS] Avoid buffer overflow */
9872                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9873                     strcat(thinkOutput, " ");
9874                     strcat(thinkOutput, p);
9875                 }
9876
9877                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9878                     strcat(programStats.movelist, " ");
9879                     strcat(programStats.movelist, p);
9880                 }
9881
9882                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9883                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9884                     DisplayMove(currentMove - 1);
9885                 }
9886                 return;
9887             }
9888         }
9889         else {
9890             buf1[0] = NULLCHAR;
9891
9892             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9893                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9894             {
9895                 ChessProgramStats cpstats;
9896
9897                 if (plyext != ' ' && plyext != '\t') {
9898                     time *= 100;
9899                 }
9900
9901                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9902                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9903                     curscore = -curscore;
9904                 }
9905
9906                 cpstats.depth = plylev;
9907                 cpstats.nodes = nodes;
9908                 cpstats.time = time;
9909                 cpstats.score = curscore;
9910                 cpstats.got_only_move = 0;
9911                 cpstats.movelist[0] = '\0';
9912
9913                 if (buf1[0] != NULLCHAR) {
9914                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9915                 }
9916
9917                 cpstats.ok_to_send = 0;
9918                 cpstats.line_is_book = 0;
9919                 cpstats.nr_moves = 0;
9920                 cpstats.moves_left = 0;
9921
9922                 SendProgramStatsToFrontend( cps, &cpstats );
9923             }
9924         }
9925     }
9926 }
9927
9928
9929 /* Parse a game score from the character string "game", and
9930    record it as the history of the current game.  The game
9931    score is NOT assumed to start from the standard position.
9932    The display is not updated in any way.
9933    */
9934 void
9935 ParseGameHistory (char *game)
9936 {
9937     ChessMove moveType;
9938     int fromX, fromY, toX, toY, boardIndex;
9939     char promoChar;
9940     char *p, *q;
9941     char buf[MSG_SIZ];
9942
9943     if (appData.debugMode)
9944       fprintf(debugFP, "Parsing game history: %s\n", game);
9945
9946     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9947     gameInfo.site = StrSave(appData.icsHost);
9948     gameInfo.date = PGNDate();
9949     gameInfo.round = StrSave("-");
9950
9951     /* Parse out names of players */
9952     while (*game == ' ') game++;
9953     p = buf;
9954     while (*game != ' ') *p++ = *game++;
9955     *p = NULLCHAR;
9956     gameInfo.white = StrSave(buf);
9957     while (*game == ' ') game++;
9958     p = buf;
9959     while (*game != ' ' && *game != '\n') *p++ = *game++;
9960     *p = NULLCHAR;
9961     gameInfo.black = StrSave(buf);
9962
9963     /* Parse moves */
9964     boardIndex = blackPlaysFirst ? 1 : 0;
9965     yynewstr(game);
9966     for (;;) {
9967         yyboardindex = boardIndex;
9968         moveType = (ChessMove) Myylex();
9969         switch (moveType) {
9970           case IllegalMove:             /* maybe suicide chess, etc. */
9971   if (appData.debugMode) {
9972     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9973     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9974     setbuf(debugFP, NULL);
9975   }
9976           case WhitePromotion:
9977           case BlackPromotion:
9978           case WhiteNonPromotion:
9979           case BlackNonPromotion:
9980           case NormalMove:
9981           case FirstLeg:
9982           case WhiteCapturesEnPassant:
9983           case BlackCapturesEnPassant:
9984           case WhiteKingSideCastle:
9985           case WhiteQueenSideCastle:
9986           case BlackKingSideCastle:
9987           case BlackQueenSideCastle:
9988           case WhiteKingSideCastleWild:
9989           case WhiteQueenSideCastleWild:
9990           case BlackKingSideCastleWild:
9991           case BlackQueenSideCastleWild:
9992           /* PUSH Fabien */
9993           case WhiteHSideCastleFR:
9994           case WhiteASideCastleFR:
9995           case BlackHSideCastleFR:
9996           case BlackASideCastleFR:
9997           /* POP Fabien */
9998             fromX = currentMoveString[0] - AAA;
9999             fromY = currentMoveString[1] - ONE;
10000             toX = currentMoveString[2] - AAA;
10001             toY = currentMoveString[3] - ONE;
10002             promoChar = currentMoveString[4];
10003             break;
10004           case WhiteDrop:
10005           case BlackDrop:
10006             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
10007             fromX = moveType == WhiteDrop ?
10008               (int) CharToPiece(ToUpper(currentMoveString[0])) :
10009             (int) CharToPiece(ToLower(currentMoveString[0]));
10010             fromY = DROP_RANK;
10011             toX = currentMoveString[2] - AAA;
10012             toY = currentMoveString[3] - ONE;
10013             promoChar = NULLCHAR;
10014             break;
10015           case AmbiguousMove:
10016             /* bug? */
10017             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
10018   if (appData.debugMode) {
10019     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
10020     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10021     setbuf(debugFP, NULL);
10022   }
10023             DisplayError(buf, 0);
10024             return;
10025           case ImpossibleMove:
10026             /* bug? */
10027             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
10028   if (appData.debugMode) {
10029     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
10030     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
10031     setbuf(debugFP, NULL);
10032   }
10033             DisplayError(buf, 0);
10034             return;
10035           case EndOfFile:
10036             if (boardIndex < backwardMostMove) {
10037                 /* Oops, gap.  How did that happen? */
10038                 DisplayError(_("Gap in move list"), 0);
10039                 return;
10040             }
10041             backwardMostMove =  blackPlaysFirst ? 1 : 0;
10042             if (boardIndex > forwardMostMove) {
10043                 forwardMostMove = boardIndex;
10044             }
10045             return;
10046           case ElapsedTime:
10047             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
10048                 strcat(parseList[boardIndex-1], " ");
10049                 strcat(parseList[boardIndex-1], yy_text);
10050             }
10051             continue;
10052           case Comment:
10053           case PGNTag:
10054           case NAG:
10055           default:
10056             /* ignore */
10057             continue;
10058           case WhiteWins:
10059           case BlackWins:
10060           case GameIsDrawn:
10061           case GameUnfinished:
10062             if (gameMode == IcsExamining) {
10063                 if (boardIndex < backwardMostMove) {
10064                     /* Oops, gap.  How did that happen? */
10065                     return;
10066                 }
10067                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10068                 return;
10069             }
10070             gameInfo.result = moveType;
10071             p = strchr(yy_text, '{');
10072             if (p == NULL) p = strchr(yy_text, '(');
10073             if (p == NULL) {
10074                 p = yy_text;
10075                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10076             } else {
10077                 q = strchr(p, *p == '{' ? '}' : ')');
10078                 if (q != NULL) *q = NULLCHAR;
10079                 p++;
10080             }
10081             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10082             gameInfo.resultDetails = StrSave(p);
10083             continue;
10084         }
10085         if (boardIndex >= forwardMostMove &&
10086             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10087             backwardMostMove = blackPlaysFirst ? 1 : 0;
10088             return;
10089         }
10090         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10091                                  fromY, fromX, toY, toX, promoChar,
10092                                  parseList[boardIndex]);
10093         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10094         /* currentMoveString is set as a side-effect of yylex */
10095         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10096         strcat(moveList[boardIndex], "\n");
10097         boardIndex++;
10098         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10099         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10100           case MT_NONE:
10101           case MT_STALEMATE:
10102           default:
10103             break;
10104           case MT_CHECK:
10105             if(!IS_SHOGI(gameInfo.variant))
10106                 strcat(parseList[boardIndex - 1], "+");
10107             break;
10108           case MT_CHECKMATE:
10109           case MT_STAINMATE:
10110             strcat(parseList[boardIndex - 1], "#");
10111             break;
10112         }
10113     }
10114 }
10115
10116
10117 /* Apply a move to the given board  */
10118 void
10119 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10120 {
10121   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10122   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10123
10124     /* [HGM] compute & store e.p. status and castling rights for new position */
10125     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10126
10127       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10128       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10129       board[EP_STATUS] = EP_NONE;
10130       board[EP_FILE] = board[EP_RANK] = 100;
10131
10132   if (fromY == DROP_RANK) {
10133         /* must be first */
10134         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10135             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10136             return;
10137         }
10138         piece = board[toY][toX] = (ChessSquare) fromX;
10139   } else {
10140 //      ChessSquare victim;
10141       int i;
10142
10143       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10144 //           victim = board[killY][killX],
10145            killed = board[killY][killX],
10146            board[killY][killX] = EmptySquare,
10147            board[EP_STATUS] = EP_CAPTURE;
10148            if( kill2X >= 0 && kill2Y >= 0)
10149              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10150       }
10151
10152       if( board[toY][toX] != EmptySquare ) {
10153            board[EP_STATUS] = EP_CAPTURE;
10154            if( (fromX != toX || fromY != toY) && // not igui!
10155                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10156                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10157                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10158            }
10159       }
10160
10161       pawn = board[fromY][fromX];
10162       if( pawn == WhiteLance || pawn == BlackLance ) {
10163            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10164                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10165                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10166            }
10167       }
10168       if( pawn == WhitePawn ) {
10169            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10170                board[EP_STATUS] = EP_PAWN_MOVE;
10171            if( toY-fromY>=2) {
10172                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10173                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10174                         gameInfo.variant != VariantBerolina || toX < fromX)
10175                       board[EP_STATUS] = toX | berolina;
10176                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10177                         gameInfo.variant != VariantBerolina || toX > fromX)
10178                       board[EP_STATUS] = toX;
10179            }
10180       } else
10181       if( pawn == BlackPawn ) {
10182            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10183                board[EP_STATUS] = EP_PAWN_MOVE;
10184            if( toY-fromY<= -2) {
10185                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10186                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10187                         gameInfo.variant != VariantBerolina || toX < fromX)
10188                       board[EP_STATUS] = toX | berolina;
10189                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10190                         gameInfo.variant != VariantBerolina || toX > fromX)
10191                       board[EP_STATUS] = toX;
10192            }
10193        }
10194
10195        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10196        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10197        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10198        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10199
10200        for(i=0; i<nrCastlingRights; i++) {
10201            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10202               board[CASTLING][i] == toX   && castlingRank[i] == toY
10203              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10204        }
10205
10206        if(gameInfo.variant == VariantSChess) { // update virginity
10207            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10208            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10209            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10210            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10211        }
10212
10213      if (fromX == toX && fromY == toY) return;
10214
10215      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10216      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10217      if(gameInfo.variant == VariantKnightmate)
10218          king += (int) WhiteUnicorn - (int) WhiteKing;
10219
10220     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10221        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10222         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10223         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10224         board[EP_STATUS] = EP_NONE; // capture was fake!
10225     } else
10226     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10227         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10228         board[toY][toX] = piece;
10229         board[EP_STATUS] = EP_NONE; // capture was fake!
10230     } else
10231     /* Code added by Tord: */
10232     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10233     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10234         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10235       board[EP_STATUS] = EP_NONE; // capture was fake!
10236       board[fromY][fromX] = EmptySquare;
10237       board[toY][toX] = EmptySquare;
10238       if((toX > fromX) != (piece == WhiteRook)) {
10239         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10240       } else {
10241         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10242       }
10243     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10244                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10245       board[EP_STATUS] = EP_NONE;
10246       board[fromY][fromX] = EmptySquare;
10247       board[toY][toX] = EmptySquare;
10248       if((toX > fromX) != (piece == BlackRook)) {
10249         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10250       } else {
10251         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10252       }
10253     /* End of code added by Tord */
10254
10255     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10256         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10257         board[toY][toX] = piece;
10258     } else if (board[fromY][fromX] == king
10259         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10260         && toY == fromY && toX > fromX+1) {
10261         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10262         board[fromY][toX-1] = board[fromY][rookX];
10263         board[fromY][rookX] = EmptySquare;
10264         board[fromY][fromX] = EmptySquare;
10265         board[toY][toX] = king;
10266     } else if (board[fromY][fromX] == king
10267         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10268                && toY == fromY && toX < fromX-1) {
10269         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10270         board[fromY][toX+1] = board[fromY][rookX];
10271         board[fromY][rookX] = EmptySquare;
10272         board[fromY][fromX] = EmptySquare;
10273         board[toY][toX] = king;
10274     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10275                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10276                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10277                ) {
10278         /* white pawn promotion */
10279         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10280         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10281             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10282         board[fromY][fromX] = EmptySquare;
10283     } else if ((fromY >= BOARD_HEIGHT>>1)
10284                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10285                && (toX != fromX)
10286                && gameInfo.variant != VariantXiangqi
10287                && gameInfo.variant != VariantBerolina
10288                && (pawn == WhitePawn)
10289                && (board[toY][toX] == EmptySquare)) {
10290         board[fromY][fromX] = EmptySquare;
10291         board[toY][toX] = piece;
10292         if(toY == epRank - 128 + 1)
10293             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10294         else
10295             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10296     } else if ((fromY == BOARD_HEIGHT-4)
10297                && (toX == fromX)
10298                && gameInfo.variant == VariantBerolina
10299                && (board[fromY][fromX] == WhitePawn)
10300                && (board[toY][toX] == EmptySquare)) {
10301         board[fromY][fromX] = EmptySquare;
10302         board[toY][toX] = WhitePawn;
10303         if(oldEP & EP_BEROLIN_A) {
10304                 captured = board[fromY][fromX-1];
10305                 board[fromY][fromX-1] = EmptySquare;
10306         }else{  captured = board[fromY][fromX+1];
10307                 board[fromY][fromX+1] = EmptySquare;
10308         }
10309     } else if (board[fromY][fromX] == king
10310         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10311                && toY == fromY && toX > fromX+1) {
10312         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10313         board[fromY][toX-1] = board[fromY][rookX];
10314         board[fromY][rookX] = EmptySquare;
10315         board[fromY][fromX] = EmptySquare;
10316         board[toY][toX] = king;
10317     } else if (board[fromY][fromX] == king
10318         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10319                && toY == fromY && toX < fromX-1) {
10320         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10321         board[fromY][toX+1] = board[fromY][rookX];
10322         board[fromY][rookX] = EmptySquare;
10323         board[fromY][fromX] = EmptySquare;
10324         board[toY][toX] = king;
10325     } else if (fromY == 7 && fromX == 3
10326                && board[fromY][fromX] == BlackKing
10327                && toY == 7 && toX == 5) {
10328         board[fromY][fromX] = EmptySquare;
10329         board[toY][toX] = BlackKing;
10330         board[fromY][7] = EmptySquare;
10331         board[toY][4] = BlackRook;
10332     } else if (fromY == 7 && fromX == 3
10333                && board[fromY][fromX] == BlackKing
10334                && toY == 7 && toX == 1) {
10335         board[fromY][fromX] = EmptySquare;
10336         board[toY][toX] = BlackKing;
10337         board[fromY][0] = EmptySquare;
10338         board[toY][2] = BlackRook;
10339     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10340                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10341                && toY < promoRank && promoChar
10342                ) {
10343         /* black pawn promotion */
10344         board[toY][toX] = CharToPiece(ToLower(promoChar));
10345         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10346             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10347         board[fromY][fromX] = EmptySquare;
10348     } else if ((fromY < BOARD_HEIGHT>>1)
10349                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10350                && (toX != fromX)
10351                && gameInfo.variant != VariantXiangqi
10352                && gameInfo.variant != VariantBerolina
10353                && (pawn == BlackPawn)
10354                && (board[toY][toX] == EmptySquare)) {
10355         board[fromY][fromX] = EmptySquare;
10356         board[toY][toX] = piece;
10357         if(toY == epRank - 128 - 1)
10358             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10359         else
10360             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10361     } else if ((fromY == 3)
10362                && (toX == fromX)
10363                && gameInfo.variant == VariantBerolina
10364                && (board[fromY][fromX] == BlackPawn)
10365                && (board[toY][toX] == EmptySquare)) {
10366         board[fromY][fromX] = EmptySquare;
10367         board[toY][toX] = BlackPawn;
10368         if(oldEP & EP_BEROLIN_A) {
10369                 captured = board[fromY][fromX-1];
10370                 board[fromY][fromX-1] = EmptySquare;
10371         }else{  captured = board[fromY][fromX+1];
10372                 board[fromY][fromX+1] = EmptySquare;
10373         }
10374     } else {
10375         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10376         board[fromY][fromX] = EmptySquare;
10377         board[toY][toX] = piece;
10378     }
10379   }
10380
10381     if (gameInfo.holdingsWidth != 0) {
10382
10383       /* !!A lot more code needs to be written to support holdings  */
10384       /* [HGM] OK, so I have written it. Holdings are stored in the */
10385       /* penultimate board files, so they are automaticlly stored   */
10386       /* in the game history.                                       */
10387       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10388                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10389         /* Delete from holdings, by decreasing count */
10390         /* and erasing image if necessary            */
10391         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10392         if(p < (int) BlackPawn) { /* white drop */
10393              p -= (int)WhitePawn;
10394                  p = PieceToNumber((ChessSquare)p);
10395              if(p >= gameInfo.holdingsSize) p = 0;
10396              if(--board[p][BOARD_WIDTH-2] <= 0)
10397                   board[p][BOARD_WIDTH-1] = EmptySquare;
10398              if((int)board[p][BOARD_WIDTH-2] < 0)
10399                         board[p][BOARD_WIDTH-2] = 0;
10400         } else {                  /* black drop */
10401              p -= (int)BlackPawn;
10402                  p = PieceToNumber((ChessSquare)p);
10403              if(p >= gameInfo.holdingsSize) p = 0;
10404              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10405                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10406              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10407                         board[BOARD_HEIGHT-1-p][1] = 0;
10408         }
10409       }
10410       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10411           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10412         /* [HGM] holdings: Add to holdings, if holdings exist */
10413         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10414                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10415                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10416         }
10417         p = (int) captured;
10418         if (p >= (int) BlackPawn) {
10419           p -= (int)BlackPawn;
10420           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10421                   /* Restore shogi-promoted piece to its original  first */
10422                   captured = (ChessSquare) (DEMOTED(captured));
10423                   p = DEMOTED(p);
10424           }
10425           p = PieceToNumber((ChessSquare)p);
10426           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10427           board[p][BOARD_WIDTH-2]++;
10428           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10429         } else {
10430           p -= (int)WhitePawn;
10431           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10432                   captured = (ChessSquare) (DEMOTED(captured));
10433                   p = DEMOTED(p);
10434           }
10435           p = PieceToNumber((ChessSquare)p);
10436           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10437           board[BOARD_HEIGHT-1-p][1]++;
10438           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10439         }
10440       }
10441     } else if (gameInfo.variant == VariantAtomic) {
10442       if (captured != EmptySquare) {
10443         int y, x;
10444         for (y = toY-1; y <= toY+1; y++) {
10445           for (x = toX-1; x <= toX+1; x++) {
10446             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10447                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10448               board[y][x] = EmptySquare;
10449             }
10450           }
10451         }
10452         board[toY][toX] = EmptySquare;
10453       }
10454     }
10455
10456     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10457         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10458     } else
10459     if(promoChar == '+') {
10460         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10461         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10462         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10463           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10464     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10465         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10466         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10467            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10468         board[toY][toX] = newPiece;
10469     }
10470     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10471                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10472         // [HGM] superchess: take promotion piece out of holdings
10473         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10474         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10475             if(!--board[k][BOARD_WIDTH-2])
10476                 board[k][BOARD_WIDTH-1] = EmptySquare;
10477         } else {
10478             if(!--board[BOARD_HEIGHT-1-k][1])
10479                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10480         }
10481     }
10482 }
10483
10484 /* Updates forwardMostMove */
10485 void
10486 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10487 {
10488     int x = toX, y = toY;
10489     char *s = parseList[forwardMostMove];
10490     ChessSquare p = boards[forwardMostMove][toY][toX];
10491 //    forwardMostMove++; // [HGM] bare: moved downstream
10492
10493     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10494     (void) CoordsToAlgebraic(boards[forwardMostMove],
10495                              PosFlags(forwardMostMove),
10496                              fromY, fromX, y, x, (killX < 0)*promoChar,
10497                              s);
10498     if(killX >= 0 && killY >= 0)
10499         sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0', promoChar);
10500
10501     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10502         int timeLeft; static int lastLoadFlag=0; int king, piece;
10503         piece = boards[forwardMostMove][fromY][fromX];
10504         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10505         if(gameInfo.variant == VariantKnightmate)
10506             king += (int) WhiteUnicorn - (int) WhiteKing;
10507         if(forwardMostMove == 0) {
10508             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10509                 fprintf(serverMoves, "%s;", UserName());
10510             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10511                 fprintf(serverMoves, "%s;", second.tidy);
10512             fprintf(serverMoves, "%s;", first.tidy);
10513             if(gameMode == MachinePlaysWhite)
10514                 fprintf(serverMoves, "%s;", UserName());
10515             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10516                 fprintf(serverMoves, "%s;", second.tidy);
10517         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10518         lastLoadFlag = loadFlag;
10519         // print base move
10520         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10521         // print castling suffix
10522         if( toY == fromY && piece == king ) {
10523             if(toX-fromX > 1)
10524                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10525             if(fromX-toX >1)
10526                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10527         }
10528         // e.p. suffix
10529         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10530              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10531              boards[forwardMostMove][toY][toX] == EmptySquare
10532              && fromX != toX && fromY != toY)
10533                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10534         // promotion suffix
10535         if(promoChar != NULLCHAR) {
10536             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10537                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10538                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10539             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10540         }
10541         if(!loadFlag) {
10542                 char buf[MOVE_LEN*2], *p; int len;
10543             fprintf(serverMoves, "/%d/%d",
10544                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10545             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10546             else                      timeLeft = blackTimeRemaining/1000;
10547             fprintf(serverMoves, "/%d", timeLeft);
10548                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10549                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10550                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10551                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10552             fprintf(serverMoves, "/%s", buf);
10553         }
10554         fflush(serverMoves);
10555     }
10556
10557     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10558         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10559       return;
10560     }
10561     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10562     if (commentList[forwardMostMove+1] != NULL) {
10563         free(commentList[forwardMostMove+1]);
10564         commentList[forwardMostMove+1] = NULL;
10565     }
10566     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10567     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10568     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10569     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10570     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10571     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10572     adjustedClock = FALSE;
10573     gameInfo.result = GameUnfinished;
10574     if (gameInfo.resultDetails != NULL) {
10575         free(gameInfo.resultDetails);
10576         gameInfo.resultDetails = NULL;
10577     }
10578     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10579                               moveList[forwardMostMove - 1]);
10580     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10581       case MT_NONE:
10582       case MT_STALEMATE:
10583       default:
10584         break;
10585       case MT_CHECK:
10586         if(!IS_SHOGI(gameInfo.variant))
10587             strcat(parseList[forwardMostMove - 1], "+");
10588         break;
10589       case MT_CHECKMATE:
10590       case MT_STAINMATE:
10591         strcat(parseList[forwardMostMove - 1], "#");
10592         break;
10593     }
10594 }
10595
10596 /* Updates currentMove if not pausing */
10597 void
10598 ShowMove (int fromX, int fromY, int toX, int toY)
10599 {
10600     int instant = (gameMode == PlayFromGameFile) ?
10601         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10602     if(appData.noGUI) return;
10603     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10604         if (!instant) {
10605             if (forwardMostMove == currentMove + 1) {
10606                 AnimateMove(boards[forwardMostMove - 1],
10607                             fromX, fromY, toX, toY);
10608             }
10609         }
10610         currentMove = forwardMostMove;
10611     }
10612
10613     killX = killY = -1; // [HGM] lion: used up
10614
10615     if (instant) return;
10616
10617     DisplayMove(currentMove - 1);
10618     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10619             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10620                 SetHighlights(fromX, fromY, toX, toY);
10621             }
10622     }
10623     DrawPosition(FALSE, boards[currentMove]);
10624     DisplayBothClocks();
10625     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10626 }
10627
10628 void
10629 SendEgtPath (ChessProgramState *cps)
10630 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10631         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10632
10633         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10634
10635         while(*p) {
10636             char c, *q = name+1, *r, *s;
10637
10638             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10639             while(*p && *p != ',') *q++ = *p++;
10640             *q++ = ':'; *q = 0;
10641             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10642                 strcmp(name, ",nalimov:") == 0 ) {
10643                 // take nalimov path from the menu-changeable option first, if it is defined
10644               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10645                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10646             } else
10647             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10648                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10649                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10650                 s = r = StrStr(s, ":") + 1; // beginning of path info
10651                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10652                 c = *r; *r = 0;             // temporarily null-terminate path info
10653                     *--q = 0;               // strip of trailig ':' from name
10654                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10655                 *r = c;
10656                 SendToProgram(buf,cps);     // send egtbpath command for this format
10657             }
10658             if(*p == ',') p++; // read away comma to position for next format name
10659         }
10660 }
10661
10662 static int
10663 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10664 {
10665       int width = 8, height = 8, holdings = 0;             // most common sizes
10666       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10667       // correct the deviations default for each variant
10668       if( v == VariantXiangqi ) width = 9,  height = 10;
10669       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10670       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10671       if( v == VariantCapablanca || v == VariantCapaRandom ||
10672           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10673                                 width = 10;
10674       if( v == VariantCourier ) width = 12;
10675       if( v == VariantSuper )                            holdings = 8;
10676       if( v == VariantGreat )   width = 10,              holdings = 8;
10677       if( v == VariantSChess )                           holdings = 7;
10678       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10679       if( v == VariantChuChess) width = 10, height = 10;
10680       if( v == VariantChu )     width = 12, height = 12;
10681       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10682              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10683              holdingsSize >= 0 && holdingsSize != holdings;
10684 }
10685
10686 char variantError[MSG_SIZ];
10687
10688 char *
10689 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10690 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10691       char *p, *variant = VariantName(v);
10692       static char b[MSG_SIZ];
10693       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10694            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10695                                                holdingsSize, variant); // cook up sized variant name
10696            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10697            if(StrStr(list, b) == NULL) {
10698                // specific sized variant not known, check if general sizing allowed
10699                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10700                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10701                             boardWidth, boardHeight, holdingsSize, engine);
10702                    return NULL;
10703                }
10704                /* [HGM] here we really should compare with the maximum supported board size */
10705            }
10706       } else snprintf(b, MSG_SIZ,"%s", variant);
10707       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10708       p = StrStr(list, b);
10709       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10710       if(p == NULL) {
10711           // occurs not at all in list, or only as sub-string
10712           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10713           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10714               int l = strlen(variantError);
10715               char *q;
10716               while(p != list && p[-1] != ',') p--;
10717               q = strchr(p, ',');
10718               if(q) *q = NULLCHAR;
10719               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10720               if(q) *q= ',';
10721           }
10722           return NULL;
10723       }
10724       return b;
10725 }
10726
10727 void
10728 InitChessProgram (ChessProgramState *cps, int setup)
10729 /* setup needed to setup FRC opening position */
10730 {
10731     char buf[MSG_SIZ], *b;
10732     if (appData.noChessProgram) return;
10733     hintRequested = FALSE;
10734     bookRequested = FALSE;
10735
10736     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10737     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10738     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10739     if(cps->memSize) { /* [HGM] memory */
10740       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10741         SendToProgram(buf, cps);
10742     }
10743     SendEgtPath(cps); /* [HGM] EGT */
10744     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10745       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10746         SendToProgram(buf, cps);
10747     }
10748
10749     setboardSpoiledMachineBlack = FALSE;
10750     SendToProgram(cps->initString, cps);
10751     if (gameInfo.variant != VariantNormal &&
10752         gameInfo.variant != VariantLoadable
10753         /* [HGM] also send variant if board size non-standard */
10754         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10755
10756       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10757                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10758       if (b == NULL) {
10759         VariantClass v;
10760         char c, *q = cps->variants, *p = strchr(q, ',');
10761         if(p) *p = NULLCHAR;
10762         v = StringToVariant(q);
10763         DisplayError(variantError, 0);
10764         if(v != VariantUnknown && cps == &first) {
10765             int w, h, s;
10766             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10767                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10768             ASSIGN(appData.variant, q);
10769             Reset(TRUE, FALSE);
10770         }
10771         if(p) *p = ',';
10772         return;
10773       }
10774
10775       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10776       SendToProgram(buf, cps);
10777     }
10778     currentlyInitializedVariant = gameInfo.variant;
10779
10780     /* [HGM] send opening position in FRC to first engine */
10781     if(setup) {
10782           SendToProgram("force\n", cps);
10783           SendBoard(cps, 0);
10784           /* engine is now in force mode! Set flag to wake it up after first move. */
10785           setboardSpoiledMachineBlack = 1;
10786     }
10787
10788     if (cps->sendICS) {
10789       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10790       SendToProgram(buf, cps);
10791     }
10792     cps->maybeThinking = FALSE;
10793     cps->offeredDraw = 0;
10794     if (!appData.icsActive) {
10795         SendTimeControl(cps, movesPerSession, timeControl,
10796                         timeIncrement, appData.searchDepth,
10797                         searchTime);
10798     }
10799     if (appData.showThinking
10800         // [HGM] thinking: four options require thinking output to be sent
10801         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10802                                 ) {
10803         SendToProgram("post\n", cps);
10804     }
10805     SendToProgram("hard\n", cps);
10806     if (!appData.ponderNextMove) {
10807         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10808            it without being sure what state we are in first.  "hard"
10809            is not a toggle, so that one is OK.
10810          */
10811         SendToProgram("easy\n", cps);
10812     }
10813     if (cps->usePing) {
10814       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10815       SendToProgram(buf, cps);
10816     }
10817     cps->initDone = TRUE;
10818     ClearEngineOutputPane(cps == &second);
10819 }
10820
10821
10822 void
10823 ResendOptions (ChessProgramState *cps)
10824 { // send the stored value of the options
10825   int i;
10826   char buf[MSG_SIZ];
10827   Option *opt = cps->option;
10828   for(i=0; i<cps->nrOptions; i++, opt++) {
10829       switch(opt->type) {
10830         case Spin:
10831         case Slider:
10832         case CheckBox:
10833             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10834           break;
10835         case ComboBox:
10836           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10837           break;
10838         default:
10839             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10840           break;
10841         case Button:
10842         case SaveButton:
10843           continue;
10844       }
10845       SendToProgram(buf, cps);
10846   }
10847 }
10848
10849 void
10850 StartChessProgram (ChessProgramState *cps)
10851 {
10852     char buf[MSG_SIZ];
10853     int err;
10854
10855     if (appData.noChessProgram) return;
10856     cps->initDone = FALSE;
10857
10858     if (strcmp(cps->host, "localhost") == 0) {
10859         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10860     } else if (*appData.remoteShell == NULLCHAR) {
10861         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10862     } else {
10863         if (*appData.remoteUser == NULLCHAR) {
10864           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10865                     cps->program);
10866         } else {
10867           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10868                     cps->host, appData.remoteUser, cps->program);
10869         }
10870         err = StartChildProcess(buf, "", &cps->pr);
10871     }
10872
10873     if (err != 0) {
10874       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10875         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10876         if(cps != &first) return;
10877         appData.noChessProgram = TRUE;
10878         ThawUI();
10879         SetNCPMode();
10880 //      DisplayFatalError(buf, err, 1);
10881 //      cps->pr = NoProc;
10882 //      cps->isr = NULL;
10883         return;
10884     }
10885
10886     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10887     if (cps->protocolVersion > 1) {
10888       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10889       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10890         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10891         cps->comboCnt = 0;  //                and values of combo boxes
10892       }
10893       SendToProgram(buf, cps);
10894       if(cps->reload) ResendOptions(cps);
10895     } else {
10896       SendToProgram("xboard\n", cps);
10897     }
10898 }
10899
10900 void
10901 TwoMachinesEventIfReady P((void))
10902 {
10903   static int curMess = 0;
10904   if (first.lastPing != first.lastPong) {
10905     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10906     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10907     return;
10908   }
10909   if (second.lastPing != second.lastPong) {
10910     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10911     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10912     return;
10913   }
10914   DisplayMessage("", ""); curMess = 0;
10915   TwoMachinesEvent();
10916 }
10917
10918 char *
10919 MakeName (char *template)
10920 {
10921     time_t clock;
10922     struct tm *tm;
10923     static char buf[MSG_SIZ];
10924     char *p = buf;
10925     int i;
10926
10927     clock = time((time_t *)NULL);
10928     tm = localtime(&clock);
10929
10930     while(*p++ = *template++) if(p[-1] == '%') {
10931         switch(*template++) {
10932           case 0:   *p = 0; return buf;
10933           case 'Y': i = tm->tm_year+1900; break;
10934           case 'y': i = tm->tm_year-100; break;
10935           case 'M': i = tm->tm_mon+1; break;
10936           case 'd': i = tm->tm_mday; break;
10937           case 'h': i = tm->tm_hour; break;
10938           case 'm': i = tm->tm_min; break;
10939           case 's': i = tm->tm_sec; break;
10940           default:  i = 0;
10941         }
10942         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10943     }
10944     return buf;
10945 }
10946
10947 int
10948 CountPlayers (char *p)
10949 {
10950     int n = 0;
10951     while(p = strchr(p, '\n')) p++, n++; // count participants
10952     return n;
10953 }
10954
10955 FILE *
10956 WriteTourneyFile (char *results, FILE *f)
10957 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10958     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10959     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10960         // create a file with tournament description
10961         fprintf(f, "-participants {%s}\n", appData.participants);
10962         fprintf(f, "-seedBase %d\n", appData.seedBase);
10963         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10964         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10965         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10966         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10967         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10968         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10969         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10970         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10971         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10972         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10973         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10974         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10975         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10976         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10977         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10978         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10979         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10980         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10981         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10982         fprintf(f, "-smpCores %d\n", appData.smpCores);
10983         if(searchTime > 0)
10984                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10985         else {
10986                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10987                 fprintf(f, "-tc %s\n", appData.timeControl);
10988                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10989         }
10990         fprintf(f, "-results \"%s\"\n", results);
10991     }
10992     return f;
10993 }
10994
10995 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10996
10997 void
10998 Substitute (char *participants, int expunge)
10999 {
11000     int i, changed, changes=0, nPlayers=0;
11001     char *p, *q, *r, buf[MSG_SIZ];
11002     if(participants == NULL) return;
11003     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
11004     r = p = participants; q = appData.participants;
11005     while(*p && *p == *q) {
11006         if(*p == '\n') r = p+1, nPlayers++;
11007         p++; q++;
11008     }
11009     if(*p) { // difference
11010         while(*p && *p++ != '\n');
11011         while(*q && *q++ != '\n');
11012       changed = nPlayers;
11013         changes = 1 + (strcmp(p, q) != 0);
11014     }
11015     if(changes == 1) { // a single engine mnemonic was changed
11016         q = r; while(*q) nPlayers += (*q++ == '\n');
11017         p = buf; while(*r && (*p = *r++) != '\n') p++;
11018         *p = NULLCHAR;
11019         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11020         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
11021         if(mnemonic[i]) { // The substitute is valid
11022             FILE *f;
11023             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
11024                 flock(fileno(f), LOCK_EX);
11025                 ParseArgsFromFile(f);
11026                 fseek(f, 0, SEEK_SET);
11027                 FREE(appData.participants); appData.participants = participants;
11028                 if(expunge) { // erase results of replaced engine
11029                     int len = strlen(appData.results), w, b, dummy;
11030                     for(i=0; i<len; i++) {
11031                         Pairing(i, nPlayers, &w, &b, &dummy);
11032                         if((w == changed || b == changed) && appData.results[i] == '*') {
11033                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
11034                             fclose(f);
11035                             return;
11036                         }
11037                     }
11038                     for(i=0; i<len; i++) {
11039                         Pairing(i, nPlayers, &w, &b, &dummy);
11040                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
11041                     }
11042                 }
11043                 WriteTourneyFile(appData.results, f);
11044                 fclose(f); // release lock
11045                 return;
11046             }
11047         } else DisplayError(_("No engine with the name you gave is installed"), 0);
11048     }
11049     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11050     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11051     free(participants);
11052     return;
11053 }
11054
11055 int
11056 CheckPlayers (char *participants)
11057 {
11058         int i;
11059         char buf[MSG_SIZ], *p;
11060         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11061         while(p = strchr(participants, '\n')) {
11062             *p = NULLCHAR;
11063             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11064             if(!mnemonic[i]) {
11065                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11066                 *p = '\n';
11067                 DisplayError(buf, 0);
11068                 return 1;
11069             }
11070             *p = '\n';
11071             participants = p + 1;
11072         }
11073         return 0;
11074 }
11075
11076 int
11077 CreateTourney (char *name)
11078 {
11079         FILE *f;
11080         if(matchMode && strcmp(name, appData.tourneyFile)) {
11081              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11082         }
11083         if(name[0] == NULLCHAR) {
11084             if(appData.participants[0])
11085                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11086             return 0;
11087         }
11088         f = fopen(name, "r");
11089         if(f) { // file exists
11090             ASSIGN(appData.tourneyFile, name);
11091             ParseArgsFromFile(f); // parse it
11092         } else {
11093             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11094             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11095                 DisplayError(_("Not enough participants"), 0);
11096                 return 0;
11097             }
11098             if(CheckPlayers(appData.participants)) return 0;
11099             ASSIGN(appData.tourneyFile, name);
11100             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11101             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11102         }
11103         fclose(f);
11104         appData.noChessProgram = FALSE;
11105         appData.clockMode = TRUE;
11106         SetGNUMode();
11107         return 1;
11108 }
11109
11110 int
11111 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11112 {
11113     char buf[MSG_SIZ], *p, *q;
11114     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11115     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11116     skip = !all && group[0]; // if group requested, we start in skip mode
11117     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11118         p = names; q = buf; header = 0;
11119         while(*p && *p != '\n') *q++ = *p++;
11120         *q = 0;
11121         if(*p == '\n') p++;
11122         if(buf[0] == '#') {
11123             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11124             depth++; // we must be entering a new group
11125             if(all) continue; // suppress printing group headers when complete list requested
11126             header = 1;
11127             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11128         }
11129         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11130         if(engineList[i]) free(engineList[i]);
11131         engineList[i] = strdup(buf);
11132         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11133         if(engineMnemonic[i]) free(engineMnemonic[i]);
11134         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11135             strcat(buf, " (");
11136             sscanf(q + 8, "%s", buf + strlen(buf));
11137             strcat(buf, ")");
11138         }
11139         engineMnemonic[i] = strdup(buf);
11140         i++;
11141     }
11142     engineList[i] = engineMnemonic[i] = NULL;
11143     return i;
11144 }
11145
11146 // following implemented as macro to avoid type limitations
11147 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11148
11149 void
11150 SwapEngines (int n)
11151 {   // swap settings for first engine and other engine (so far only some selected options)
11152     int h;
11153     char *p;
11154     if(n == 0) return;
11155     SWAP(directory, p)
11156     SWAP(chessProgram, p)
11157     SWAP(isUCI, h)
11158     SWAP(hasOwnBookUCI, h)
11159     SWAP(protocolVersion, h)
11160     SWAP(reuse, h)
11161     SWAP(scoreIsAbsolute, h)
11162     SWAP(timeOdds, h)
11163     SWAP(logo, p)
11164     SWAP(pgnName, p)
11165     SWAP(pvSAN, h)
11166     SWAP(engOptions, p)
11167     SWAP(engInitString, p)
11168     SWAP(computerString, p)
11169     SWAP(features, p)
11170     SWAP(fenOverride, p)
11171     SWAP(NPS, h)
11172     SWAP(accumulateTC, h)
11173     SWAP(drawDepth, h)
11174     SWAP(host, p)
11175     SWAP(pseudo, h)
11176 }
11177
11178 int
11179 GetEngineLine (char *s, int n)
11180 {
11181     int i;
11182     char buf[MSG_SIZ];
11183     extern char *icsNames;
11184     if(!s || !*s) return 0;
11185     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11186     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11187     if(!mnemonic[i]) return 0;
11188     if(n == 11) return 1; // just testing if there was a match
11189     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11190     if(n == 1) SwapEngines(n);
11191     ParseArgsFromString(buf);
11192     if(n == 1) SwapEngines(n);
11193     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11194         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11195         ParseArgsFromString(buf);
11196     }
11197     return 1;
11198 }
11199
11200 int
11201 SetPlayer (int player, char *p)
11202 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11203     int i;
11204     char buf[MSG_SIZ], *engineName;
11205     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11206     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11207     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11208     if(mnemonic[i]) {
11209         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11210         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11211         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11212         ParseArgsFromString(buf);
11213     } else { // no engine with this nickname is installed!
11214         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11215         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11216         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11217         ModeHighlight();
11218         DisplayError(buf, 0);
11219         return 0;
11220     }
11221     free(engineName);
11222     return i;
11223 }
11224
11225 char *recentEngines;
11226
11227 void
11228 RecentEngineEvent (int nr)
11229 {
11230     int n;
11231 //    SwapEngines(1); // bump first to second
11232 //    ReplaceEngine(&second, 1); // and load it there
11233     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11234     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11235     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11236         ReplaceEngine(&first, 0);
11237         FloatToFront(&appData.recentEngineList, command[n]);
11238     }
11239 }
11240
11241 int
11242 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11243 {   // determine players from game number
11244     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11245
11246     if(appData.tourneyType == 0) {
11247         roundsPerCycle = (nPlayers - 1) | 1;
11248         pairingsPerRound = nPlayers / 2;
11249     } else if(appData.tourneyType > 0) {
11250         roundsPerCycle = nPlayers - appData.tourneyType;
11251         pairingsPerRound = appData.tourneyType;
11252     }
11253     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11254     gamesPerCycle = gamesPerRound * roundsPerCycle;
11255     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11256     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11257     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11258     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11259     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11260     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11261
11262     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11263     if(appData.roundSync) *syncInterval = gamesPerRound;
11264
11265     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11266
11267     if(appData.tourneyType == 0) {
11268         if(curPairing == (nPlayers-1)/2 ) {
11269             *whitePlayer = curRound;
11270             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11271         } else {
11272             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11273             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11274             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11275             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11276         }
11277     } else if(appData.tourneyType > 1) {
11278         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11279         *whitePlayer = curRound + appData.tourneyType;
11280     } else if(appData.tourneyType > 0) {
11281         *whitePlayer = curPairing;
11282         *blackPlayer = curRound + appData.tourneyType;
11283     }
11284
11285     // take care of white/black alternation per round.
11286     // For cycles and games this is already taken care of by default, derived from matchGame!
11287     return curRound & 1;
11288 }
11289
11290 int
11291 NextTourneyGame (int nr, int *swapColors)
11292 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11293     char *p, *q;
11294     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11295     FILE *tf;
11296     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11297     tf = fopen(appData.tourneyFile, "r");
11298     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11299     ParseArgsFromFile(tf); fclose(tf);
11300     InitTimeControls(); // TC might be altered from tourney file
11301
11302     nPlayers = CountPlayers(appData.participants); // count participants
11303     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11304     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11305
11306     if(syncInterval) {
11307         p = q = appData.results;
11308         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11309         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11310             DisplayMessage(_("Waiting for other game(s)"),"");
11311             waitingForGame = TRUE;
11312             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11313             return 0;
11314         }
11315         waitingForGame = FALSE;
11316     }
11317
11318     if(appData.tourneyType < 0) {
11319         if(nr>=0 && !pairingReceived) {
11320             char buf[1<<16];
11321             if(pairing.pr == NoProc) {
11322                 if(!appData.pairingEngine[0]) {
11323                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11324                     return 0;
11325                 }
11326                 StartChessProgram(&pairing); // starts the pairing engine
11327             }
11328             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11329             SendToProgram(buf, &pairing);
11330             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11331             SendToProgram(buf, &pairing);
11332             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11333         }
11334         pairingReceived = 0;                              // ... so we continue here
11335         *swapColors = 0;
11336         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11337         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11338         matchGame = 1; roundNr = nr / syncInterval + 1;
11339     }
11340
11341     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11342
11343     // redefine engines, engine dir, etc.
11344     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11345     if(first.pr == NoProc) {
11346       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11347       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11348     }
11349     if(second.pr == NoProc) {
11350       SwapEngines(1);
11351       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11352       SwapEngines(1);         // and make that valid for second engine by swapping
11353       InitEngine(&second, 1);
11354     }
11355     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11356     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11357     return OK;
11358 }
11359
11360 void
11361 NextMatchGame ()
11362 {   // performs game initialization that does not invoke engines, and then tries to start the game
11363     int res, firstWhite, swapColors = 0;
11364     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11365     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
11366         char buf[MSG_SIZ];
11367         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11368         if(strcmp(buf, currentDebugFile)) { // name has changed
11369             FILE *f = fopen(buf, "w");
11370             if(f) { // if opening the new file failed, just keep using the old one
11371                 ASSIGN(currentDebugFile, buf);
11372                 fclose(debugFP);
11373                 debugFP = f;
11374             }
11375             if(appData.serverFileName) {
11376                 if(serverFP) fclose(serverFP);
11377                 serverFP = fopen(appData.serverFileName, "w");
11378                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11379                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11380             }
11381         }
11382     }
11383     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11384     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11385     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11386     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11387     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11388     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11389     Reset(FALSE, first.pr != NoProc);
11390     res = LoadGameOrPosition(matchGame); // setup game
11391     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11392     if(!res) return; // abort when bad game/pos file
11393     TwoMachinesEvent();
11394 }
11395
11396 void
11397 UserAdjudicationEvent (int result)
11398 {
11399     ChessMove gameResult = GameIsDrawn;
11400
11401     if( result > 0 ) {
11402         gameResult = WhiteWins;
11403     }
11404     else if( result < 0 ) {
11405         gameResult = BlackWins;
11406     }
11407
11408     if( gameMode == TwoMachinesPlay ) {
11409         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11410     }
11411 }
11412
11413
11414 // [HGM] save: calculate checksum of game to make games easily identifiable
11415 int
11416 StringCheckSum (char *s)
11417 {
11418         int i = 0;
11419         if(s==NULL) return 0;
11420         while(*s) i = i*259 + *s++;
11421         return i;
11422 }
11423
11424 int
11425 GameCheckSum ()
11426 {
11427         int i, sum=0;
11428         for(i=backwardMostMove; i<forwardMostMove; i++) {
11429                 sum += pvInfoList[i].depth;
11430                 sum += StringCheckSum(parseList[i]);
11431                 sum += StringCheckSum(commentList[i]);
11432                 sum *= 261;
11433         }
11434         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11435         return sum + StringCheckSum(commentList[i]);
11436 } // end of save patch
11437
11438 void
11439 GameEnds (ChessMove result, char *resultDetails, int whosays)
11440 {
11441     GameMode nextGameMode;
11442     int isIcsGame;
11443     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11444
11445     if(endingGame) return; /* [HGM] crash: forbid recursion */
11446     endingGame = 1;
11447     if(twoBoards) { // [HGM] dual: switch back to one board
11448         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11449         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11450     }
11451     if (appData.debugMode) {
11452       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11453               result, resultDetails ? resultDetails : "(null)", whosays);
11454     }
11455
11456     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11457
11458     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11459
11460     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11461         /* If we are playing on ICS, the server decides when the
11462            game is over, but the engine can offer to draw, claim
11463            a draw, or resign.
11464          */
11465 #if ZIPPY
11466         if (appData.zippyPlay && first.initDone) {
11467             if (result == GameIsDrawn) {
11468                 /* In case draw still needs to be claimed */
11469                 SendToICS(ics_prefix);
11470                 SendToICS("draw\n");
11471             } else if (StrCaseStr(resultDetails, "resign")) {
11472                 SendToICS(ics_prefix);
11473                 SendToICS("resign\n");
11474             }
11475         }
11476 #endif
11477         endingGame = 0; /* [HGM] crash */
11478         return;
11479     }
11480
11481     /* If we're loading the game from a file, stop */
11482     if (whosays == GE_FILE) {
11483       (void) StopLoadGameTimer();
11484       gameFileFP = NULL;
11485     }
11486
11487     /* Cancel draw offers */
11488     first.offeredDraw = second.offeredDraw = 0;
11489
11490     /* If this is an ICS game, only ICS can really say it's done;
11491        if not, anyone can. */
11492     isIcsGame = (gameMode == IcsPlayingWhite ||
11493                  gameMode == IcsPlayingBlack ||
11494                  gameMode == IcsObserving    ||
11495                  gameMode == IcsExamining);
11496
11497     if (!isIcsGame || whosays == GE_ICS) {
11498         /* OK -- not an ICS game, or ICS said it was done */
11499         StopClocks();
11500         if (!isIcsGame && !appData.noChessProgram)
11501           SetUserThinkingEnables();
11502
11503         /* [HGM] if a machine claims the game end we verify this claim */
11504         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11505             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11506                 char claimer;
11507                 ChessMove trueResult = (ChessMove) -1;
11508
11509                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11510                                             first.twoMachinesColor[0] :
11511                                             second.twoMachinesColor[0] ;
11512
11513                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11514                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11515                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11516                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11517                 } else
11518                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11519                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11520                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11521                 } else
11522                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11523                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11524                 }
11525
11526                 // now verify win claims, but not in drop games, as we don't understand those yet
11527                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11528                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11529                     (result == WhiteWins && claimer == 'w' ||
11530                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11531                       if (appData.debugMode) {
11532                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11533                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11534                       }
11535                       if(result != trueResult) {
11536                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11537                               result = claimer == 'w' ? BlackWins : WhiteWins;
11538                               resultDetails = buf;
11539                       }
11540                 } else
11541                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11542                     && (forwardMostMove <= backwardMostMove ||
11543                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11544                         (claimer=='b')==(forwardMostMove&1))
11545                                                                                   ) {
11546                       /* [HGM] verify: draws that were not flagged are false claims */
11547                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11548                       result = claimer == 'w' ? BlackWins : WhiteWins;
11549                       resultDetails = buf;
11550                 }
11551                 /* (Claiming a loss is accepted no questions asked!) */
11552             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11553                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11554                 result = GameUnfinished;
11555                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11556             }
11557             /* [HGM] bare: don't allow bare King to win */
11558             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11559                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11560                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11561                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11562                && result != GameIsDrawn)
11563             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11564                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11565                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11566                         if(p >= 0 && p <= (int)WhiteKing) k++;
11567                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11568                 }
11569                 if (appData.debugMode) {
11570                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11571                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11572                 }
11573                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11574                         result = GameIsDrawn;
11575                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11576                         resultDetails = buf;
11577                 }
11578             }
11579         }
11580
11581
11582         if(serverMoves != NULL && !loadFlag) { char c = '=';
11583             if(result==WhiteWins) c = '+';
11584             if(result==BlackWins) c = '-';
11585             if(resultDetails != NULL)
11586                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11587         }
11588         if (resultDetails != NULL) {
11589             gameInfo.result = result;
11590             gameInfo.resultDetails = StrSave(resultDetails);
11591
11592             /* display last move only if game was not loaded from file */
11593             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11594                 DisplayMove(currentMove - 1);
11595
11596             if (forwardMostMove != 0) {
11597                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11598                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11599                                                                 ) {
11600                     if (*appData.saveGameFile != NULLCHAR) {
11601                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11602                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11603                         else
11604                         SaveGameToFile(appData.saveGameFile, TRUE);
11605                     } else if (appData.autoSaveGames) {
11606                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11607                     }
11608                     if (*appData.savePositionFile != NULLCHAR) {
11609                         SavePositionToFile(appData.savePositionFile);
11610                     }
11611                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11612                 }
11613             }
11614
11615             /* Tell program how game ended in case it is learning */
11616             /* [HGM] Moved this to after saving the PGN, just in case */
11617             /* engine died and we got here through time loss. In that */
11618             /* case we will get a fatal error writing the pipe, which */
11619             /* would otherwise lose us the PGN.                       */
11620             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11621             /* output during GameEnds should never be fatal anymore   */
11622             if (gameMode == MachinePlaysWhite ||
11623                 gameMode == MachinePlaysBlack ||
11624                 gameMode == TwoMachinesPlay ||
11625                 gameMode == IcsPlayingWhite ||
11626                 gameMode == IcsPlayingBlack ||
11627                 gameMode == BeginningOfGame) {
11628                 char buf[MSG_SIZ];
11629                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11630                         resultDetails);
11631                 if (first.pr != NoProc) {
11632                     SendToProgram(buf, &first);
11633                 }
11634                 if (second.pr != NoProc &&
11635                     gameMode == TwoMachinesPlay) {
11636                     SendToProgram(buf, &second);
11637                 }
11638             }
11639         }
11640
11641         if (appData.icsActive) {
11642             if (appData.quietPlay &&
11643                 (gameMode == IcsPlayingWhite ||
11644                  gameMode == IcsPlayingBlack)) {
11645                 SendToICS(ics_prefix);
11646                 SendToICS("set shout 1\n");
11647             }
11648             nextGameMode = IcsIdle;
11649             ics_user_moved = FALSE;
11650             /* clean up premove.  It's ugly when the game has ended and the
11651              * premove highlights are still on the board.
11652              */
11653             if (gotPremove) {
11654               gotPremove = FALSE;
11655               ClearPremoveHighlights();
11656               DrawPosition(FALSE, boards[currentMove]);
11657             }
11658             if (whosays == GE_ICS) {
11659                 switch (result) {
11660                 case WhiteWins:
11661                     if (gameMode == IcsPlayingWhite)
11662                         PlayIcsWinSound();
11663                     else if(gameMode == IcsPlayingBlack)
11664                         PlayIcsLossSound();
11665                     break;
11666                 case BlackWins:
11667                     if (gameMode == IcsPlayingBlack)
11668                         PlayIcsWinSound();
11669                     else if(gameMode == IcsPlayingWhite)
11670                         PlayIcsLossSound();
11671                     break;
11672                 case GameIsDrawn:
11673                     PlayIcsDrawSound();
11674                     break;
11675                 default:
11676                     PlayIcsUnfinishedSound();
11677                 }
11678             }
11679             if(appData.quitNext) { ExitEvent(0); return; }
11680         } else if (gameMode == EditGame ||
11681                    gameMode == PlayFromGameFile ||
11682                    gameMode == AnalyzeMode ||
11683                    gameMode == AnalyzeFile) {
11684             nextGameMode = gameMode;
11685         } else {
11686             nextGameMode = EndOfGame;
11687         }
11688         pausing = FALSE;
11689         ModeHighlight();
11690     } else {
11691         nextGameMode = gameMode;
11692     }
11693
11694     if (appData.noChessProgram) {
11695         gameMode = nextGameMode;
11696         ModeHighlight();
11697         endingGame = 0; /* [HGM] crash */
11698         return;
11699     }
11700
11701     if (first.reuse) {
11702         /* Put first chess program into idle state */
11703         if (first.pr != NoProc &&
11704             (gameMode == MachinePlaysWhite ||
11705              gameMode == MachinePlaysBlack ||
11706              gameMode == TwoMachinesPlay ||
11707              gameMode == IcsPlayingWhite ||
11708              gameMode == IcsPlayingBlack ||
11709              gameMode == BeginningOfGame)) {
11710             SendToProgram("force\n", &first);
11711             if (first.usePing) {
11712               char buf[MSG_SIZ];
11713               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11714               SendToProgram(buf, &first);
11715             }
11716         }
11717     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11718         /* Kill off first chess program */
11719         if (first.isr != NULL)
11720           RemoveInputSource(first.isr);
11721         first.isr = NULL;
11722
11723         if (first.pr != NoProc) {
11724             ExitAnalyzeMode();
11725             DoSleep( appData.delayBeforeQuit );
11726             SendToProgram("quit\n", &first);
11727             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11728             first.reload = TRUE;
11729         }
11730         first.pr = NoProc;
11731     }
11732     if (second.reuse) {
11733         /* Put second chess program into idle state */
11734         if (second.pr != NoProc &&
11735             gameMode == TwoMachinesPlay) {
11736             SendToProgram("force\n", &second);
11737             if (second.usePing) {
11738               char buf[MSG_SIZ];
11739               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11740               SendToProgram(buf, &second);
11741             }
11742         }
11743     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11744         /* Kill off second chess program */
11745         if (second.isr != NULL)
11746           RemoveInputSource(second.isr);
11747         second.isr = NULL;
11748
11749         if (second.pr != NoProc) {
11750             DoSleep( appData.delayBeforeQuit );
11751             SendToProgram("quit\n", &second);
11752             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11753             second.reload = TRUE;
11754         }
11755         second.pr = NoProc;
11756     }
11757
11758     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11759         char resChar = '=';
11760         switch (result) {
11761         case WhiteWins:
11762           resChar = '+';
11763           if (first.twoMachinesColor[0] == 'w') {
11764             first.matchWins++;
11765           } else {
11766             second.matchWins++;
11767           }
11768           break;
11769         case BlackWins:
11770           resChar = '-';
11771           if (first.twoMachinesColor[0] == 'b') {
11772             first.matchWins++;
11773           } else {
11774             second.matchWins++;
11775           }
11776           break;
11777         case GameUnfinished:
11778           resChar = ' ';
11779         default:
11780           break;
11781         }
11782
11783         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11784         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11785             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11786             ReserveGame(nextGame, resChar); // sets nextGame
11787             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11788             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11789         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11790
11791         if (nextGame <= appData.matchGames && !abortMatch) {
11792             gameMode = nextGameMode;
11793             matchGame = nextGame; // this will be overruled in tourney mode!
11794             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11795             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11796             endingGame = 0; /* [HGM] crash */
11797             return;
11798         } else {
11799             gameMode = nextGameMode;
11800             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11801                      first.tidy, second.tidy,
11802                      first.matchWins, second.matchWins,
11803                      appData.matchGames - (first.matchWins + second.matchWins));
11804             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11805             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11806             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11807             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11808                 first.twoMachinesColor = "black\n";
11809                 second.twoMachinesColor = "white\n";
11810             } else {
11811                 first.twoMachinesColor = "white\n";
11812                 second.twoMachinesColor = "black\n";
11813             }
11814         }
11815     }
11816     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11817         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11818       ExitAnalyzeMode();
11819     gameMode = nextGameMode;
11820     ModeHighlight();
11821     endingGame = 0;  /* [HGM] crash */
11822     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11823         if(matchMode == TRUE) { // match through command line: exit with or without popup
11824             if(ranking) {
11825                 ToNrEvent(forwardMostMove);
11826                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11827                 else ExitEvent(0);
11828             } else DisplayFatalError(buf, 0, 0);
11829         } else { // match through menu; just stop, with or without popup
11830             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11831             ModeHighlight();
11832             if(ranking){
11833                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11834             } else DisplayNote(buf);
11835       }
11836       if(ranking) free(ranking);
11837     }
11838 }
11839
11840 /* Assumes program was just initialized (initString sent).
11841    Leaves program in force mode. */
11842 void
11843 FeedMovesToProgram (ChessProgramState *cps, int upto)
11844 {
11845     int i;
11846
11847     if (appData.debugMode)
11848       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11849               startedFromSetupPosition ? "position and " : "",
11850               backwardMostMove, upto, cps->which);
11851     if(currentlyInitializedVariant != gameInfo.variant) {
11852       char buf[MSG_SIZ];
11853         // [HGM] variantswitch: make engine aware of new variant
11854         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11855                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11856                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11857         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11858         SendToProgram(buf, cps);
11859         currentlyInitializedVariant = gameInfo.variant;
11860     }
11861     SendToProgram("force\n", cps);
11862     if (startedFromSetupPosition) {
11863         SendBoard(cps, backwardMostMove);
11864     if (appData.debugMode) {
11865         fprintf(debugFP, "feedMoves\n");
11866     }
11867     }
11868     for (i = backwardMostMove; i < upto; i++) {
11869         SendMoveToProgram(i, cps);
11870     }
11871 }
11872
11873
11874 int
11875 ResurrectChessProgram ()
11876 {
11877      /* The chess program may have exited.
11878         If so, restart it and feed it all the moves made so far. */
11879     static int doInit = 0;
11880
11881     if (appData.noChessProgram) return 1;
11882
11883     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11884         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11885         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11886         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11887     } else {
11888         if (first.pr != NoProc) return 1;
11889         StartChessProgram(&first);
11890     }
11891     InitChessProgram(&first, FALSE);
11892     FeedMovesToProgram(&first, currentMove);
11893
11894     if (!first.sendTime) {
11895         /* can't tell gnuchess what its clock should read,
11896            so we bow to its notion. */
11897         ResetClocks();
11898         timeRemaining[0][currentMove] = whiteTimeRemaining;
11899         timeRemaining[1][currentMove] = blackTimeRemaining;
11900     }
11901
11902     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11903                 appData.icsEngineAnalyze) && first.analysisSupport) {
11904       SendToProgram("analyze\n", &first);
11905       first.analyzing = TRUE;
11906     }
11907     return 1;
11908 }
11909
11910 /*
11911  * Button procedures
11912  */
11913 void
11914 Reset (int redraw, int init)
11915 {
11916     int i;
11917
11918     if (appData.debugMode) {
11919         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11920                 redraw, init, gameMode);
11921     }
11922     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11923     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11924     CleanupTail(); // [HGM] vari: delete any stored variations
11925     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11926     pausing = pauseExamInvalid = FALSE;
11927     startedFromSetupPosition = blackPlaysFirst = FALSE;
11928     firstMove = TRUE;
11929     whiteFlag = blackFlag = FALSE;
11930     userOfferedDraw = FALSE;
11931     hintRequested = bookRequested = FALSE;
11932     first.maybeThinking = FALSE;
11933     second.maybeThinking = FALSE;
11934     first.bookSuspend = FALSE; // [HGM] book
11935     second.bookSuspend = FALSE;
11936     thinkOutput[0] = NULLCHAR;
11937     lastHint[0] = NULLCHAR;
11938     ClearGameInfo(&gameInfo);
11939     gameInfo.variant = StringToVariant(appData.variant);
11940     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11941     ics_user_moved = ics_clock_paused = FALSE;
11942     ics_getting_history = H_FALSE;
11943     ics_gamenum = -1;
11944     white_holding[0] = black_holding[0] = NULLCHAR;
11945     ClearProgramStats();
11946     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11947
11948     ResetFrontEnd();
11949     ClearHighlights();
11950     flipView = appData.flipView;
11951     ClearPremoveHighlights();
11952     gotPremove = FALSE;
11953     alarmSounded = FALSE;
11954     killX = killY = -1; // [HGM] lion
11955
11956     GameEnds(EndOfFile, NULL, GE_PLAYER);
11957     if(appData.serverMovesName != NULL) {
11958         /* [HGM] prepare to make moves file for broadcasting */
11959         clock_t t = clock();
11960         if(serverMoves != NULL) fclose(serverMoves);
11961         serverMoves = fopen(appData.serverMovesName, "r");
11962         if(serverMoves != NULL) {
11963             fclose(serverMoves);
11964             /* delay 15 sec before overwriting, so all clients can see end */
11965             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11966         }
11967         serverMoves = fopen(appData.serverMovesName, "w");
11968     }
11969
11970     ExitAnalyzeMode();
11971     gameMode = BeginningOfGame;
11972     ModeHighlight();
11973     if(appData.icsActive) gameInfo.variant = VariantNormal;
11974     currentMove = forwardMostMove = backwardMostMove = 0;
11975     MarkTargetSquares(1);
11976     InitPosition(redraw);
11977     for (i = 0; i < MAX_MOVES; i++) {
11978         if (commentList[i] != NULL) {
11979             free(commentList[i]);
11980             commentList[i] = NULL;
11981         }
11982     }
11983     ResetClocks();
11984     timeRemaining[0][0] = whiteTimeRemaining;
11985     timeRemaining[1][0] = blackTimeRemaining;
11986
11987     if (first.pr == NoProc) {
11988         StartChessProgram(&first);
11989     }
11990     if (init) {
11991             InitChessProgram(&first, startedFromSetupPosition);
11992     }
11993     DisplayTitle("");
11994     DisplayMessage("", "");
11995     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11996     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11997     ClearMap();        // [HGM] exclude: invalidate map
11998 }
11999
12000 void
12001 AutoPlayGameLoop ()
12002 {
12003     for (;;) {
12004         if (!AutoPlayOneMove())
12005           return;
12006         if (matchMode || appData.timeDelay == 0)
12007           continue;
12008         if (appData.timeDelay < 0)
12009           return;
12010         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
12011         break;
12012     }
12013 }
12014
12015 void
12016 AnalyzeNextGame()
12017 {
12018     ReloadGame(1); // next game
12019 }
12020
12021 int
12022 AutoPlayOneMove ()
12023 {
12024     int fromX, fromY, toX, toY;
12025
12026     if (appData.debugMode) {
12027       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
12028     }
12029
12030     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
12031       return FALSE;
12032
12033     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
12034       pvInfoList[currentMove].depth = programStats.depth;
12035       pvInfoList[currentMove].score = programStats.score;
12036       pvInfoList[currentMove].time  = 0;
12037       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
12038       else { // append analysis of final position as comment
12039         char buf[MSG_SIZ];
12040         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
12041         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
12042       }
12043       programStats.depth = 0;
12044     }
12045
12046     if (currentMove >= forwardMostMove) {
12047       if(gameMode == AnalyzeFile) {
12048           if(appData.loadGameIndex == -1) {
12049             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12050           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12051           } else {
12052           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12053         }
12054       }
12055 //      gameMode = EndOfGame;
12056 //      ModeHighlight();
12057
12058       /* [AS] Clear current move marker at the end of a game */
12059       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12060
12061       return FALSE;
12062     }
12063
12064     toX = moveList[currentMove][2] - AAA;
12065     toY = moveList[currentMove][3] - ONE;
12066
12067     if (moveList[currentMove][1] == '@') {
12068         if (appData.highlightLastMove) {
12069             SetHighlights(-1, -1, toX, toY);
12070         }
12071     } else {
12072         int viaX = moveList[currentMove][5] - AAA;
12073         int viaY = moveList[currentMove][6] - ONE;
12074         fromX = moveList[currentMove][0] - AAA;
12075         fromY = moveList[currentMove][1] - ONE;
12076
12077         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12078
12079         if(moveList[currentMove][4] == ';') { // multi-leg
12080             ChessSquare piece = boards[currentMove][viaY][viaX];
12081             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12082             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12083             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12084             boards[currentMove][viaY][viaX] = piece;
12085         } else
12086         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12087
12088         if (appData.highlightLastMove) {
12089             SetHighlights(fromX, fromY, toX, toY);
12090         }
12091     }
12092     DisplayMove(currentMove);
12093     SendMoveToProgram(currentMove++, &first);
12094     DisplayBothClocks();
12095     DrawPosition(FALSE, boards[currentMove]);
12096     // [HGM] PV info: always display, routine tests if empty
12097     DisplayComment(currentMove - 1, commentList[currentMove]);
12098     return TRUE;
12099 }
12100
12101
12102 int
12103 LoadGameOneMove (ChessMove readAhead)
12104 {
12105     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12106     char promoChar = NULLCHAR;
12107     ChessMove moveType;
12108     char move[MSG_SIZ];
12109     char *p, *q;
12110
12111     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12112         gameMode != AnalyzeMode && gameMode != Training) {
12113         gameFileFP = NULL;
12114         return FALSE;
12115     }
12116
12117     yyboardindex = forwardMostMove;
12118     if (readAhead != EndOfFile) {
12119       moveType = readAhead;
12120     } else {
12121       if (gameFileFP == NULL)
12122           return FALSE;
12123       moveType = (ChessMove) Myylex();
12124     }
12125
12126     done = FALSE;
12127     switch (moveType) {
12128       case Comment:
12129         if (appData.debugMode)
12130           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12131         p = yy_text;
12132
12133         /* append the comment but don't display it */
12134         AppendComment(currentMove, p, FALSE);
12135         return TRUE;
12136
12137       case WhiteCapturesEnPassant:
12138       case BlackCapturesEnPassant:
12139       case WhitePromotion:
12140       case BlackPromotion:
12141       case WhiteNonPromotion:
12142       case BlackNonPromotion:
12143       case NormalMove:
12144       case FirstLeg:
12145       case WhiteKingSideCastle:
12146       case WhiteQueenSideCastle:
12147       case BlackKingSideCastle:
12148       case BlackQueenSideCastle:
12149       case WhiteKingSideCastleWild:
12150       case WhiteQueenSideCastleWild:
12151       case BlackKingSideCastleWild:
12152       case BlackQueenSideCastleWild:
12153       /* PUSH Fabien */
12154       case WhiteHSideCastleFR:
12155       case WhiteASideCastleFR:
12156       case BlackHSideCastleFR:
12157       case BlackASideCastleFR:
12158       /* POP Fabien */
12159         if (appData.debugMode)
12160           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12161         fromX = currentMoveString[0] - AAA;
12162         fromY = currentMoveString[1] - ONE;
12163         toX = currentMoveString[2] - AAA;
12164         toY = currentMoveString[3] - ONE;
12165         promoChar = currentMoveString[4];
12166         if(promoChar == ';') promoChar = currentMoveString[7];
12167         break;
12168
12169       case WhiteDrop:
12170       case BlackDrop:
12171         if (appData.debugMode)
12172           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12173         fromX = moveType == WhiteDrop ?
12174           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12175         (int) CharToPiece(ToLower(currentMoveString[0]));
12176         fromY = DROP_RANK;
12177         toX = currentMoveString[2] - AAA;
12178         toY = currentMoveString[3] - ONE;
12179         break;
12180
12181       case WhiteWins:
12182       case BlackWins:
12183       case GameIsDrawn:
12184       case GameUnfinished:
12185         if (appData.debugMode)
12186           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12187         p = strchr(yy_text, '{');
12188         if (p == NULL) p = strchr(yy_text, '(');
12189         if (p == NULL) {
12190             p = yy_text;
12191             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12192         } else {
12193             q = strchr(p, *p == '{' ? '}' : ')');
12194             if (q != NULL) *q = NULLCHAR;
12195             p++;
12196         }
12197         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12198         GameEnds(moveType, p, GE_FILE);
12199         done = TRUE;
12200         if (cmailMsgLoaded) {
12201             ClearHighlights();
12202             flipView = WhiteOnMove(currentMove);
12203             if (moveType == GameUnfinished) flipView = !flipView;
12204             if (appData.debugMode)
12205               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12206         }
12207         break;
12208
12209       case EndOfFile:
12210         if (appData.debugMode)
12211           fprintf(debugFP, "Parser hit end of file\n");
12212         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12213           case MT_NONE:
12214           case MT_CHECK:
12215             break;
12216           case MT_CHECKMATE:
12217           case MT_STAINMATE:
12218             if (WhiteOnMove(currentMove)) {
12219                 GameEnds(BlackWins, "Black mates", GE_FILE);
12220             } else {
12221                 GameEnds(WhiteWins, "White mates", GE_FILE);
12222             }
12223             break;
12224           case MT_STALEMATE:
12225             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12226             break;
12227         }
12228         done = TRUE;
12229         break;
12230
12231       case MoveNumberOne:
12232         if (lastLoadGameStart == GNUChessGame) {
12233             /* GNUChessGames have numbers, but they aren't move numbers */
12234             if (appData.debugMode)
12235               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12236                       yy_text, (int) moveType);
12237             return LoadGameOneMove(EndOfFile); /* tail recursion */
12238         }
12239         /* else fall thru */
12240
12241       case XBoardGame:
12242       case GNUChessGame:
12243       case PGNTag:
12244         /* Reached start of next game in file */
12245         if (appData.debugMode)
12246           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12247         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12248           case MT_NONE:
12249           case MT_CHECK:
12250             break;
12251           case MT_CHECKMATE:
12252           case MT_STAINMATE:
12253             if (WhiteOnMove(currentMove)) {
12254                 GameEnds(BlackWins, "Black mates", GE_FILE);
12255             } else {
12256                 GameEnds(WhiteWins, "White mates", GE_FILE);
12257             }
12258             break;
12259           case MT_STALEMATE:
12260             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12261             break;
12262         }
12263         done = TRUE;
12264         break;
12265
12266       case PositionDiagram:     /* should not happen; ignore */
12267       case ElapsedTime:         /* ignore */
12268       case NAG:                 /* ignore */
12269         if (appData.debugMode)
12270           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12271                   yy_text, (int) moveType);
12272         return LoadGameOneMove(EndOfFile); /* tail recursion */
12273
12274       case IllegalMove:
12275         if (appData.testLegality) {
12276             if (appData.debugMode)
12277               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12278             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12279                     (forwardMostMove / 2) + 1,
12280                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12281             DisplayError(move, 0);
12282             done = TRUE;
12283         } else {
12284             if (appData.debugMode)
12285               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12286                       yy_text, currentMoveString);
12287             if(currentMoveString[1] == '@') {
12288                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12289                 fromY = DROP_RANK;
12290             } else {
12291                 fromX = currentMoveString[0] - AAA;
12292                 fromY = currentMoveString[1] - ONE;
12293             }
12294             toX = currentMoveString[2] - AAA;
12295             toY = currentMoveString[3] - ONE;
12296             promoChar = currentMoveString[4];
12297         }
12298         break;
12299
12300       case AmbiguousMove:
12301         if (appData.debugMode)
12302           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12303         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12304                 (forwardMostMove / 2) + 1,
12305                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12306         DisplayError(move, 0);
12307         done = TRUE;
12308         break;
12309
12310       default:
12311       case ImpossibleMove:
12312         if (appData.debugMode)
12313           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12314         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12315                 (forwardMostMove / 2) + 1,
12316                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12317         DisplayError(move, 0);
12318         done = TRUE;
12319         break;
12320     }
12321
12322     if (done) {
12323         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12324             DrawPosition(FALSE, boards[currentMove]);
12325             DisplayBothClocks();
12326             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12327               DisplayComment(currentMove - 1, commentList[currentMove]);
12328         }
12329         (void) StopLoadGameTimer();
12330         gameFileFP = NULL;
12331         cmailOldMove = forwardMostMove;
12332         return FALSE;
12333     } else {
12334         /* currentMoveString is set as a side-effect of yylex */
12335
12336         thinkOutput[0] = NULLCHAR;
12337         MakeMove(fromX, fromY, toX, toY, promoChar);
12338         killX = killY = -1; // [HGM] lion: used up
12339         currentMove = forwardMostMove;
12340         return TRUE;
12341     }
12342 }
12343
12344 /* Load the nth game from the given file */
12345 int
12346 LoadGameFromFile (char *filename, int n, char *title, int useList)
12347 {
12348     FILE *f;
12349     char buf[MSG_SIZ];
12350
12351     if (strcmp(filename, "-") == 0) {
12352         f = stdin;
12353         title = "stdin";
12354     } else {
12355         f = fopen(filename, "rb");
12356         if (f == NULL) {
12357           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12358             DisplayError(buf, errno);
12359             return FALSE;
12360         }
12361     }
12362     if (fseek(f, 0, 0) == -1) {
12363         /* f is not seekable; probably a pipe */
12364         useList = FALSE;
12365     }
12366     if (useList && n == 0) {
12367         int error = GameListBuild(f);
12368         if (error) {
12369             DisplayError(_("Cannot build game list"), error);
12370         } else if (!ListEmpty(&gameList) &&
12371                    ((ListGame *) gameList.tailPred)->number > 1) {
12372             GameListPopUp(f, title);
12373             return TRUE;
12374         }
12375         GameListDestroy();
12376         n = 1;
12377     }
12378     if (n == 0) n = 1;
12379     return LoadGame(f, n, title, FALSE);
12380 }
12381
12382
12383 void
12384 MakeRegisteredMove ()
12385 {
12386     int fromX, fromY, toX, toY;
12387     char promoChar;
12388     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12389         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12390           case CMAIL_MOVE:
12391           case CMAIL_DRAW:
12392             if (appData.debugMode)
12393               fprintf(debugFP, "Restoring %s for game %d\n",
12394                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12395
12396             thinkOutput[0] = NULLCHAR;
12397             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12398             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12399             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12400             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12401             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12402             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12403             MakeMove(fromX, fromY, toX, toY, promoChar);
12404             ShowMove(fromX, fromY, toX, toY);
12405
12406             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12407               case MT_NONE:
12408               case MT_CHECK:
12409                 break;
12410
12411               case MT_CHECKMATE:
12412               case MT_STAINMATE:
12413                 if (WhiteOnMove(currentMove)) {
12414                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12415                 } else {
12416                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12417                 }
12418                 break;
12419
12420               case MT_STALEMATE:
12421                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12422                 break;
12423             }
12424
12425             break;
12426
12427           case CMAIL_RESIGN:
12428             if (WhiteOnMove(currentMove)) {
12429                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12430             } else {
12431                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12432             }
12433             break;
12434
12435           case CMAIL_ACCEPT:
12436             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12437             break;
12438
12439           default:
12440             break;
12441         }
12442     }
12443
12444     return;
12445 }
12446
12447 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12448 int
12449 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12450 {
12451     int retVal;
12452
12453     if (gameNumber > nCmailGames) {
12454         DisplayError(_("No more games in this message"), 0);
12455         return FALSE;
12456     }
12457     if (f == lastLoadGameFP) {
12458         int offset = gameNumber - lastLoadGameNumber;
12459         if (offset == 0) {
12460             cmailMsg[0] = NULLCHAR;
12461             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12462                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12463                 nCmailMovesRegistered--;
12464             }
12465             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12466             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12467                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12468             }
12469         } else {
12470             if (! RegisterMove()) return FALSE;
12471         }
12472     }
12473
12474     retVal = LoadGame(f, gameNumber, title, useList);
12475
12476     /* Make move registered during previous look at this game, if any */
12477     MakeRegisteredMove();
12478
12479     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12480         commentList[currentMove]
12481           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12482         DisplayComment(currentMove - 1, commentList[currentMove]);
12483     }
12484
12485     return retVal;
12486 }
12487
12488 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12489 int
12490 ReloadGame (int offset)
12491 {
12492     int gameNumber = lastLoadGameNumber + offset;
12493     if (lastLoadGameFP == NULL) {
12494         DisplayError(_("No game has been loaded yet"), 0);
12495         return FALSE;
12496     }
12497     if (gameNumber <= 0) {
12498         DisplayError(_("Can't back up any further"), 0);
12499         return FALSE;
12500     }
12501     if (cmailMsgLoaded) {
12502         return CmailLoadGame(lastLoadGameFP, gameNumber,
12503                              lastLoadGameTitle, lastLoadGameUseList);
12504     } else {
12505         return LoadGame(lastLoadGameFP, gameNumber,
12506                         lastLoadGameTitle, lastLoadGameUseList);
12507     }
12508 }
12509
12510 int keys[EmptySquare+1];
12511
12512 int
12513 PositionMatches (Board b1, Board b2)
12514 {
12515     int r, f, sum=0;
12516     switch(appData.searchMode) {
12517         case 1: return CompareWithRights(b1, b2);
12518         case 2:
12519             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12520                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12521             }
12522             return TRUE;
12523         case 3:
12524             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12525               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12526                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12527             }
12528             return sum==0;
12529         case 4:
12530             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12531                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12532             }
12533             return sum==0;
12534     }
12535     return TRUE;
12536 }
12537
12538 #define Q_PROMO  4
12539 #define Q_EP     3
12540 #define Q_BCASTL 2
12541 #define Q_WCASTL 1
12542
12543 int pieceList[256], quickBoard[256];
12544 ChessSquare pieceType[256] = { EmptySquare };
12545 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12546 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12547 int soughtTotal, turn;
12548 Boolean epOK, flipSearch;
12549
12550 typedef struct {
12551     unsigned char piece, to;
12552 } Move;
12553
12554 #define DSIZE (250000)
12555
12556 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12557 Move *moveDatabase = initialSpace;
12558 unsigned int movePtr, dataSize = DSIZE;
12559
12560 int
12561 MakePieceList (Board board, int *counts)
12562 {
12563     int r, f, n=Q_PROMO, total=0;
12564     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12565     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12566         int sq = f + (r<<4);
12567         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12568             quickBoard[sq] = ++n;
12569             pieceList[n] = sq;
12570             pieceType[n] = board[r][f];
12571             counts[board[r][f]]++;
12572             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12573             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12574             total++;
12575         }
12576     }
12577     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12578     return total;
12579 }
12580
12581 void
12582 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12583 {
12584     int sq = fromX + (fromY<<4);
12585     int piece = quickBoard[sq], rook;
12586     quickBoard[sq] = 0;
12587     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12588     if(piece == pieceList[1] && fromY == toY) {
12589       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12590         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12591         moveDatabase[movePtr++].piece = Q_WCASTL;
12592         quickBoard[sq] = piece;
12593         piece = quickBoard[from]; quickBoard[from] = 0;
12594         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12595       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12596         quickBoard[sq] = 0; // remove Rook
12597         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12598         moveDatabase[movePtr++].piece = Q_WCASTL;
12599         quickBoard[sq] = pieceList[1]; // put King
12600         piece = rook;
12601         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12602       }
12603     } else
12604     if(piece == pieceList[2] && fromY == toY) {
12605       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12606         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12607         moveDatabase[movePtr++].piece = Q_BCASTL;
12608         quickBoard[sq] = piece;
12609         piece = quickBoard[from]; quickBoard[from] = 0;
12610         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12611       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12612         quickBoard[sq] = 0; // remove Rook
12613         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12614         moveDatabase[movePtr++].piece = Q_BCASTL;
12615         quickBoard[sq] = pieceList[2]; // put King
12616         piece = rook;
12617         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12618       }
12619     } else
12620     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12621         quickBoard[(fromY<<4)+toX] = 0;
12622         moveDatabase[movePtr].piece = Q_EP;
12623         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12624         moveDatabase[movePtr].to = sq;
12625     } else
12626     if(promoPiece != pieceType[piece]) {
12627         moveDatabase[movePtr++].piece = Q_PROMO;
12628         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12629     }
12630     moveDatabase[movePtr].piece = piece;
12631     quickBoard[sq] = piece;
12632     movePtr++;
12633 }
12634
12635 int
12636 PackGame (Board board)
12637 {
12638     Move *newSpace = NULL;
12639     moveDatabase[movePtr].piece = 0; // terminate previous game
12640     if(movePtr > dataSize) {
12641         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12642         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12643         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12644         if(newSpace) {
12645             int i;
12646             Move *p = moveDatabase, *q = newSpace;
12647             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12648             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12649             moveDatabase = newSpace;
12650         } else { // calloc failed, we must be out of memory. Too bad...
12651             dataSize = 0; // prevent calloc events for all subsequent games
12652             return 0;     // and signal this one isn't cached
12653         }
12654     }
12655     movePtr++;
12656     MakePieceList(board, counts);
12657     return movePtr;
12658 }
12659
12660 int
12661 QuickCompare (Board board, int *minCounts, int *maxCounts)
12662 {   // compare according to search mode
12663     int r, f;
12664     switch(appData.searchMode)
12665     {
12666       case 1: // exact position match
12667         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12668         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12669             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12670         }
12671         break;
12672       case 2: // can have extra material on empty squares
12673         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12674             if(board[r][f] == EmptySquare) continue;
12675             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12676         }
12677         break;
12678       case 3: // material with exact Pawn structure
12679         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12680             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12681             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12682         } // fall through to material comparison
12683       case 4: // exact material
12684         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12685         break;
12686       case 6: // material range with given imbalance
12687         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12688         // fall through to range comparison
12689       case 5: // material range
12690         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12691     }
12692     return TRUE;
12693 }
12694
12695 int
12696 QuickScan (Board board, Move *move)
12697 {   // reconstruct game,and compare all positions in it
12698     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12699     do {
12700         int piece = move->piece;
12701         int to = move->to, from = pieceList[piece];
12702         if(found < 0) { // if already found just scan to game end for final piece count
12703           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12704            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12705            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12706                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12707             ) {
12708             static int lastCounts[EmptySquare+1];
12709             int i;
12710             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12711             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12712           } else stretch = 0;
12713           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12714           if(found >= 0 && !appData.minPieces) return found;
12715         }
12716         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12717           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12718           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12719             piece = (++move)->piece;
12720             from = pieceList[piece];
12721             counts[pieceType[piece]]--;
12722             pieceType[piece] = (ChessSquare) move->to;
12723             counts[move->to]++;
12724           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12725             counts[pieceType[quickBoard[to]]]--;
12726             quickBoard[to] = 0; total--;
12727             move++;
12728             continue;
12729           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12730             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12731             from  = pieceList[piece]; // so this must be King
12732             quickBoard[from] = 0;
12733             pieceList[piece] = to;
12734             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12735             quickBoard[from] = 0; // rook
12736             quickBoard[to] = piece;
12737             to = move->to; piece = move->piece;
12738             goto aftercastle;
12739           }
12740         }
12741         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12742         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12743         quickBoard[from] = 0;
12744       aftercastle:
12745         quickBoard[to] = piece;
12746         pieceList[piece] = to;
12747         cnt++; turn ^= 3;
12748         move++;
12749     } while(1);
12750 }
12751
12752 void
12753 InitSearch ()
12754 {
12755     int r, f;
12756     flipSearch = FALSE;
12757     CopyBoard(soughtBoard, boards[currentMove]);
12758     soughtTotal = MakePieceList(soughtBoard, maxSought);
12759     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12760     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12761     CopyBoard(reverseBoard, boards[currentMove]);
12762     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12763         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12764         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12765         reverseBoard[r][f] = piece;
12766     }
12767     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12768     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12769     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12770                  || (boards[currentMove][CASTLING][2] == NoRights ||
12771                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12772                  && (boards[currentMove][CASTLING][5] == NoRights ||
12773                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12774       ) {
12775         flipSearch = TRUE;
12776         CopyBoard(flipBoard, soughtBoard);
12777         CopyBoard(rotateBoard, reverseBoard);
12778         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12779             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12780             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12781         }
12782     }
12783     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12784     if(appData.searchMode >= 5) {
12785         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12786         MakePieceList(soughtBoard, minSought);
12787         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12788     }
12789     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12790         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12791 }
12792
12793 GameInfo dummyInfo;
12794 static int creatingBook;
12795
12796 int
12797 GameContainsPosition (FILE *f, ListGame *lg)
12798 {
12799     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12800     int fromX, fromY, toX, toY;
12801     char promoChar;
12802     static int initDone=FALSE;
12803
12804     // weed out games based on numerical tag comparison
12805     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12806     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12807     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12808     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12809     if(!initDone) {
12810         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12811         initDone = TRUE;
12812     }
12813     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12814     else CopyBoard(boards[scratch], initialPosition); // default start position
12815     if(lg->moves) {
12816         turn = btm + 1;
12817         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12818         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12819     }
12820     if(btm) plyNr++;
12821     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12822     fseek(f, lg->offset, 0);
12823     yynewfile(f);
12824     while(1) {
12825         yyboardindex = scratch;
12826         quickFlag = plyNr+1;
12827         next = Myylex();
12828         quickFlag = 0;
12829         switch(next) {
12830             case PGNTag:
12831                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12832             default:
12833                 continue;
12834
12835             case XBoardGame:
12836             case GNUChessGame:
12837                 if(plyNr) return -1; // after we have seen moves, this is for new game
12838               continue;
12839
12840             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12841             case ImpossibleMove:
12842             case WhiteWins: // game ends here with these four
12843             case BlackWins:
12844             case GameIsDrawn:
12845             case GameUnfinished:
12846                 return -1;
12847
12848             case IllegalMove:
12849                 if(appData.testLegality) return -1;
12850             case WhiteCapturesEnPassant:
12851             case BlackCapturesEnPassant:
12852             case WhitePromotion:
12853             case BlackPromotion:
12854             case WhiteNonPromotion:
12855             case BlackNonPromotion:
12856             case NormalMove:
12857             case FirstLeg:
12858             case WhiteKingSideCastle:
12859             case WhiteQueenSideCastle:
12860             case BlackKingSideCastle:
12861             case BlackQueenSideCastle:
12862             case WhiteKingSideCastleWild:
12863             case WhiteQueenSideCastleWild:
12864             case BlackKingSideCastleWild:
12865             case BlackQueenSideCastleWild:
12866             case WhiteHSideCastleFR:
12867             case WhiteASideCastleFR:
12868             case BlackHSideCastleFR:
12869             case BlackASideCastleFR:
12870                 fromX = currentMoveString[0] - AAA;
12871                 fromY = currentMoveString[1] - ONE;
12872                 toX = currentMoveString[2] - AAA;
12873                 toY = currentMoveString[3] - ONE;
12874                 promoChar = currentMoveString[4];
12875                 break;
12876             case WhiteDrop:
12877             case BlackDrop:
12878                 fromX = next == WhiteDrop ?
12879                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12880                   (int) CharToPiece(ToLower(currentMoveString[0]));
12881                 fromY = DROP_RANK;
12882                 toX = currentMoveString[2] - AAA;
12883                 toY = currentMoveString[3] - ONE;
12884                 promoChar = 0;
12885                 break;
12886         }
12887         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12888         plyNr++;
12889         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12890         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12891         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12892         if(appData.findMirror) {
12893             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12894             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12895         }
12896     }
12897 }
12898
12899 /* Load the nth game from open file f */
12900 int
12901 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12902 {
12903     ChessMove cm;
12904     char buf[MSG_SIZ];
12905     int gn = gameNumber;
12906     ListGame *lg = NULL;
12907     int numPGNTags = 0;
12908     int err, pos = -1;
12909     GameMode oldGameMode;
12910     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12911     char oldName[MSG_SIZ];
12912
12913     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12914
12915     if (appData.debugMode)
12916         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12917
12918     if (gameMode == Training )
12919         SetTrainingModeOff();
12920
12921     oldGameMode = gameMode;
12922     if (gameMode != BeginningOfGame) {
12923       Reset(FALSE, TRUE);
12924     }
12925     killX = killY = -1; // [HGM] lion: in case we did not Reset
12926
12927     gameFileFP = f;
12928     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12929         fclose(lastLoadGameFP);
12930     }
12931
12932     if (useList) {
12933         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12934
12935         if (lg) {
12936             fseek(f, lg->offset, 0);
12937             GameListHighlight(gameNumber);
12938             pos = lg->position;
12939             gn = 1;
12940         }
12941         else {
12942             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12943               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12944             else
12945             DisplayError(_("Game number out of range"), 0);
12946             return FALSE;
12947         }
12948     } else {
12949         GameListDestroy();
12950         if (fseek(f, 0, 0) == -1) {
12951             if (f == lastLoadGameFP ?
12952                 gameNumber == lastLoadGameNumber + 1 :
12953                 gameNumber == 1) {
12954                 gn = 1;
12955             } else {
12956                 DisplayError(_("Can't seek on game file"), 0);
12957                 return FALSE;
12958             }
12959         }
12960     }
12961     lastLoadGameFP = f;
12962     lastLoadGameNumber = gameNumber;
12963     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12964     lastLoadGameUseList = useList;
12965
12966     yynewfile(f);
12967
12968     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12969       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12970                 lg->gameInfo.black);
12971             DisplayTitle(buf);
12972     } else if (*title != NULLCHAR) {
12973         if (gameNumber > 1) {
12974           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12975             DisplayTitle(buf);
12976         } else {
12977             DisplayTitle(title);
12978         }
12979     }
12980
12981     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12982         gameMode = PlayFromGameFile;
12983         ModeHighlight();
12984     }
12985
12986     currentMove = forwardMostMove = backwardMostMove = 0;
12987     CopyBoard(boards[0], initialPosition);
12988     StopClocks();
12989
12990     /*
12991      * Skip the first gn-1 games in the file.
12992      * Also skip over anything that precedes an identifiable
12993      * start of game marker, to avoid being confused by
12994      * garbage at the start of the file.  Currently
12995      * recognized start of game markers are the move number "1",
12996      * the pattern "gnuchess .* game", the pattern
12997      * "^[#;%] [^ ]* game file", and a PGN tag block.
12998      * A game that starts with one of the latter two patterns
12999      * will also have a move number 1, possibly
13000      * following a position diagram.
13001      * 5-4-02: Let's try being more lenient and allowing a game to
13002      * start with an unnumbered move.  Does that break anything?
13003      */
13004     cm = lastLoadGameStart = EndOfFile;
13005     while (gn > 0) {
13006         yyboardindex = forwardMostMove;
13007         cm = (ChessMove) Myylex();
13008         switch (cm) {
13009           case EndOfFile:
13010             if (cmailMsgLoaded) {
13011                 nCmailGames = CMAIL_MAX_GAMES - gn;
13012             } else {
13013                 Reset(TRUE, TRUE);
13014                 DisplayError(_("Game not found in file"), 0);
13015             }
13016             return FALSE;
13017
13018           case GNUChessGame:
13019           case XBoardGame:
13020             gn--;
13021             lastLoadGameStart = cm;
13022             break;
13023
13024           case MoveNumberOne:
13025             switch (lastLoadGameStart) {
13026               case GNUChessGame:
13027               case XBoardGame:
13028               case PGNTag:
13029                 break;
13030               case MoveNumberOne:
13031               case EndOfFile:
13032                 gn--;           /* count this game */
13033                 lastLoadGameStart = cm;
13034                 break;
13035               default:
13036                 /* impossible */
13037                 break;
13038             }
13039             break;
13040
13041           case PGNTag:
13042             switch (lastLoadGameStart) {
13043               case GNUChessGame:
13044               case PGNTag:
13045               case MoveNumberOne:
13046               case EndOfFile:
13047                 gn--;           /* count this game */
13048                 lastLoadGameStart = cm;
13049                 break;
13050               case XBoardGame:
13051                 lastLoadGameStart = cm; /* game counted already */
13052                 break;
13053               default:
13054                 /* impossible */
13055                 break;
13056             }
13057             if (gn > 0) {
13058                 do {
13059                     yyboardindex = forwardMostMove;
13060                     cm = (ChessMove) Myylex();
13061                 } while (cm == PGNTag || cm == Comment);
13062             }
13063             break;
13064
13065           case WhiteWins:
13066           case BlackWins:
13067           case GameIsDrawn:
13068             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13069                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13070                     != CMAIL_OLD_RESULT) {
13071                     nCmailResults ++ ;
13072                     cmailResult[  CMAIL_MAX_GAMES
13073                                 - gn - 1] = CMAIL_OLD_RESULT;
13074                 }
13075             }
13076             break;
13077
13078           case NormalMove:
13079           case FirstLeg:
13080             /* Only a NormalMove can be at the start of a game
13081              * without a position diagram. */
13082             if (lastLoadGameStart == EndOfFile ) {
13083               gn--;
13084               lastLoadGameStart = MoveNumberOne;
13085             }
13086             break;
13087
13088           default:
13089             break;
13090         }
13091     }
13092
13093     if (appData.debugMode)
13094       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13095
13096     if (cm == XBoardGame) {
13097         /* Skip any header junk before position diagram and/or move 1 */
13098         for (;;) {
13099             yyboardindex = forwardMostMove;
13100             cm = (ChessMove) Myylex();
13101
13102             if (cm == EndOfFile ||
13103                 cm == GNUChessGame || cm == XBoardGame) {
13104                 /* Empty game; pretend end-of-file and handle later */
13105                 cm = EndOfFile;
13106                 break;
13107             }
13108
13109             if (cm == MoveNumberOne || cm == PositionDiagram ||
13110                 cm == PGNTag || cm == Comment)
13111               break;
13112         }
13113     } else if (cm == GNUChessGame) {
13114         if (gameInfo.event != NULL) {
13115             free(gameInfo.event);
13116         }
13117         gameInfo.event = StrSave(yy_text);
13118     }
13119
13120     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13121     while (cm == PGNTag) {
13122         if (appData.debugMode)
13123           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13124         err = ParsePGNTag(yy_text, &gameInfo);
13125         if (!err) numPGNTags++;
13126
13127         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13128         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13129             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13130             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13131             InitPosition(TRUE);
13132             oldVariant = gameInfo.variant;
13133             if (appData.debugMode)
13134               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13135         }
13136
13137
13138         if (gameInfo.fen != NULL) {
13139           Board initial_position;
13140           startedFromSetupPosition = TRUE;
13141           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13142             Reset(TRUE, TRUE);
13143             DisplayError(_("Bad FEN position in file"), 0);
13144             return FALSE;
13145           }
13146           CopyBoard(boards[0], initial_position);
13147           if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13148             CopyBoard(initialPosition, initial_position);
13149           if (blackPlaysFirst) {
13150             currentMove = forwardMostMove = backwardMostMove = 1;
13151             CopyBoard(boards[1], initial_position);
13152             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13153             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13154             timeRemaining[0][1] = whiteTimeRemaining;
13155             timeRemaining[1][1] = blackTimeRemaining;
13156             if (commentList[0] != NULL) {
13157               commentList[1] = commentList[0];
13158               commentList[0] = NULL;
13159             }
13160           } else {
13161             currentMove = forwardMostMove = backwardMostMove = 0;
13162           }
13163           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13164           {   int i;
13165               initialRulePlies = FENrulePlies;
13166               for( i=0; i< nrCastlingRights; i++ )
13167                   initialRights[i] = initial_position[CASTLING][i];
13168           }
13169           yyboardindex = forwardMostMove;
13170           free(gameInfo.fen);
13171           gameInfo.fen = NULL;
13172         }
13173
13174         yyboardindex = forwardMostMove;
13175         cm = (ChessMove) Myylex();
13176
13177         /* Handle comments interspersed among the tags */
13178         while (cm == Comment) {
13179             char *p;
13180             if (appData.debugMode)
13181               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13182             p = yy_text;
13183             AppendComment(currentMove, p, FALSE);
13184             yyboardindex = forwardMostMove;
13185             cm = (ChessMove) Myylex();
13186         }
13187     }
13188
13189     /* don't rely on existence of Event tag since if game was
13190      * pasted from clipboard the Event tag may not exist
13191      */
13192     if (numPGNTags > 0){
13193         char *tags;
13194         if (gameInfo.variant == VariantNormal) {
13195           VariantClass v = StringToVariant(gameInfo.event);
13196           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13197           if(v < VariantShogi) gameInfo.variant = v;
13198         }
13199         if (!matchMode) {
13200           if( appData.autoDisplayTags ) {
13201             tags = PGNTags(&gameInfo);
13202             TagsPopUp(tags, CmailMsg());
13203             free(tags);
13204           }
13205         }
13206     } else {
13207         /* Make something up, but don't display it now */
13208         SetGameInfo();
13209         TagsPopDown();
13210     }
13211
13212     if (cm == PositionDiagram) {
13213         int i, j;
13214         char *p;
13215         Board initial_position;
13216
13217         if (appData.debugMode)
13218           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13219
13220         if (!startedFromSetupPosition) {
13221             p = yy_text;
13222             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13223               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13224                 switch (*p) {
13225                   case '{':
13226                   case '[':
13227                   case '-':
13228                   case ' ':
13229                   case '\t':
13230                   case '\n':
13231                   case '\r':
13232                     break;
13233                   default:
13234                     initial_position[i][j++] = CharToPiece(*p);
13235                     break;
13236                 }
13237             while (*p == ' ' || *p == '\t' ||
13238                    *p == '\n' || *p == '\r') p++;
13239
13240             if (strncmp(p, "black", strlen("black"))==0)
13241               blackPlaysFirst = TRUE;
13242             else
13243               blackPlaysFirst = FALSE;
13244             startedFromSetupPosition = TRUE;
13245
13246             CopyBoard(boards[0], initial_position);
13247             if (blackPlaysFirst) {
13248                 currentMove = forwardMostMove = backwardMostMove = 1;
13249                 CopyBoard(boards[1], initial_position);
13250                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13251                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13252                 timeRemaining[0][1] = whiteTimeRemaining;
13253                 timeRemaining[1][1] = blackTimeRemaining;
13254                 if (commentList[0] != NULL) {
13255                     commentList[1] = commentList[0];
13256                     commentList[0] = NULL;
13257                 }
13258             } else {
13259                 currentMove = forwardMostMove = backwardMostMove = 0;
13260             }
13261         }
13262         yyboardindex = forwardMostMove;
13263         cm = (ChessMove) Myylex();
13264     }
13265
13266   if(!creatingBook) {
13267     if (first.pr == NoProc) {
13268         StartChessProgram(&first);
13269     }
13270     InitChessProgram(&first, FALSE);
13271     if(gameInfo.variant == VariantUnknown && *oldName) {
13272         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13273         gameInfo.variant = v;
13274     }
13275     SendToProgram("force\n", &first);
13276     if (startedFromSetupPosition) {
13277         SendBoard(&first, forwardMostMove);
13278     if (appData.debugMode) {
13279         fprintf(debugFP, "Load Game\n");
13280     }
13281         DisplayBothClocks();
13282     }
13283   }
13284
13285     /* [HGM] server: flag to write setup moves in broadcast file as one */
13286     loadFlag = appData.suppressLoadMoves;
13287
13288     while (cm == Comment) {
13289         char *p;
13290         if (appData.debugMode)
13291           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13292         p = yy_text;
13293         AppendComment(currentMove, p, FALSE);
13294         yyboardindex = forwardMostMove;
13295         cm = (ChessMove) Myylex();
13296     }
13297
13298     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13299         cm == WhiteWins || cm == BlackWins ||
13300         cm == GameIsDrawn || cm == GameUnfinished) {
13301         DisplayMessage("", _("No moves in game"));
13302         if (cmailMsgLoaded) {
13303             if (appData.debugMode)
13304               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13305             ClearHighlights();
13306             flipView = FALSE;
13307         }
13308         DrawPosition(FALSE, boards[currentMove]);
13309         DisplayBothClocks();
13310         gameMode = EditGame;
13311         ModeHighlight();
13312         gameFileFP = NULL;
13313         cmailOldMove = 0;
13314         return TRUE;
13315     }
13316
13317     // [HGM] PV info: routine tests if comment empty
13318     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13319         DisplayComment(currentMove - 1, commentList[currentMove]);
13320     }
13321     if (!matchMode && appData.timeDelay != 0)
13322       DrawPosition(FALSE, boards[currentMove]);
13323
13324     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13325       programStats.ok_to_send = 1;
13326     }
13327
13328     /* if the first token after the PGN tags is a move
13329      * and not move number 1, retrieve it from the parser
13330      */
13331     if (cm != MoveNumberOne)
13332         LoadGameOneMove(cm);
13333
13334     /* load the remaining moves from the file */
13335     while (LoadGameOneMove(EndOfFile)) {
13336       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13337       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13338     }
13339
13340     /* rewind to the start of the game */
13341     currentMove = backwardMostMove;
13342
13343     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13344
13345     if (oldGameMode == AnalyzeFile) {
13346       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13347       AnalyzeFileEvent();
13348     } else
13349     if (oldGameMode == AnalyzeMode) {
13350       AnalyzeFileEvent();
13351     }
13352
13353     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13354         long int w, b; // [HGM] adjourn: restore saved clock times
13355         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13356         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13357             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13358             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13359         }
13360     }
13361
13362     if(creatingBook) return TRUE;
13363     if (!matchMode && pos > 0) {
13364         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13365     } else
13366     if (matchMode || appData.timeDelay == 0) {
13367       ToEndEvent();
13368     } else if (appData.timeDelay > 0) {
13369       AutoPlayGameLoop();
13370     }
13371
13372     if (appData.debugMode)
13373         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13374
13375     loadFlag = 0; /* [HGM] true game starts */
13376     return TRUE;
13377 }
13378
13379 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13380 int
13381 ReloadPosition (int offset)
13382 {
13383     int positionNumber = lastLoadPositionNumber + offset;
13384     if (lastLoadPositionFP == NULL) {
13385         DisplayError(_("No position has been loaded yet"), 0);
13386         return FALSE;
13387     }
13388     if (positionNumber <= 0) {
13389         DisplayError(_("Can't back up any further"), 0);
13390         return FALSE;
13391     }
13392     return LoadPosition(lastLoadPositionFP, positionNumber,
13393                         lastLoadPositionTitle);
13394 }
13395
13396 /* Load the nth position from the given file */
13397 int
13398 LoadPositionFromFile (char *filename, int n, char *title)
13399 {
13400     FILE *f;
13401     char buf[MSG_SIZ];
13402
13403     if (strcmp(filename, "-") == 0) {
13404         return LoadPosition(stdin, n, "stdin");
13405     } else {
13406         f = fopen(filename, "rb");
13407         if (f == NULL) {
13408             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13409             DisplayError(buf, errno);
13410             return FALSE;
13411         } else {
13412             return LoadPosition(f, n, title);
13413         }
13414     }
13415 }
13416
13417 /* Load the nth position from the given open file, and close it */
13418 int
13419 LoadPosition (FILE *f, int positionNumber, char *title)
13420 {
13421     char *p, line[MSG_SIZ];
13422     Board initial_position;
13423     int i, j, fenMode, pn;
13424
13425     if (gameMode == Training )
13426         SetTrainingModeOff();
13427
13428     if (gameMode != BeginningOfGame) {
13429         Reset(FALSE, TRUE);
13430     }
13431     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13432         fclose(lastLoadPositionFP);
13433     }
13434     if (positionNumber == 0) positionNumber = 1;
13435     lastLoadPositionFP = f;
13436     lastLoadPositionNumber = positionNumber;
13437     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13438     if (first.pr == NoProc && !appData.noChessProgram) {
13439       StartChessProgram(&first);
13440       InitChessProgram(&first, FALSE);
13441     }
13442     pn = positionNumber;
13443     if (positionNumber < 0) {
13444         /* Negative position number means to seek to that byte offset */
13445         if (fseek(f, -positionNumber, 0) == -1) {
13446             DisplayError(_("Can't seek on position file"), 0);
13447             return FALSE;
13448         };
13449         pn = 1;
13450     } else {
13451         if (fseek(f, 0, 0) == -1) {
13452             if (f == lastLoadPositionFP ?
13453                 positionNumber == lastLoadPositionNumber + 1 :
13454                 positionNumber == 1) {
13455                 pn = 1;
13456             } else {
13457                 DisplayError(_("Can't seek on position file"), 0);
13458                 return FALSE;
13459             }
13460         }
13461     }
13462     /* See if this file is FEN or old-style xboard */
13463     if (fgets(line, MSG_SIZ, f) == NULL) {
13464         DisplayError(_("Position not found in file"), 0);
13465         return FALSE;
13466     }
13467     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13468     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13469
13470     if (pn >= 2) {
13471         if (fenMode || line[0] == '#') pn--;
13472         while (pn > 0) {
13473             /* skip positions before number pn */
13474             if (fgets(line, MSG_SIZ, f) == NULL) {
13475                 Reset(TRUE, TRUE);
13476                 DisplayError(_("Position not found in file"), 0);
13477                 return FALSE;
13478             }
13479             if (fenMode || line[0] == '#') pn--;
13480         }
13481     }
13482
13483     if (fenMode) {
13484         char *p;
13485         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13486             DisplayError(_("Bad FEN position in file"), 0);
13487             return FALSE;
13488         }
13489         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13490             sscanf(p+3, "%s", bestMove);
13491         } else *bestMove = NULLCHAR;
13492     } else {
13493         (void) fgets(line, MSG_SIZ, f);
13494         (void) fgets(line, MSG_SIZ, f);
13495
13496         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13497             (void) fgets(line, MSG_SIZ, f);
13498             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13499                 if (*p == ' ')
13500                   continue;
13501                 initial_position[i][j++] = CharToPiece(*p);
13502             }
13503         }
13504
13505         blackPlaysFirst = FALSE;
13506         if (!feof(f)) {
13507             (void) fgets(line, MSG_SIZ, f);
13508             if (strncmp(line, "black", strlen("black"))==0)
13509               blackPlaysFirst = TRUE;
13510         }
13511     }
13512     startedFromSetupPosition = TRUE;
13513
13514     CopyBoard(boards[0], initial_position);
13515     if (blackPlaysFirst) {
13516         currentMove = forwardMostMove = backwardMostMove = 1;
13517         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13518         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13519         CopyBoard(boards[1], initial_position);
13520         DisplayMessage("", _("Black to play"));
13521     } else {
13522         currentMove = forwardMostMove = backwardMostMove = 0;
13523         DisplayMessage("", _("White to play"));
13524     }
13525     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13526     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13527         SendToProgram("force\n", &first);
13528         SendBoard(&first, forwardMostMove);
13529     }
13530     if (appData.debugMode) {
13531 int i, j;
13532   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13533   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13534         fprintf(debugFP, "Load Position\n");
13535     }
13536
13537     if (positionNumber > 1) {
13538       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13539         DisplayTitle(line);
13540     } else {
13541         DisplayTitle(title);
13542     }
13543     gameMode = EditGame;
13544     ModeHighlight();
13545     ResetClocks();
13546     timeRemaining[0][1] = whiteTimeRemaining;
13547     timeRemaining[1][1] = blackTimeRemaining;
13548     DrawPosition(FALSE, boards[currentMove]);
13549
13550     return TRUE;
13551 }
13552
13553
13554 void
13555 CopyPlayerNameIntoFileName (char **dest, char *src)
13556 {
13557     while (*src != NULLCHAR && *src != ',') {
13558         if (*src == ' ') {
13559             *(*dest)++ = '_';
13560             src++;
13561         } else {
13562             *(*dest)++ = *src++;
13563         }
13564     }
13565 }
13566
13567 char *
13568 DefaultFileName (char *ext)
13569 {
13570     static char def[MSG_SIZ];
13571     char *p;
13572
13573     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13574         p = def;
13575         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13576         *p++ = '-';
13577         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13578         *p++ = '.';
13579         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13580     } else {
13581         def[0] = NULLCHAR;
13582     }
13583     return def;
13584 }
13585
13586 /* Save the current game to the given file */
13587 int
13588 SaveGameToFile (char *filename, int append)
13589 {
13590     FILE *f;
13591     char buf[MSG_SIZ];
13592     int result, i, t,tot=0;
13593
13594     if (strcmp(filename, "-") == 0) {
13595         return SaveGame(stdout, 0, NULL);
13596     } else {
13597         for(i=0; i<10; i++) { // upto 10 tries
13598              f = fopen(filename, append ? "a" : "w");
13599              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13600              if(f || errno != 13) break;
13601              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13602              tot += t;
13603         }
13604         if (f == NULL) {
13605             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13606             DisplayError(buf, errno);
13607             return FALSE;
13608         } else {
13609             safeStrCpy(buf, lastMsg, MSG_SIZ);
13610             DisplayMessage(_("Waiting for access to save file"), "");
13611             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13612             DisplayMessage(_("Saving game"), "");
13613             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13614             result = SaveGame(f, 0, NULL);
13615             DisplayMessage(buf, "");
13616             return result;
13617         }
13618     }
13619 }
13620
13621 char *
13622 SavePart (char *str)
13623 {
13624     static char buf[MSG_SIZ];
13625     char *p;
13626
13627     p = strchr(str, ' ');
13628     if (p == NULL) return str;
13629     strncpy(buf, str, p - str);
13630     buf[p - str] = NULLCHAR;
13631     return buf;
13632 }
13633
13634 #define PGN_MAX_LINE 75
13635
13636 #define PGN_SIDE_WHITE  0
13637 #define PGN_SIDE_BLACK  1
13638
13639 static int
13640 FindFirstMoveOutOfBook (int side)
13641 {
13642     int result = -1;
13643
13644     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13645         int index = backwardMostMove;
13646         int has_book_hit = 0;
13647
13648         if( (index % 2) != side ) {
13649             index++;
13650         }
13651
13652         while( index < forwardMostMove ) {
13653             /* Check to see if engine is in book */
13654             int depth = pvInfoList[index].depth;
13655             int score = pvInfoList[index].score;
13656             int in_book = 0;
13657
13658             if( depth <= 2 ) {
13659                 in_book = 1;
13660             }
13661             else if( score == 0 && depth == 63 ) {
13662                 in_book = 1; /* Zappa */
13663             }
13664             else if( score == 2 && depth == 99 ) {
13665                 in_book = 1; /* Abrok */
13666             }
13667
13668             has_book_hit += in_book;
13669
13670             if( ! in_book ) {
13671                 result = index;
13672
13673                 break;
13674             }
13675
13676             index += 2;
13677         }
13678     }
13679
13680     return result;
13681 }
13682
13683 void
13684 GetOutOfBookInfo (char * buf)
13685 {
13686     int oob[2];
13687     int i;
13688     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13689
13690     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13691     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13692
13693     *buf = '\0';
13694
13695     if( oob[0] >= 0 || oob[1] >= 0 ) {
13696         for( i=0; i<2; i++ ) {
13697             int idx = oob[i];
13698
13699             if( idx >= 0 ) {
13700                 if( i > 0 && oob[0] >= 0 ) {
13701                     strcat( buf, "   " );
13702                 }
13703
13704                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13705                 sprintf( buf+strlen(buf), "%s%.2f",
13706                     pvInfoList[idx].score >= 0 ? "+" : "",
13707                     pvInfoList[idx].score / 100.0 );
13708             }
13709         }
13710     }
13711 }
13712
13713 /* Save game in PGN style */
13714 static void
13715 SaveGamePGN2 (FILE *f)
13716 {
13717     int i, offset, linelen, newblock;
13718 //    char *movetext;
13719     char numtext[32];
13720     int movelen, numlen, blank;
13721     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13722
13723     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13724
13725     PrintPGNTags(f, &gameInfo);
13726
13727     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13728
13729     if (backwardMostMove > 0 || startedFromSetupPosition) {
13730         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13731         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13732         fprintf(f, "\n{--------------\n");
13733         PrintPosition(f, backwardMostMove);
13734         fprintf(f, "--------------}\n");
13735         free(fen);
13736     }
13737     else {
13738         /* [AS] Out of book annotation */
13739         if( appData.saveOutOfBookInfo ) {
13740             char buf[64];
13741
13742             GetOutOfBookInfo( buf );
13743
13744             if( buf[0] != '\0' ) {
13745                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13746             }
13747         }
13748
13749         fprintf(f, "\n");
13750     }
13751
13752     i = backwardMostMove;
13753     linelen = 0;
13754     newblock = TRUE;
13755
13756     while (i < forwardMostMove) {
13757         /* Print comments preceding this move */
13758         if (commentList[i] != NULL) {
13759             if (linelen > 0) fprintf(f, "\n");
13760             fprintf(f, "%s", commentList[i]);
13761             linelen = 0;
13762             newblock = TRUE;
13763         }
13764
13765         /* Format move number */
13766         if ((i % 2) == 0)
13767           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13768         else
13769           if (newblock)
13770             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13771           else
13772             numtext[0] = NULLCHAR;
13773
13774         numlen = strlen(numtext);
13775         newblock = FALSE;
13776
13777         /* Print move number */
13778         blank = linelen > 0 && numlen > 0;
13779         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13780             fprintf(f, "\n");
13781             linelen = 0;
13782             blank = 0;
13783         }
13784         if (blank) {
13785             fprintf(f, " ");
13786             linelen++;
13787         }
13788         fprintf(f, "%s", numtext);
13789         linelen += numlen;
13790
13791         /* Get move */
13792         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13793         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13794
13795         /* Print move */
13796         blank = linelen > 0 && movelen > 0;
13797         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13798             fprintf(f, "\n");
13799             linelen = 0;
13800             blank = 0;
13801         }
13802         if (blank) {
13803             fprintf(f, " ");
13804             linelen++;
13805         }
13806         fprintf(f, "%s", move_buffer);
13807         linelen += movelen;
13808
13809         /* [AS] Add PV info if present */
13810         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13811             /* [HGM] add time */
13812             char buf[MSG_SIZ]; int seconds;
13813
13814             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13815
13816             if( seconds <= 0)
13817               buf[0] = 0;
13818             else
13819               if( seconds < 30 )
13820                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13821               else
13822                 {
13823                   seconds = (seconds + 4)/10; // round to full seconds
13824                   if( seconds < 60 )
13825                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13826                   else
13827                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13828                 }
13829
13830             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13831                       pvInfoList[i].score >= 0 ? "+" : "",
13832                       pvInfoList[i].score / 100.0,
13833                       pvInfoList[i].depth,
13834                       buf );
13835
13836             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13837
13838             /* Print score/depth */
13839             blank = linelen > 0 && movelen > 0;
13840             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13841                 fprintf(f, "\n");
13842                 linelen = 0;
13843                 blank = 0;
13844             }
13845             if (blank) {
13846                 fprintf(f, " ");
13847                 linelen++;
13848             }
13849             fprintf(f, "%s", move_buffer);
13850             linelen += movelen;
13851         }
13852
13853         i++;
13854     }
13855
13856     /* Start a new line */
13857     if (linelen > 0) fprintf(f, "\n");
13858
13859     /* Print comments after last move */
13860     if (commentList[i] != NULL) {
13861         fprintf(f, "%s\n", commentList[i]);
13862     }
13863
13864     /* Print result */
13865     if (gameInfo.resultDetails != NULL &&
13866         gameInfo.resultDetails[0] != NULLCHAR) {
13867         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13868         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13869            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13870             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13871         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13872     } else {
13873         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13874     }
13875 }
13876
13877 /* Save game in PGN style and close the file */
13878 int
13879 SaveGamePGN (FILE *f)
13880 {
13881     SaveGamePGN2(f);
13882     fclose(f);
13883     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13884     return TRUE;
13885 }
13886
13887 /* Save game in old style and close the file */
13888 int
13889 SaveGameOldStyle (FILE *f)
13890 {
13891     int i, offset;
13892     time_t tm;
13893
13894     tm = time((time_t *) NULL);
13895
13896     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13897     PrintOpponents(f);
13898
13899     if (backwardMostMove > 0 || startedFromSetupPosition) {
13900         fprintf(f, "\n[--------------\n");
13901         PrintPosition(f, backwardMostMove);
13902         fprintf(f, "--------------]\n");
13903     } else {
13904         fprintf(f, "\n");
13905     }
13906
13907     i = backwardMostMove;
13908     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13909
13910     while (i < forwardMostMove) {
13911         if (commentList[i] != NULL) {
13912             fprintf(f, "[%s]\n", commentList[i]);
13913         }
13914
13915         if ((i % 2) == 1) {
13916             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13917             i++;
13918         } else {
13919             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13920             i++;
13921             if (commentList[i] != NULL) {
13922                 fprintf(f, "\n");
13923                 continue;
13924             }
13925             if (i >= forwardMostMove) {
13926                 fprintf(f, "\n");
13927                 break;
13928             }
13929             fprintf(f, "%s\n", parseList[i]);
13930             i++;
13931         }
13932     }
13933
13934     if (commentList[i] != NULL) {
13935         fprintf(f, "[%s]\n", commentList[i]);
13936     }
13937
13938     /* This isn't really the old style, but it's close enough */
13939     if (gameInfo.resultDetails != NULL &&
13940         gameInfo.resultDetails[0] != NULLCHAR) {
13941         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13942                 gameInfo.resultDetails);
13943     } else {
13944         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13945     }
13946
13947     fclose(f);
13948     return TRUE;
13949 }
13950
13951 /* Save the current game to open file f and close the file */
13952 int
13953 SaveGame (FILE *f, int dummy, char *dummy2)
13954 {
13955     if (gameMode == EditPosition) EditPositionDone(TRUE);
13956     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13957     if (appData.oldSaveStyle)
13958       return SaveGameOldStyle(f);
13959     else
13960       return SaveGamePGN(f);
13961 }
13962
13963 /* Save the current position to the given file */
13964 int
13965 SavePositionToFile (char *filename)
13966 {
13967     FILE *f;
13968     char buf[MSG_SIZ];
13969
13970     if (strcmp(filename, "-") == 0) {
13971         return SavePosition(stdout, 0, NULL);
13972     } else {
13973         f = fopen(filename, "a");
13974         if (f == NULL) {
13975             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13976             DisplayError(buf, errno);
13977             return FALSE;
13978         } else {
13979             safeStrCpy(buf, lastMsg, MSG_SIZ);
13980             DisplayMessage(_("Waiting for access to save file"), "");
13981             flock(fileno(f), LOCK_EX); // [HGM] lock
13982             DisplayMessage(_("Saving position"), "");
13983             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13984             SavePosition(f, 0, NULL);
13985             DisplayMessage(buf, "");
13986             return TRUE;
13987         }
13988     }
13989 }
13990
13991 /* Save the current position to the given open file and close the file */
13992 int
13993 SavePosition (FILE *f, int dummy, char *dummy2)
13994 {
13995     time_t tm;
13996     char *fen;
13997
13998     if (gameMode == EditPosition) EditPositionDone(TRUE);
13999     if (appData.oldSaveStyle) {
14000         tm = time((time_t *) NULL);
14001
14002         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
14003         PrintOpponents(f);
14004         fprintf(f, "[--------------\n");
14005         PrintPosition(f, currentMove);
14006         fprintf(f, "--------------]\n");
14007     } else {
14008         fen = PositionToFEN(currentMove, NULL, 1);
14009         fprintf(f, "%s\n", fen);
14010         free(fen);
14011     }
14012     fclose(f);
14013     return TRUE;
14014 }
14015
14016 void
14017 ReloadCmailMsgEvent (int unregister)
14018 {
14019 #if !WIN32
14020     static char *inFilename = NULL;
14021     static char *outFilename;
14022     int i;
14023     struct stat inbuf, outbuf;
14024     int status;
14025
14026     /* Any registered moves are unregistered if unregister is set, */
14027     /* i.e. invoked by the signal handler */
14028     if (unregister) {
14029         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14030             cmailMoveRegistered[i] = FALSE;
14031             if (cmailCommentList[i] != NULL) {
14032                 free(cmailCommentList[i]);
14033                 cmailCommentList[i] = NULL;
14034             }
14035         }
14036         nCmailMovesRegistered = 0;
14037     }
14038
14039     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
14040         cmailResult[i] = CMAIL_NOT_RESULT;
14041     }
14042     nCmailResults = 0;
14043
14044     if (inFilename == NULL) {
14045         /* Because the filenames are static they only get malloced once  */
14046         /* and they never get freed                                      */
14047         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
14048         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
14049
14050         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14051         sprintf(outFilename, "%s.out", appData.cmailGameName);
14052     }
14053
14054     status = stat(outFilename, &outbuf);
14055     if (status < 0) {
14056         cmailMailedMove = FALSE;
14057     } else {
14058         status = stat(inFilename, &inbuf);
14059         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14060     }
14061
14062     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14063        counts the games, notes how each one terminated, etc.
14064
14065        It would be nice to remove this kludge and instead gather all
14066        the information while building the game list.  (And to keep it
14067        in the game list nodes instead of having a bunch of fixed-size
14068        parallel arrays.)  Note this will require getting each game's
14069        termination from the PGN tags, as the game list builder does
14070        not process the game moves.  --mann
14071        */
14072     cmailMsgLoaded = TRUE;
14073     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14074
14075     /* Load first game in the file or popup game menu */
14076     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14077
14078 #endif /* !WIN32 */
14079     return;
14080 }
14081
14082 int
14083 RegisterMove ()
14084 {
14085     FILE *f;
14086     char string[MSG_SIZ];
14087
14088     if (   cmailMailedMove
14089         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14090         return TRUE;            /* Allow free viewing  */
14091     }
14092
14093     /* Unregister move to ensure that we don't leave RegisterMove        */
14094     /* with the move registered when the conditions for registering no   */
14095     /* longer hold                                                       */
14096     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14097         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14098         nCmailMovesRegistered --;
14099
14100         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14101           {
14102               free(cmailCommentList[lastLoadGameNumber - 1]);
14103               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14104           }
14105     }
14106
14107     if (cmailOldMove == -1) {
14108         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14109         return FALSE;
14110     }
14111
14112     if (currentMove > cmailOldMove + 1) {
14113         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14114         return FALSE;
14115     }
14116
14117     if (currentMove < cmailOldMove) {
14118         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14119         return FALSE;
14120     }
14121
14122     if (forwardMostMove > currentMove) {
14123         /* Silently truncate extra moves */
14124         TruncateGame();
14125     }
14126
14127     if (   (currentMove == cmailOldMove + 1)
14128         || (   (currentMove == cmailOldMove)
14129             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14130                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14131         if (gameInfo.result != GameUnfinished) {
14132             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14133         }
14134
14135         if (commentList[currentMove] != NULL) {
14136             cmailCommentList[lastLoadGameNumber - 1]
14137               = StrSave(commentList[currentMove]);
14138         }
14139         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14140
14141         if (appData.debugMode)
14142           fprintf(debugFP, "Saving %s for game %d\n",
14143                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14144
14145         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14146
14147         f = fopen(string, "w");
14148         if (appData.oldSaveStyle) {
14149             SaveGameOldStyle(f); /* also closes the file */
14150
14151             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14152             f = fopen(string, "w");
14153             SavePosition(f, 0, NULL); /* also closes the file */
14154         } else {
14155             fprintf(f, "{--------------\n");
14156             PrintPosition(f, currentMove);
14157             fprintf(f, "--------------}\n\n");
14158
14159             SaveGame(f, 0, NULL); /* also closes the file*/
14160         }
14161
14162         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14163         nCmailMovesRegistered ++;
14164     } else if (nCmailGames == 1) {
14165         DisplayError(_("You have not made a move yet"), 0);
14166         return FALSE;
14167     }
14168
14169     return TRUE;
14170 }
14171
14172 void
14173 MailMoveEvent ()
14174 {
14175 #if !WIN32
14176     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14177     FILE *commandOutput;
14178     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14179     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14180     int nBuffers;
14181     int i;
14182     int archived;
14183     char *arcDir;
14184
14185     if (! cmailMsgLoaded) {
14186         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14187         return;
14188     }
14189
14190     if (nCmailGames == nCmailResults) {
14191         DisplayError(_("No unfinished games"), 0);
14192         return;
14193     }
14194
14195 #if CMAIL_PROHIBIT_REMAIL
14196     if (cmailMailedMove) {
14197       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);
14198         DisplayError(msg, 0);
14199         return;
14200     }
14201 #endif
14202
14203     if (! (cmailMailedMove || RegisterMove())) return;
14204
14205     if (   cmailMailedMove
14206         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14207       snprintf(string, MSG_SIZ, partCommandString,
14208                appData.debugMode ? " -v" : "", appData.cmailGameName);
14209         commandOutput = popen(string, "r");
14210
14211         if (commandOutput == NULL) {
14212             DisplayError(_("Failed to invoke cmail"), 0);
14213         } else {
14214             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14215                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14216             }
14217             if (nBuffers > 1) {
14218                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14219                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14220                 nBytes = MSG_SIZ - 1;
14221             } else {
14222                 (void) memcpy(msg, buffer, nBytes);
14223             }
14224             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14225
14226             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14227                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14228
14229                 archived = TRUE;
14230                 for (i = 0; i < nCmailGames; i ++) {
14231                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14232                         archived = FALSE;
14233                     }
14234                 }
14235                 if (   archived
14236                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14237                         != NULL)) {
14238                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14239                            arcDir,
14240                            appData.cmailGameName,
14241                            gameInfo.date);
14242                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14243                     cmailMsgLoaded = FALSE;
14244                 }
14245             }
14246
14247             DisplayInformation(msg);
14248             pclose(commandOutput);
14249         }
14250     } else {
14251         if ((*cmailMsg) != '\0') {
14252             DisplayInformation(cmailMsg);
14253         }
14254     }
14255
14256     return;
14257 #endif /* !WIN32 */
14258 }
14259
14260 char *
14261 CmailMsg ()
14262 {
14263 #if WIN32
14264     return NULL;
14265 #else
14266     int  prependComma = 0;
14267     char number[5];
14268     char string[MSG_SIZ];       /* Space for game-list */
14269     int  i;
14270
14271     if (!cmailMsgLoaded) return "";
14272
14273     if (cmailMailedMove) {
14274       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14275     } else {
14276         /* Create a list of games left */
14277       snprintf(string, MSG_SIZ, "[");
14278         for (i = 0; i < nCmailGames; i ++) {
14279             if (! (   cmailMoveRegistered[i]
14280                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14281                 if (prependComma) {
14282                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14283                 } else {
14284                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14285                     prependComma = 1;
14286                 }
14287
14288                 strcat(string, number);
14289             }
14290         }
14291         strcat(string, "]");
14292
14293         if (nCmailMovesRegistered + nCmailResults == 0) {
14294             switch (nCmailGames) {
14295               case 1:
14296                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14297                 break;
14298
14299               case 2:
14300                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14301                 break;
14302
14303               default:
14304                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14305                          nCmailGames);
14306                 break;
14307             }
14308         } else {
14309             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14310               case 1:
14311                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14312                          string);
14313                 break;
14314
14315               case 0:
14316                 if (nCmailResults == nCmailGames) {
14317                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14318                 } else {
14319                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14320                 }
14321                 break;
14322
14323               default:
14324                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14325                          string);
14326             }
14327         }
14328     }
14329     return cmailMsg;
14330 #endif /* WIN32 */
14331 }
14332
14333 void
14334 ResetGameEvent ()
14335 {
14336     if (gameMode == Training)
14337       SetTrainingModeOff();
14338
14339     Reset(TRUE, TRUE);
14340     cmailMsgLoaded = FALSE;
14341     if (appData.icsActive) {
14342       SendToICS(ics_prefix);
14343       SendToICS("refresh\n");
14344     }
14345 }
14346
14347 void
14348 ExitEvent (int status)
14349 {
14350     exiting++;
14351     if (exiting > 2) {
14352       /* Give up on clean exit */
14353       exit(status);
14354     }
14355     if (exiting > 1) {
14356       /* Keep trying for clean exit */
14357       return;
14358     }
14359
14360     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14361     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14362
14363     if (telnetISR != NULL) {
14364       RemoveInputSource(telnetISR);
14365     }
14366     if (icsPR != NoProc) {
14367       DestroyChildProcess(icsPR, TRUE);
14368     }
14369
14370     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14371     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14372
14373     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14374     /* make sure this other one finishes before killing it!                  */
14375     if(endingGame) { int count = 0;
14376         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14377         while(endingGame && count++ < 10) DoSleep(1);
14378         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14379     }
14380
14381     /* Kill off chess programs */
14382     if (first.pr != NoProc) {
14383         ExitAnalyzeMode();
14384
14385         DoSleep( appData.delayBeforeQuit );
14386         SendToProgram("quit\n", &first);
14387         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14388     }
14389     if (second.pr != NoProc) {
14390         DoSleep( appData.delayBeforeQuit );
14391         SendToProgram("quit\n", &second);
14392         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14393     }
14394     if (first.isr != NULL) {
14395         RemoveInputSource(first.isr);
14396     }
14397     if (second.isr != NULL) {
14398         RemoveInputSource(second.isr);
14399     }
14400
14401     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14402     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14403
14404     ShutDownFrontEnd();
14405     exit(status);
14406 }
14407
14408 void
14409 PauseEngine (ChessProgramState *cps)
14410 {
14411     SendToProgram("pause\n", cps);
14412     cps->pause = 2;
14413 }
14414
14415 void
14416 UnPauseEngine (ChessProgramState *cps)
14417 {
14418     SendToProgram("resume\n", cps);
14419     cps->pause = 1;
14420 }
14421
14422 void
14423 PauseEvent ()
14424 {
14425     if (appData.debugMode)
14426         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14427     if (pausing) {
14428         pausing = FALSE;
14429         ModeHighlight();
14430         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14431             StartClocks();
14432             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14433                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14434                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14435             }
14436             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14437             HandleMachineMove(stashedInputMove, stalledEngine);
14438             stalledEngine = NULL;
14439             return;
14440         }
14441         if (gameMode == MachinePlaysWhite ||
14442             gameMode == TwoMachinesPlay   ||
14443             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14444             if(first.pause)  UnPauseEngine(&first);
14445             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14446             if(second.pause) UnPauseEngine(&second);
14447             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14448             StartClocks();
14449         } else {
14450             DisplayBothClocks();
14451         }
14452         if (gameMode == PlayFromGameFile) {
14453             if (appData.timeDelay >= 0)
14454                 AutoPlayGameLoop();
14455         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14456             Reset(FALSE, TRUE);
14457             SendToICS(ics_prefix);
14458             SendToICS("refresh\n");
14459         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14460             ForwardInner(forwardMostMove);
14461         }
14462         pauseExamInvalid = FALSE;
14463     } else {
14464         switch (gameMode) {
14465           default:
14466             return;
14467           case IcsExamining:
14468             pauseExamForwardMostMove = forwardMostMove;
14469             pauseExamInvalid = FALSE;
14470             /* fall through */
14471           case IcsObserving:
14472           case IcsPlayingWhite:
14473           case IcsPlayingBlack:
14474             pausing = TRUE;
14475             ModeHighlight();
14476             return;
14477           case PlayFromGameFile:
14478             (void) StopLoadGameTimer();
14479             pausing = TRUE;
14480             ModeHighlight();
14481             break;
14482           case BeginningOfGame:
14483             if (appData.icsActive) return;
14484             /* else fall through */
14485           case MachinePlaysWhite:
14486           case MachinePlaysBlack:
14487           case TwoMachinesPlay:
14488             if (forwardMostMove == 0)
14489               return;           /* don't pause if no one has moved */
14490             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14491                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14492                 if(onMove->pause) {           // thinking engine can be paused
14493                     PauseEngine(onMove);      // do it
14494                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14495                         PauseEngine(onMove->other);
14496                     else
14497                         SendToProgram("easy\n", onMove->other);
14498                     StopClocks();
14499                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14500             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14501                 if(first.pause) {
14502                     PauseEngine(&first);
14503                     StopClocks();
14504                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14505             } else { // human on move, pause pondering by either method
14506                 if(first.pause)
14507                     PauseEngine(&first);
14508                 else if(appData.ponderNextMove)
14509                     SendToProgram("easy\n", &first);
14510                 StopClocks();
14511             }
14512             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14513           case AnalyzeMode:
14514             pausing = TRUE;
14515             ModeHighlight();
14516             break;
14517         }
14518     }
14519 }
14520
14521 void
14522 EditCommentEvent ()
14523 {
14524     char title[MSG_SIZ];
14525
14526     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14527       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14528     } else {
14529       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14530                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14531                parseList[currentMove - 1]);
14532     }
14533
14534     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14535 }
14536
14537
14538 void
14539 EditTagsEvent ()
14540 {
14541     char *tags = PGNTags(&gameInfo);
14542     bookUp = FALSE;
14543     EditTagsPopUp(tags, NULL);
14544     free(tags);
14545 }
14546
14547 void
14548 ToggleSecond ()
14549 {
14550   if(second.analyzing) {
14551     SendToProgram("exit\n", &second);
14552     second.analyzing = FALSE;
14553   } else {
14554     if (second.pr == NoProc) StartChessProgram(&second);
14555     InitChessProgram(&second, FALSE);
14556     FeedMovesToProgram(&second, currentMove);
14557
14558     SendToProgram("analyze\n", &second);
14559     second.analyzing = TRUE;
14560   }
14561 }
14562
14563 /* Toggle ShowThinking */
14564 void
14565 ToggleShowThinking()
14566 {
14567   appData.showThinking = !appData.showThinking;
14568   ShowThinkingEvent();
14569 }
14570
14571 int
14572 AnalyzeModeEvent ()
14573 {
14574     char buf[MSG_SIZ];
14575
14576     if (!first.analysisSupport) {
14577       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14578       DisplayError(buf, 0);
14579       return 0;
14580     }
14581     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14582     if (appData.icsActive) {
14583         if (gameMode != IcsObserving) {
14584           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14585             DisplayError(buf, 0);
14586             /* secure check */
14587             if (appData.icsEngineAnalyze) {
14588                 if (appData.debugMode)
14589                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14590                 ExitAnalyzeMode();
14591                 ModeHighlight();
14592             }
14593             return 0;
14594         }
14595         /* if enable, user wants to disable icsEngineAnalyze */
14596         if (appData.icsEngineAnalyze) {
14597                 ExitAnalyzeMode();
14598                 ModeHighlight();
14599                 return 0;
14600         }
14601         appData.icsEngineAnalyze = TRUE;
14602         if (appData.debugMode)
14603             fprintf(debugFP, "ICS engine analyze starting... \n");
14604     }
14605
14606     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14607     if (appData.noChessProgram || gameMode == AnalyzeMode)
14608       return 0;
14609
14610     if (gameMode != AnalyzeFile) {
14611         if (!appData.icsEngineAnalyze) {
14612                EditGameEvent();
14613                if (gameMode != EditGame) return 0;
14614         }
14615         if (!appData.showThinking) ToggleShowThinking();
14616         ResurrectChessProgram();
14617         SendToProgram("analyze\n", &first);
14618         first.analyzing = TRUE;
14619         /*first.maybeThinking = TRUE;*/
14620         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14621         EngineOutputPopUp();
14622     }
14623     if (!appData.icsEngineAnalyze) {
14624         gameMode = AnalyzeMode;
14625         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14626     }
14627     pausing = FALSE;
14628     ModeHighlight();
14629     SetGameInfo();
14630
14631     StartAnalysisClock();
14632     GetTimeMark(&lastNodeCountTime);
14633     lastNodeCount = 0;
14634     return 1;
14635 }
14636
14637 void
14638 AnalyzeFileEvent ()
14639 {
14640     if (appData.noChessProgram || gameMode == AnalyzeFile)
14641       return;
14642
14643     if (!first.analysisSupport) {
14644       char buf[MSG_SIZ];
14645       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14646       DisplayError(buf, 0);
14647       return;
14648     }
14649
14650     if (gameMode != AnalyzeMode) {
14651         keepInfo = 1; // mere annotating should not alter PGN tags
14652         EditGameEvent();
14653         keepInfo = 0;
14654         if (gameMode != EditGame) return;
14655         if (!appData.showThinking) ToggleShowThinking();
14656         ResurrectChessProgram();
14657         SendToProgram("analyze\n", &first);
14658         first.analyzing = TRUE;
14659         /*first.maybeThinking = TRUE;*/
14660         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14661         EngineOutputPopUp();
14662     }
14663     gameMode = AnalyzeFile;
14664     pausing = FALSE;
14665     ModeHighlight();
14666
14667     StartAnalysisClock();
14668     GetTimeMark(&lastNodeCountTime);
14669     lastNodeCount = 0;
14670     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14671     AnalysisPeriodicEvent(1);
14672 }
14673
14674 void
14675 MachineWhiteEvent ()
14676 {
14677     char buf[MSG_SIZ];
14678     char *bookHit = NULL;
14679
14680     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14681       return;
14682
14683
14684     if (gameMode == PlayFromGameFile ||
14685         gameMode == TwoMachinesPlay  ||
14686         gameMode == Training         ||
14687         gameMode == AnalyzeMode      ||
14688         gameMode == EndOfGame)
14689         EditGameEvent();
14690
14691     if (gameMode == EditPosition)
14692         EditPositionDone(TRUE);
14693
14694     if (!WhiteOnMove(currentMove)) {
14695         DisplayError(_("It is not White's turn"), 0);
14696         return;
14697     }
14698
14699     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14700       ExitAnalyzeMode();
14701
14702     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14703         gameMode == AnalyzeFile)
14704         TruncateGame();
14705
14706     ResurrectChessProgram();    /* in case it isn't running */
14707     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14708         gameMode = MachinePlaysWhite;
14709         ResetClocks();
14710     } else
14711     gameMode = MachinePlaysWhite;
14712     pausing = FALSE;
14713     ModeHighlight();
14714     SetGameInfo();
14715     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14716     DisplayTitle(buf);
14717     if (first.sendName) {
14718       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14719       SendToProgram(buf, &first);
14720     }
14721     if (first.sendTime) {
14722       if (first.useColors) {
14723         SendToProgram("black\n", &first); /*gnu kludge*/
14724       }
14725       SendTimeRemaining(&first, TRUE);
14726     }
14727     if (first.useColors) {
14728       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14729     }
14730     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14731     SetMachineThinkingEnables();
14732     first.maybeThinking = TRUE;
14733     StartClocks();
14734     firstMove = FALSE;
14735
14736     if (appData.autoFlipView && !flipView) {
14737       flipView = !flipView;
14738       DrawPosition(FALSE, NULL);
14739       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14740     }
14741
14742     if(bookHit) { // [HGM] book: simulate book reply
14743         static char bookMove[MSG_SIZ]; // a bit generous?
14744
14745         programStats.nodes = programStats.depth = programStats.time =
14746         programStats.score = programStats.got_only_move = 0;
14747         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14748
14749         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14750         strcat(bookMove, bookHit);
14751         HandleMachineMove(bookMove, &first);
14752     }
14753 }
14754
14755 void
14756 MachineBlackEvent ()
14757 {
14758   char buf[MSG_SIZ];
14759   char *bookHit = NULL;
14760
14761     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14762         return;
14763
14764
14765     if (gameMode == PlayFromGameFile ||
14766         gameMode == TwoMachinesPlay  ||
14767         gameMode == Training         ||
14768         gameMode == AnalyzeMode      ||
14769         gameMode == EndOfGame)
14770         EditGameEvent();
14771
14772     if (gameMode == EditPosition)
14773         EditPositionDone(TRUE);
14774
14775     if (WhiteOnMove(currentMove)) {
14776         DisplayError(_("It is not Black's turn"), 0);
14777         return;
14778     }
14779
14780     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14781       ExitAnalyzeMode();
14782
14783     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14784         gameMode == AnalyzeFile)
14785         TruncateGame();
14786
14787     ResurrectChessProgram();    /* in case it isn't running */
14788     gameMode = MachinePlaysBlack;
14789     pausing = FALSE;
14790     ModeHighlight();
14791     SetGameInfo();
14792     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14793     DisplayTitle(buf);
14794     if (first.sendName) {
14795       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14796       SendToProgram(buf, &first);
14797     }
14798     if (first.sendTime) {
14799       if (first.useColors) {
14800         SendToProgram("white\n", &first); /*gnu kludge*/
14801       }
14802       SendTimeRemaining(&first, FALSE);
14803     }
14804     if (first.useColors) {
14805       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14806     }
14807     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14808     SetMachineThinkingEnables();
14809     first.maybeThinking = TRUE;
14810     StartClocks();
14811
14812     if (appData.autoFlipView && flipView) {
14813       flipView = !flipView;
14814       DrawPosition(FALSE, NULL);
14815       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14816     }
14817     if(bookHit) { // [HGM] book: simulate book reply
14818         static char bookMove[MSG_SIZ]; // a bit generous?
14819
14820         programStats.nodes = programStats.depth = programStats.time =
14821         programStats.score = programStats.got_only_move = 0;
14822         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14823
14824         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14825         strcat(bookMove, bookHit);
14826         HandleMachineMove(bookMove, &first);
14827     }
14828 }
14829
14830
14831 void
14832 DisplayTwoMachinesTitle ()
14833 {
14834     char buf[MSG_SIZ];
14835     if (appData.matchGames > 0) {
14836         if(appData.tourneyFile[0]) {
14837           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14838                    gameInfo.white, _("vs."), gameInfo.black,
14839                    nextGame+1, appData.matchGames+1,
14840                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14841         } else
14842         if (first.twoMachinesColor[0] == 'w') {
14843           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14844                    gameInfo.white, _("vs."),  gameInfo.black,
14845                    first.matchWins, second.matchWins,
14846                    matchGame - 1 - (first.matchWins + second.matchWins));
14847         } else {
14848           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14849                    gameInfo.white, _("vs."), gameInfo.black,
14850                    second.matchWins, first.matchWins,
14851                    matchGame - 1 - (first.matchWins + second.matchWins));
14852         }
14853     } else {
14854       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14855     }
14856     DisplayTitle(buf);
14857 }
14858
14859 void
14860 SettingsMenuIfReady ()
14861 {
14862   if (second.lastPing != second.lastPong) {
14863     DisplayMessage("", _("Waiting for second chess program"));
14864     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14865     return;
14866   }
14867   ThawUI();
14868   DisplayMessage("", "");
14869   SettingsPopUp(&second);
14870 }
14871
14872 int
14873 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14874 {
14875     char buf[MSG_SIZ];
14876     if (cps->pr == NoProc) {
14877         StartChessProgram(cps);
14878         if (cps->protocolVersion == 1) {
14879           retry();
14880           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14881         } else {
14882           /* kludge: allow timeout for initial "feature" command */
14883           if(retry != TwoMachinesEventIfReady) FreezeUI();
14884           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14885           DisplayMessage("", buf);
14886           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14887         }
14888         return 1;
14889     }
14890     return 0;
14891 }
14892
14893 void
14894 TwoMachinesEvent P((void))
14895 {
14896     int i;
14897     char buf[MSG_SIZ];
14898     ChessProgramState *onmove;
14899     char *bookHit = NULL;
14900     static int stalling = 0;
14901     TimeMark now;
14902     long wait;
14903
14904     if (appData.noChessProgram) return;
14905
14906     switch (gameMode) {
14907       case TwoMachinesPlay:
14908         return;
14909       case MachinePlaysWhite:
14910       case MachinePlaysBlack:
14911         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14912             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14913             return;
14914         }
14915         /* fall through */
14916       case BeginningOfGame:
14917       case PlayFromGameFile:
14918       case EndOfGame:
14919         EditGameEvent();
14920         if (gameMode != EditGame) return;
14921         break;
14922       case EditPosition:
14923         EditPositionDone(TRUE);
14924         break;
14925       case AnalyzeMode:
14926       case AnalyzeFile:
14927         ExitAnalyzeMode();
14928         break;
14929       case EditGame:
14930       default:
14931         break;
14932     }
14933
14934 //    forwardMostMove = currentMove;
14935     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14936     startingEngine = TRUE;
14937
14938     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14939
14940     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14941     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14942       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14943       return;
14944     }
14945     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14946
14947     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14948                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14949         startingEngine = matchMode = FALSE;
14950         DisplayError("second engine does not play this", 0);
14951         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14952         EditGameEvent(); // switch back to EditGame mode
14953         return;
14954     }
14955
14956     if(!stalling) {
14957       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14958       SendToProgram("force\n", &second);
14959       stalling = 1;
14960       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14961       return;
14962     }
14963     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14964     if(appData.matchPause>10000 || appData.matchPause<10)
14965                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14966     wait = SubtractTimeMarks(&now, &pauseStart);
14967     if(wait < appData.matchPause) {
14968         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14969         return;
14970     }
14971     // we are now committed to starting the game
14972     stalling = 0;
14973     DisplayMessage("", "");
14974     if (startedFromSetupPosition) {
14975         SendBoard(&second, backwardMostMove);
14976     if (appData.debugMode) {
14977         fprintf(debugFP, "Two Machines\n");
14978     }
14979     }
14980     for (i = backwardMostMove; i < forwardMostMove; i++) {
14981         SendMoveToProgram(i, &second);
14982     }
14983
14984     gameMode = TwoMachinesPlay;
14985     pausing = startingEngine = FALSE;
14986     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14987     SetGameInfo();
14988     DisplayTwoMachinesTitle();
14989     firstMove = TRUE;
14990     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14991         onmove = &first;
14992     } else {
14993         onmove = &second;
14994     }
14995     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14996     SendToProgram(first.computerString, &first);
14997     if (first.sendName) {
14998       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14999       SendToProgram(buf, &first);
15000     }
15001     SendToProgram(second.computerString, &second);
15002     if (second.sendName) {
15003       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
15004       SendToProgram(buf, &second);
15005     }
15006
15007     ResetClocks();
15008     if (!first.sendTime || !second.sendTime) {
15009         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15010         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15011     }
15012     if (onmove->sendTime) {
15013       if (onmove->useColors) {
15014         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
15015       }
15016       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
15017     }
15018     if (onmove->useColors) {
15019       SendToProgram(onmove->twoMachinesColor, onmove);
15020     }
15021     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
15022 //    SendToProgram("go\n", onmove);
15023     onmove->maybeThinking = TRUE;
15024     SetMachineThinkingEnables();
15025
15026     StartClocks();
15027
15028     if(bookHit) { // [HGM] book: simulate book reply
15029         static char bookMove[MSG_SIZ]; // a bit generous?
15030
15031         programStats.nodes = programStats.depth = programStats.time =
15032         programStats.score = programStats.got_only_move = 0;
15033         sprintf(programStats.movelist, "%s (xbook)", bookHit);
15034
15035         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
15036         strcat(bookMove, bookHit);
15037         savedMessage = bookMove; // args for deferred call
15038         savedState = onmove;
15039         ScheduleDelayedEvent(DeferredBookMove, 1);
15040     }
15041 }
15042
15043 void
15044 TrainingEvent ()
15045 {
15046     if (gameMode == Training) {
15047       SetTrainingModeOff();
15048       gameMode = PlayFromGameFile;
15049       DisplayMessage("", _("Training mode off"));
15050     } else {
15051       gameMode = Training;
15052       animateTraining = appData.animate;
15053
15054       /* make sure we are not already at the end of the game */
15055       if (currentMove < forwardMostMove) {
15056         SetTrainingModeOn();
15057         DisplayMessage("", _("Training mode on"));
15058       } else {
15059         gameMode = PlayFromGameFile;
15060         DisplayError(_("Already at end of game"), 0);
15061       }
15062     }
15063     ModeHighlight();
15064 }
15065
15066 void
15067 IcsClientEvent ()
15068 {
15069     if (!appData.icsActive) return;
15070     switch (gameMode) {
15071       case IcsPlayingWhite:
15072       case IcsPlayingBlack:
15073       case IcsObserving:
15074       case IcsIdle:
15075       case BeginningOfGame:
15076       case IcsExamining:
15077         return;
15078
15079       case EditGame:
15080         break;
15081
15082       case EditPosition:
15083         EditPositionDone(TRUE);
15084         break;
15085
15086       case AnalyzeMode:
15087       case AnalyzeFile:
15088         ExitAnalyzeMode();
15089         break;
15090
15091       default:
15092         EditGameEvent();
15093         break;
15094     }
15095
15096     gameMode = IcsIdle;
15097     ModeHighlight();
15098     return;
15099 }
15100
15101 void
15102 EditGameEvent ()
15103 {
15104     int i;
15105
15106     switch (gameMode) {
15107       case Training:
15108         SetTrainingModeOff();
15109         break;
15110       case MachinePlaysWhite:
15111       case MachinePlaysBlack:
15112       case BeginningOfGame:
15113         SendToProgram("force\n", &first);
15114         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15115             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15116                 char buf[MSG_SIZ];
15117                 abortEngineThink = TRUE;
15118                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15119                 SendToProgram(buf, &first);
15120                 DisplayMessage("Aborting engine think", "");
15121                 FreezeUI();
15122             }
15123         }
15124         SetUserThinkingEnables();
15125         break;
15126       case PlayFromGameFile:
15127         (void) StopLoadGameTimer();
15128         if (gameFileFP != NULL) {
15129             gameFileFP = NULL;
15130         }
15131         break;
15132       case EditPosition:
15133         EditPositionDone(TRUE);
15134         break;
15135       case AnalyzeMode:
15136       case AnalyzeFile:
15137         ExitAnalyzeMode();
15138         SendToProgram("force\n", &first);
15139         break;
15140       case TwoMachinesPlay:
15141         GameEnds(EndOfFile, NULL, GE_PLAYER);
15142         ResurrectChessProgram();
15143         SetUserThinkingEnables();
15144         break;
15145       case EndOfGame:
15146         ResurrectChessProgram();
15147         break;
15148       case IcsPlayingBlack:
15149       case IcsPlayingWhite:
15150         DisplayError(_("Warning: You are still playing a game"), 0);
15151         break;
15152       case IcsObserving:
15153         DisplayError(_("Warning: You are still observing a game"), 0);
15154         break;
15155       case IcsExamining:
15156         DisplayError(_("Warning: You are still examining a game"), 0);
15157         break;
15158       case IcsIdle:
15159         break;
15160       case EditGame:
15161       default:
15162         return;
15163     }
15164
15165     pausing = FALSE;
15166     StopClocks();
15167     first.offeredDraw = second.offeredDraw = 0;
15168
15169     if (gameMode == PlayFromGameFile) {
15170         whiteTimeRemaining = timeRemaining[0][currentMove];
15171         blackTimeRemaining = timeRemaining[1][currentMove];
15172         DisplayTitle("");
15173     }
15174
15175     if (gameMode == MachinePlaysWhite ||
15176         gameMode == MachinePlaysBlack ||
15177         gameMode == TwoMachinesPlay ||
15178         gameMode == EndOfGame) {
15179         i = forwardMostMove;
15180         while (i > currentMove) {
15181             SendToProgram("undo\n", &first);
15182             i--;
15183         }
15184         if(!adjustedClock) {
15185         whiteTimeRemaining = timeRemaining[0][currentMove];
15186         blackTimeRemaining = timeRemaining[1][currentMove];
15187         DisplayBothClocks();
15188         }
15189         if (whiteFlag || blackFlag) {
15190             whiteFlag = blackFlag = 0;
15191         }
15192         DisplayTitle("");
15193     }
15194
15195     gameMode = EditGame;
15196     ModeHighlight();
15197     SetGameInfo();
15198 }
15199
15200
15201 void
15202 EditPositionEvent ()
15203 {
15204     if (gameMode == EditPosition) {
15205         EditGameEvent();
15206         return;
15207     }
15208
15209     EditGameEvent();
15210     if (gameMode != EditGame) return;
15211
15212     gameMode = EditPosition;
15213     ModeHighlight();
15214     SetGameInfo();
15215     if (currentMove > 0)
15216       CopyBoard(boards[0], boards[currentMove]);
15217
15218     blackPlaysFirst = !WhiteOnMove(currentMove);
15219     ResetClocks();
15220     currentMove = forwardMostMove = backwardMostMove = 0;
15221     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15222     DisplayMove(-1);
15223     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15224 }
15225
15226 void
15227 ExitAnalyzeMode ()
15228 {
15229     /* [DM] icsEngineAnalyze - possible call from other functions */
15230     if (appData.icsEngineAnalyze) {
15231         appData.icsEngineAnalyze = FALSE;
15232
15233         DisplayMessage("",_("Close ICS engine analyze..."));
15234     }
15235     if (first.analysisSupport && first.analyzing) {
15236       SendToBoth("exit\n");
15237       first.analyzing = second.analyzing = FALSE;
15238     }
15239     thinkOutput[0] = NULLCHAR;
15240 }
15241
15242 void
15243 EditPositionDone (Boolean fakeRights)
15244 {
15245     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15246
15247     startedFromSetupPosition = TRUE;
15248     InitChessProgram(&first, FALSE);
15249     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15250       boards[0][EP_STATUS] = EP_NONE;
15251       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15252       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15253         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15254         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15255       } else boards[0][CASTLING][2] = NoRights;
15256       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15257         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15258         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15259       } else boards[0][CASTLING][5] = NoRights;
15260       if(gameInfo.variant == VariantSChess) {
15261         int i;
15262         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15263           boards[0][VIRGIN][i] = 0;
15264           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15265           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15266         }
15267       }
15268     }
15269     SendToProgram("force\n", &first);
15270     if (blackPlaysFirst) {
15271         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15272         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15273         currentMove = forwardMostMove = backwardMostMove = 1;
15274         CopyBoard(boards[1], boards[0]);
15275     } else {
15276         currentMove = forwardMostMove = backwardMostMove = 0;
15277     }
15278     SendBoard(&first, forwardMostMove);
15279     if (appData.debugMode) {
15280         fprintf(debugFP, "EditPosDone\n");
15281     }
15282     DisplayTitle("");
15283     DisplayMessage("", "");
15284     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15285     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15286     gameMode = EditGame;
15287     ModeHighlight();
15288     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15289     ClearHighlights(); /* [AS] */
15290 }
15291
15292 /* Pause for `ms' milliseconds */
15293 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15294 void
15295 TimeDelay (long ms)
15296 {
15297     TimeMark m1, m2;
15298
15299     GetTimeMark(&m1);
15300     do {
15301         GetTimeMark(&m2);
15302     } while (SubtractTimeMarks(&m2, &m1) < ms);
15303 }
15304
15305 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15306 void
15307 SendMultiLineToICS (char *buf)
15308 {
15309     char temp[MSG_SIZ+1], *p;
15310     int len;
15311
15312     len = strlen(buf);
15313     if (len > MSG_SIZ)
15314       len = MSG_SIZ;
15315
15316     strncpy(temp, buf, len);
15317     temp[len] = 0;
15318
15319     p = temp;
15320     while (*p) {
15321         if (*p == '\n' || *p == '\r')
15322           *p = ' ';
15323         ++p;
15324     }
15325
15326     strcat(temp, "\n");
15327     SendToICS(temp);
15328     SendToPlayer(temp, strlen(temp));
15329 }
15330
15331 void
15332 SetWhiteToPlayEvent ()
15333 {
15334     if (gameMode == EditPosition) {
15335         blackPlaysFirst = FALSE;
15336         DisplayBothClocks();    /* works because currentMove is 0 */
15337     } else if (gameMode == IcsExamining) {
15338         SendToICS(ics_prefix);
15339         SendToICS("tomove white\n");
15340     }
15341 }
15342
15343 void
15344 SetBlackToPlayEvent ()
15345 {
15346     if (gameMode == EditPosition) {
15347         blackPlaysFirst = TRUE;
15348         currentMove = 1;        /* kludge */
15349         DisplayBothClocks();
15350         currentMove = 0;
15351     } else if (gameMode == IcsExamining) {
15352         SendToICS(ics_prefix);
15353         SendToICS("tomove black\n");
15354     }
15355 }
15356
15357 void
15358 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15359 {
15360     char buf[MSG_SIZ];
15361     ChessSquare piece = boards[0][y][x];
15362     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15363     static int lastVariant;
15364
15365     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15366
15367     switch (selection) {
15368       case ClearBoard:
15369         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15370         MarkTargetSquares(1);
15371         CopyBoard(currentBoard, boards[0]);
15372         CopyBoard(menuBoard, initialPosition);
15373         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15374             SendToICS(ics_prefix);
15375             SendToICS("bsetup clear\n");
15376         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15377             SendToICS(ics_prefix);
15378             SendToICS("clearboard\n");
15379         } else {
15380             int nonEmpty = 0;
15381             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15382                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15383                 for (y = 0; y < BOARD_HEIGHT; y++) {
15384                     if (gameMode == IcsExamining) {
15385                         if (boards[currentMove][y][x] != EmptySquare) {
15386                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15387                                     AAA + x, ONE + y);
15388                             SendToICS(buf);
15389                         }
15390                     } else if(boards[0][y][x] != DarkSquare) {
15391                         if(boards[0][y][x] != p) nonEmpty++;
15392                         boards[0][y][x] = p;
15393                     }
15394                 }
15395             }
15396             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15397                 int r;
15398                 for(r = 0; r < BOARD_HEIGHT; r++) {
15399                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15400                     ChessSquare p = menuBoard[r][x];
15401                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15402                   }
15403                 }
15404                 DisplayMessage("Clicking clock again restores position", "");
15405                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15406                 if(!nonEmpty) { // asked to clear an empty board
15407                     CopyBoard(boards[0], menuBoard);
15408                 } else
15409                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15410                     CopyBoard(boards[0], initialPosition);
15411                 } else
15412                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15413                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15414                     CopyBoard(boards[0], erasedBoard);
15415                 } else
15416                     CopyBoard(erasedBoard, currentBoard);
15417
15418             }
15419         }
15420         if (gameMode == EditPosition) {
15421             DrawPosition(FALSE, boards[0]);
15422         }
15423         break;
15424
15425       case WhitePlay:
15426         SetWhiteToPlayEvent();
15427         break;
15428
15429       case BlackPlay:
15430         SetBlackToPlayEvent();
15431         break;
15432
15433       case EmptySquare:
15434         if (gameMode == IcsExamining) {
15435             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15436             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15437             SendToICS(buf);
15438         } else {
15439             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15440                 if(x == BOARD_LEFT-2) {
15441                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15442                     boards[0][y][1] = 0;
15443                 } else
15444                 if(x == BOARD_RGHT+1) {
15445                     if(y >= gameInfo.holdingsSize) break;
15446                     boards[0][y][BOARD_WIDTH-2] = 0;
15447                 } else break;
15448             }
15449             boards[0][y][x] = EmptySquare;
15450             DrawPosition(FALSE, boards[0]);
15451         }
15452         break;
15453
15454       case PromotePiece:
15455         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15456            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15457             selection = (ChessSquare) (PROMOTED(piece));
15458         } else if(piece == EmptySquare) selection = WhiteSilver;
15459         else selection = (ChessSquare)((int)piece - 1);
15460         goto defaultlabel;
15461
15462       case DemotePiece:
15463         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15464            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15465             selection = (ChessSquare) (DEMOTED(piece));
15466         } else if(piece == EmptySquare) selection = BlackSilver;
15467         else selection = (ChessSquare)((int)piece + 1);
15468         goto defaultlabel;
15469
15470       case WhiteQueen:
15471       case BlackQueen:
15472         if(gameInfo.variant == VariantShatranj ||
15473            gameInfo.variant == VariantXiangqi  ||
15474            gameInfo.variant == VariantCourier  ||
15475            gameInfo.variant == VariantASEAN    ||
15476            gameInfo.variant == VariantMakruk     )
15477             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15478         goto defaultlabel;
15479
15480       case WhiteKing:
15481       case BlackKing:
15482         if(gameInfo.variant == VariantXiangqi)
15483             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15484         if(gameInfo.variant == VariantKnightmate)
15485             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15486       default:
15487         defaultlabel:
15488         if (gameMode == IcsExamining) {
15489             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15490             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15491                      PieceToChar(selection), AAA + x, ONE + y);
15492             SendToICS(buf);
15493         } else {
15494             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15495                 int n;
15496                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15497                     n = PieceToNumber(selection - BlackPawn);
15498                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15499                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15500                     boards[0][BOARD_HEIGHT-1-n][1]++;
15501                 } else
15502                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15503                     n = PieceToNumber(selection);
15504                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15505                     boards[0][n][BOARD_WIDTH-1] = selection;
15506                     boards[0][n][BOARD_WIDTH-2]++;
15507                 }
15508             } else
15509             boards[0][y][x] = selection;
15510             DrawPosition(TRUE, boards[0]);
15511             ClearHighlights();
15512             fromX = fromY = -1;
15513         }
15514         break;
15515     }
15516 }
15517
15518
15519 void
15520 DropMenuEvent (ChessSquare selection, int x, int y)
15521 {
15522     ChessMove moveType;
15523
15524     switch (gameMode) {
15525       case IcsPlayingWhite:
15526       case MachinePlaysBlack:
15527         if (!WhiteOnMove(currentMove)) {
15528             DisplayMoveError(_("It is Black's turn"));
15529             return;
15530         }
15531         moveType = WhiteDrop;
15532         break;
15533       case IcsPlayingBlack:
15534       case MachinePlaysWhite:
15535         if (WhiteOnMove(currentMove)) {
15536             DisplayMoveError(_("It is White's turn"));
15537             return;
15538         }
15539         moveType = BlackDrop;
15540         break;
15541       case EditGame:
15542         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15543         break;
15544       default:
15545         return;
15546     }
15547
15548     if (moveType == BlackDrop && selection < BlackPawn) {
15549       selection = (ChessSquare) ((int) selection
15550                                  + (int) BlackPawn - (int) WhitePawn);
15551     }
15552     if (boards[currentMove][y][x] != EmptySquare) {
15553         DisplayMoveError(_("That square is occupied"));
15554         return;
15555     }
15556
15557     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15558 }
15559
15560 void
15561 AcceptEvent ()
15562 {
15563     /* Accept a pending offer of any kind from opponent */
15564
15565     if (appData.icsActive) {
15566         SendToICS(ics_prefix);
15567         SendToICS("accept\n");
15568     } else if (cmailMsgLoaded) {
15569         if (currentMove == cmailOldMove &&
15570             commentList[cmailOldMove] != NULL &&
15571             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15572                    "Black offers a draw" : "White offers a draw")) {
15573             TruncateGame();
15574             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15575             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15576         } else {
15577             DisplayError(_("There is no pending offer on this move"), 0);
15578             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15579         }
15580     } else {
15581         /* Not used for offers from chess program */
15582     }
15583 }
15584
15585 void
15586 DeclineEvent ()
15587 {
15588     /* Decline a pending offer of any kind from opponent */
15589
15590     if (appData.icsActive) {
15591         SendToICS(ics_prefix);
15592         SendToICS("decline\n");
15593     } else if (cmailMsgLoaded) {
15594         if (currentMove == cmailOldMove &&
15595             commentList[cmailOldMove] != NULL &&
15596             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15597                    "Black offers a draw" : "White offers a draw")) {
15598 #ifdef NOTDEF
15599             AppendComment(cmailOldMove, "Draw declined", TRUE);
15600             DisplayComment(cmailOldMove - 1, "Draw declined");
15601 #endif /*NOTDEF*/
15602         } else {
15603             DisplayError(_("There is no pending offer on this move"), 0);
15604         }
15605     } else {
15606         /* Not used for offers from chess program */
15607     }
15608 }
15609
15610 void
15611 RematchEvent ()
15612 {
15613     /* Issue ICS rematch command */
15614     if (appData.icsActive) {
15615         SendToICS(ics_prefix);
15616         SendToICS("rematch\n");
15617     }
15618 }
15619
15620 void
15621 CallFlagEvent ()
15622 {
15623     /* Call your opponent's flag (claim a win on time) */
15624     if (appData.icsActive) {
15625         SendToICS(ics_prefix);
15626         SendToICS("flag\n");
15627     } else {
15628         switch (gameMode) {
15629           default:
15630             return;
15631           case MachinePlaysWhite:
15632             if (whiteFlag) {
15633                 if (blackFlag)
15634                   GameEnds(GameIsDrawn, "Both players ran out of time",
15635                            GE_PLAYER);
15636                 else
15637                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15638             } else {
15639                 DisplayError(_("Your opponent is not out of time"), 0);
15640             }
15641             break;
15642           case MachinePlaysBlack:
15643             if (blackFlag) {
15644                 if (whiteFlag)
15645                   GameEnds(GameIsDrawn, "Both players ran out of time",
15646                            GE_PLAYER);
15647                 else
15648                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15649             } else {
15650                 DisplayError(_("Your opponent is not out of time"), 0);
15651             }
15652             break;
15653         }
15654     }
15655 }
15656
15657 void
15658 ClockClick (int which)
15659 {       // [HGM] code moved to back-end from winboard.c
15660         if(which) { // black clock
15661           if (gameMode == EditPosition || gameMode == IcsExamining) {
15662             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15663             SetBlackToPlayEvent();
15664           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15665                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15666           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15667           } else if (shiftKey) {
15668             AdjustClock(which, -1);
15669           } else if (gameMode == IcsPlayingWhite ||
15670                      gameMode == MachinePlaysBlack) {
15671             CallFlagEvent();
15672           }
15673         } else { // white clock
15674           if (gameMode == EditPosition || gameMode == IcsExamining) {
15675             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15676             SetWhiteToPlayEvent();
15677           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15678                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15679           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15680           } else if (shiftKey) {
15681             AdjustClock(which, -1);
15682           } else if (gameMode == IcsPlayingBlack ||
15683                    gameMode == MachinePlaysWhite) {
15684             CallFlagEvent();
15685           }
15686         }
15687 }
15688
15689 void
15690 DrawEvent ()
15691 {
15692     /* Offer draw or accept pending draw offer from opponent */
15693
15694     if (appData.icsActive) {
15695         /* Note: tournament rules require draw offers to be
15696            made after you make your move but before you punch
15697            your clock.  Currently ICS doesn't let you do that;
15698            instead, you immediately punch your clock after making
15699            a move, but you can offer a draw at any time. */
15700
15701         SendToICS(ics_prefix);
15702         SendToICS("draw\n");
15703         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15704     } else if (cmailMsgLoaded) {
15705         if (currentMove == cmailOldMove &&
15706             commentList[cmailOldMove] != NULL &&
15707             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15708                    "Black offers a draw" : "White offers a draw")) {
15709             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15710             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15711         } else if (currentMove == cmailOldMove + 1) {
15712             char *offer = WhiteOnMove(cmailOldMove) ?
15713               "White offers a draw" : "Black offers a draw";
15714             AppendComment(currentMove, offer, TRUE);
15715             DisplayComment(currentMove - 1, offer);
15716             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15717         } else {
15718             DisplayError(_("You must make your move before offering a draw"), 0);
15719             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15720         }
15721     } else if (first.offeredDraw) {
15722         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15723     } else {
15724         if (first.sendDrawOffers) {
15725             SendToProgram("draw\n", &first);
15726             userOfferedDraw = TRUE;
15727         }
15728     }
15729 }
15730
15731 void
15732 AdjournEvent ()
15733 {
15734     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15735
15736     if (appData.icsActive) {
15737         SendToICS(ics_prefix);
15738         SendToICS("adjourn\n");
15739     } else {
15740         /* Currently GNU Chess doesn't offer or accept Adjourns */
15741     }
15742 }
15743
15744
15745 void
15746 AbortEvent ()
15747 {
15748     /* Offer Abort or accept pending Abort offer from opponent */
15749
15750     if (appData.icsActive) {
15751         SendToICS(ics_prefix);
15752         SendToICS("abort\n");
15753     } else {
15754         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15755     }
15756 }
15757
15758 void
15759 ResignEvent ()
15760 {
15761     /* Resign.  You can do this even if it's not your turn. */
15762
15763     if (appData.icsActive) {
15764         SendToICS(ics_prefix);
15765         SendToICS("resign\n");
15766     } else {
15767         switch (gameMode) {
15768           case MachinePlaysWhite:
15769             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15770             break;
15771           case MachinePlaysBlack:
15772             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15773             break;
15774           case EditGame:
15775             if (cmailMsgLoaded) {
15776                 TruncateGame();
15777                 if (WhiteOnMove(cmailOldMove)) {
15778                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15779                 } else {
15780                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15781                 }
15782                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15783             }
15784             break;
15785           default:
15786             break;
15787         }
15788     }
15789 }
15790
15791
15792 void
15793 StopObservingEvent ()
15794 {
15795     /* Stop observing current games */
15796     SendToICS(ics_prefix);
15797     SendToICS("unobserve\n");
15798 }
15799
15800 void
15801 StopExaminingEvent ()
15802 {
15803     /* Stop observing current game */
15804     SendToICS(ics_prefix);
15805     SendToICS("unexamine\n");
15806 }
15807
15808 void
15809 ForwardInner (int target)
15810 {
15811     int limit; int oldSeekGraphUp = seekGraphUp;
15812
15813     if (appData.debugMode)
15814         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15815                 target, currentMove, forwardMostMove);
15816
15817     if (gameMode == EditPosition)
15818       return;
15819
15820     seekGraphUp = FALSE;
15821     MarkTargetSquares(1);
15822     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15823
15824     if (gameMode == PlayFromGameFile && !pausing)
15825       PauseEvent();
15826
15827     if (gameMode == IcsExamining && pausing)
15828       limit = pauseExamForwardMostMove;
15829     else
15830       limit = forwardMostMove;
15831
15832     if (target > limit) target = limit;
15833
15834     if (target > 0 && moveList[target - 1][0]) {
15835         int fromX, fromY, toX, toY;
15836         toX = moveList[target - 1][2] - AAA;
15837         toY = moveList[target - 1][3] - ONE;
15838         if (moveList[target - 1][1] == '@') {
15839             if (appData.highlightLastMove) {
15840                 SetHighlights(-1, -1, toX, toY);
15841             }
15842         } else {
15843             int viaX = moveList[target - 1][5] - AAA;
15844             int viaY = moveList[target - 1][6] - ONE;
15845             fromX = moveList[target - 1][0] - AAA;
15846             fromY = moveList[target - 1][1] - ONE;
15847             if (target == currentMove + 1) {
15848                 if(moveList[target - 1][4] == ';') { // multi-leg
15849                     ChessSquare piece = boards[currentMove][viaY][viaX];
15850                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15851                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15852                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15853                     boards[currentMove][viaY][viaX] = piece;
15854                 } else
15855                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15856             }
15857             if (appData.highlightLastMove) {
15858                 SetHighlights(fromX, fromY, toX, toY);
15859             }
15860         }
15861     }
15862     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15863         gameMode == Training || gameMode == PlayFromGameFile ||
15864         gameMode == AnalyzeFile) {
15865         while (currentMove < target) {
15866             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15867             SendMoveToProgram(currentMove++, &first);
15868         }
15869     } else {
15870         currentMove = target;
15871     }
15872
15873     if (gameMode == EditGame || gameMode == EndOfGame) {
15874         whiteTimeRemaining = timeRemaining[0][currentMove];
15875         blackTimeRemaining = timeRemaining[1][currentMove];
15876     }
15877     DisplayBothClocks();
15878     DisplayMove(currentMove - 1);
15879     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15880     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15881     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15882         DisplayComment(currentMove - 1, commentList[currentMove]);
15883     }
15884     ClearMap(); // [HGM] exclude: invalidate map
15885 }
15886
15887
15888 void
15889 ForwardEvent ()
15890 {
15891     if (gameMode == IcsExamining && !pausing) {
15892         SendToICS(ics_prefix);
15893         SendToICS("forward\n");
15894     } else {
15895         ForwardInner(currentMove + 1);
15896     }
15897 }
15898
15899 void
15900 ToEndEvent ()
15901 {
15902     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15903         /* to optimze, we temporarily turn off analysis mode while we feed
15904          * the remaining moves to the engine. Otherwise we get analysis output
15905          * after each move.
15906          */
15907         if (first.analysisSupport) {
15908           SendToProgram("exit\nforce\n", &first);
15909           first.analyzing = FALSE;
15910         }
15911     }
15912
15913     if (gameMode == IcsExamining && !pausing) {
15914         SendToICS(ics_prefix);
15915         SendToICS("forward 999999\n");
15916     } else {
15917         ForwardInner(forwardMostMove);
15918     }
15919
15920     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15921         /* we have fed all the moves, so reactivate analysis mode */
15922         SendToProgram("analyze\n", &first);
15923         first.analyzing = TRUE;
15924         /*first.maybeThinking = TRUE;*/
15925         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15926     }
15927 }
15928
15929 void
15930 BackwardInner (int target)
15931 {
15932     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15933
15934     if (appData.debugMode)
15935         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15936                 target, currentMove, forwardMostMove);
15937
15938     if (gameMode == EditPosition) return;
15939     seekGraphUp = FALSE;
15940     MarkTargetSquares(1);
15941     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15942     if (currentMove <= backwardMostMove) {
15943         ClearHighlights();
15944         DrawPosition(full_redraw, boards[currentMove]);
15945         return;
15946     }
15947     if (gameMode == PlayFromGameFile && !pausing)
15948       PauseEvent();
15949
15950     if (moveList[target][0]) {
15951         int fromX, fromY, toX, toY;
15952         toX = moveList[target][2] - AAA;
15953         toY = moveList[target][3] - ONE;
15954         if (moveList[target][1] == '@') {
15955             if (appData.highlightLastMove) {
15956                 SetHighlights(-1, -1, toX, toY);
15957             }
15958         } else {
15959             fromX = moveList[target][0] - AAA;
15960             fromY = moveList[target][1] - ONE;
15961             if (target == currentMove - 1) {
15962                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15963             }
15964             if (appData.highlightLastMove) {
15965                 SetHighlights(fromX, fromY, toX, toY);
15966             }
15967         }
15968     }
15969     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15970         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15971         while (currentMove > target) {
15972             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15973                 // null move cannot be undone. Reload program with move history before it.
15974                 int i;
15975                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15976                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15977                 }
15978                 SendBoard(&first, i);
15979               if(second.analyzing) SendBoard(&second, i);
15980                 for(currentMove=i; currentMove<target; currentMove++) {
15981                     SendMoveToProgram(currentMove, &first);
15982                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15983                 }
15984                 break;
15985             }
15986             SendToBoth("undo\n");
15987             currentMove--;
15988         }
15989     } else {
15990         currentMove = target;
15991     }
15992
15993     if (gameMode == EditGame || gameMode == EndOfGame) {
15994         whiteTimeRemaining = timeRemaining[0][currentMove];
15995         blackTimeRemaining = timeRemaining[1][currentMove];
15996     }
15997     DisplayBothClocks();
15998     DisplayMove(currentMove - 1);
15999     DrawPosition(full_redraw, boards[currentMove]);
16000     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
16001     // [HGM] PV info: routine tests if comment empty
16002     DisplayComment(currentMove - 1, commentList[currentMove]);
16003     ClearMap(); // [HGM] exclude: invalidate map
16004 }
16005
16006 void
16007 BackwardEvent ()
16008 {
16009     if (gameMode == IcsExamining && !pausing) {
16010         SendToICS(ics_prefix);
16011         SendToICS("backward\n");
16012     } else {
16013         BackwardInner(currentMove - 1);
16014     }
16015 }
16016
16017 void
16018 ToStartEvent ()
16019 {
16020     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16021         /* to optimize, we temporarily turn off analysis mode while we undo
16022          * all the moves. Otherwise we get analysis output after each undo.
16023          */
16024         if (first.analysisSupport) {
16025           SendToProgram("exit\nforce\n", &first);
16026           first.analyzing = FALSE;
16027         }
16028     }
16029
16030     if (gameMode == IcsExamining && !pausing) {
16031         SendToICS(ics_prefix);
16032         SendToICS("backward 999999\n");
16033     } else {
16034         BackwardInner(backwardMostMove);
16035     }
16036
16037     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16038         /* we have fed all the moves, so reactivate analysis mode */
16039         SendToProgram("analyze\n", &first);
16040         first.analyzing = TRUE;
16041         /*first.maybeThinking = TRUE;*/
16042         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
16043     }
16044 }
16045
16046 void
16047 ToNrEvent (int to)
16048 {
16049   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16050   if (to >= forwardMostMove) to = forwardMostMove;
16051   if (to <= backwardMostMove) to = backwardMostMove;
16052   if (to < currentMove) {
16053     BackwardInner(to);
16054   } else {
16055     ForwardInner(to);
16056   }
16057 }
16058
16059 void
16060 RevertEvent (Boolean annotate)
16061 {
16062     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16063         return;
16064     }
16065     if (gameMode != IcsExamining) {
16066         DisplayError(_("You are not examining a game"), 0);
16067         return;
16068     }
16069     if (pausing) {
16070         DisplayError(_("You can't revert while pausing"), 0);
16071         return;
16072     }
16073     SendToICS(ics_prefix);
16074     SendToICS("revert\n");
16075 }
16076
16077 void
16078 RetractMoveEvent ()
16079 {
16080     switch (gameMode) {
16081       case MachinePlaysWhite:
16082       case MachinePlaysBlack:
16083         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16084             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16085             return;
16086         }
16087         if (forwardMostMove < 2) return;
16088         currentMove = forwardMostMove = forwardMostMove - 2;
16089         whiteTimeRemaining = timeRemaining[0][currentMove];
16090         blackTimeRemaining = timeRemaining[1][currentMove];
16091         DisplayBothClocks();
16092         DisplayMove(currentMove - 1);
16093         ClearHighlights();/*!! could figure this out*/
16094         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16095         SendToProgram("remove\n", &first);
16096         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16097         break;
16098
16099       case BeginningOfGame:
16100       default:
16101         break;
16102
16103       case IcsPlayingWhite:
16104       case IcsPlayingBlack:
16105         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16106             SendToICS(ics_prefix);
16107             SendToICS("takeback 2\n");
16108         } else {
16109             SendToICS(ics_prefix);
16110             SendToICS("takeback 1\n");
16111         }
16112         break;
16113     }
16114 }
16115
16116 void
16117 MoveNowEvent ()
16118 {
16119     ChessProgramState *cps;
16120
16121     switch (gameMode) {
16122       case MachinePlaysWhite:
16123         if (!WhiteOnMove(forwardMostMove)) {
16124             DisplayError(_("It is your turn"), 0);
16125             return;
16126         }
16127         cps = &first;
16128         break;
16129       case MachinePlaysBlack:
16130         if (WhiteOnMove(forwardMostMove)) {
16131             DisplayError(_("It is your turn"), 0);
16132             return;
16133         }
16134         cps = &first;
16135         break;
16136       case TwoMachinesPlay:
16137         if (WhiteOnMove(forwardMostMove) ==
16138             (first.twoMachinesColor[0] == 'w')) {
16139             cps = &first;
16140         } else {
16141             cps = &second;
16142         }
16143         break;
16144       case BeginningOfGame:
16145       default:
16146         return;
16147     }
16148     SendToProgram("?\n", cps);
16149 }
16150
16151 void
16152 TruncateGameEvent ()
16153 {
16154     EditGameEvent();
16155     if (gameMode != EditGame) return;
16156     TruncateGame();
16157 }
16158
16159 void
16160 TruncateGame ()
16161 {
16162     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16163     if (forwardMostMove > currentMove) {
16164         if (gameInfo.resultDetails != NULL) {
16165             free(gameInfo.resultDetails);
16166             gameInfo.resultDetails = NULL;
16167             gameInfo.result = GameUnfinished;
16168         }
16169         forwardMostMove = currentMove;
16170         HistorySet(parseList, backwardMostMove, forwardMostMove,
16171                    currentMove-1);
16172     }
16173 }
16174
16175 void
16176 HintEvent ()
16177 {
16178     if (appData.noChessProgram) return;
16179     switch (gameMode) {
16180       case MachinePlaysWhite:
16181         if (WhiteOnMove(forwardMostMove)) {
16182             DisplayError(_("Wait until your turn."), 0);
16183             return;
16184         }
16185         break;
16186       case BeginningOfGame:
16187       case MachinePlaysBlack:
16188         if (!WhiteOnMove(forwardMostMove)) {
16189             DisplayError(_("Wait until your turn."), 0);
16190             return;
16191         }
16192         break;
16193       default:
16194         DisplayError(_("No hint available"), 0);
16195         return;
16196     }
16197     SendToProgram("hint\n", &first);
16198     hintRequested = TRUE;
16199 }
16200
16201 int
16202 SaveSelected (FILE *g, int dummy, char *dummy2)
16203 {
16204     ListGame * lg = (ListGame *) gameList.head;
16205     int nItem, cnt=0;
16206     FILE *f;
16207
16208     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16209         DisplayError(_("Game list not loaded or empty"), 0);
16210         return 0;
16211     }
16212
16213     creatingBook = TRUE; // suppresses stuff during load game
16214
16215     /* Get list size */
16216     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16217         if(lg->position >= 0) { // selected?
16218             LoadGame(f, nItem, "", TRUE);
16219             SaveGamePGN2(g); // leaves g open
16220             cnt++; DoEvents();
16221         }
16222         lg = (ListGame *) lg->node.succ;
16223     }
16224
16225     fclose(g);
16226     creatingBook = FALSE;
16227
16228     return cnt;
16229 }
16230
16231 void
16232 CreateBookEvent ()
16233 {
16234     ListGame * lg = (ListGame *) gameList.head;
16235     FILE *f, *g;
16236     int nItem;
16237     static int secondTime = FALSE;
16238
16239     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16240         DisplayError(_("Game list not loaded or empty"), 0);
16241         return;
16242     }
16243
16244     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16245         fclose(g);
16246         secondTime++;
16247         DisplayNote(_("Book file exists! Try again for overwrite."));
16248         return;
16249     }
16250
16251     creatingBook = TRUE;
16252     secondTime = FALSE;
16253
16254     /* Get list size */
16255     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16256         if(lg->position >= 0) {
16257             LoadGame(f, nItem, "", TRUE);
16258             AddGameToBook(TRUE);
16259             DoEvents();
16260         }
16261         lg = (ListGame *) lg->node.succ;
16262     }
16263
16264     creatingBook = FALSE;
16265     FlushBook();
16266 }
16267
16268 void
16269 BookEvent ()
16270 {
16271     if (appData.noChessProgram) return;
16272     switch (gameMode) {
16273       case MachinePlaysWhite:
16274         if (WhiteOnMove(forwardMostMove)) {
16275             DisplayError(_("Wait until your turn."), 0);
16276             return;
16277         }
16278         break;
16279       case BeginningOfGame:
16280       case MachinePlaysBlack:
16281         if (!WhiteOnMove(forwardMostMove)) {
16282             DisplayError(_("Wait until your turn."), 0);
16283             return;
16284         }
16285         break;
16286       case EditPosition:
16287         EditPositionDone(TRUE);
16288         break;
16289       case TwoMachinesPlay:
16290         return;
16291       default:
16292         break;
16293     }
16294     SendToProgram("bk\n", &first);
16295     bookOutput[0] = NULLCHAR;
16296     bookRequested = TRUE;
16297 }
16298
16299 void
16300 AboutGameEvent ()
16301 {
16302     char *tags = PGNTags(&gameInfo);
16303     TagsPopUp(tags, CmailMsg());
16304     free(tags);
16305 }
16306
16307 /* end button procedures */
16308
16309 void
16310 PrintPosition (FILE *fp, int move)
16311 {
16312     int i, j;
16313
16314     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16315         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16316             char c = PieceToChar(boards[move][i][j]);
16317             fputc(c == '?' ? '.' : c, fp);
16318             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16319         }
16320     }
16321     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16322       fprintf(fp, "white to play\n");
16323     else
16324       fprintf(fp, "black to play\n");
16325 }
16326
16327 void
16328 PrintOpponents (FILE *fp)
16329 {
16330     if (gameInfo.white != NULL) {
16331         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16332     } else {
16333         fprintf(fp, "\n");
16334     }
16335 }
16336
16337 /* Find last component of program's own name, using some heuristics */
16338 void
16339 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16340 {
16341     char *p, *q, c;
16342     int local = (strcmp(host, "localhost") == 0);
16343     while (!local && (p = strchr(prog, ';')) != NULL) {
16344         p++;
16345         while (*p == ' ') p++;
16346         prog = p;
16347     }
16348     if (*prog == '"' || *prog == '\'') {
16349         q = strchr(prog + 1, *prog);
16350     } else {
16351         q = strchr(prog, ' ');
16352     }
16353     if (q == NULL) q = prog + strlen(prog);
16354     p = q;
16355     while (p >= prog && *p != '/' && *p != '\\') p--;
16356     p++;
16357     if(p == prog && *p == '"') p++;
16358     c = *q; *q = 0;
16359     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16360     memcpy(buf, p, q - p);
16361     buf[q - p] = NULLCHAR;
16362     if (!local) {
16363         strcat(buf, "@");
16364         strcat(buf, host);
16365     }
16366 }
16367
16368 char *
16369 TimeControlTagValue ()
16370 {
16371     char buf[MSG_SIZ];
16372     if (!appData.clockMode) {
16373       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16374     } else if (movesPerSession > 0) {
16375       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16376     } else if (timeIncrement == 0) {
16377       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16378     } else {
16379       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16380     }
16381     return StrSave(buf);
16382 }
16383
16384 void
16385 SetGameInfo ()
16386 {
16387     /* This routine is used only for certain modes */
16388     VariantClass v = gameInfo.variant;
16389     ChessMove r = GameUnfinished;
16390     char *p = NULL;
16391
16392     if(keepInfo) return;
16393
16394     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16395         r = gameInfo.result;
16396         p = gameInfo.resultDetails;
16397         gameInfo.resultDetails = NULL;
16398     }
16399     ClearGameInfo(&gameInfo);
16400     gameInfo.variant = v;
16401
16402     switch (gameMode) {
16403       case MachinePlaysWhite:
16404         gameInfo.event = StrSave( appData.pgnEventHeader );
16405         gameInfo.site = StrSave(HostName());
16406         gameInfo.date = PGNDate();
16407         gameInfo.round = StrSave("-");
16408         gameInfo.white = StrSave(first.tidy);
16409         gameInfo.black = StrSave(UserName());
16410         gameInfo.timeControl = TimeControlTagValue();
16411         break;
16412
16413       case MachinePlaysBlack:
16414         gameInfo.event = StrSave( appData.pgnEventHeader );
16415         gameInfo.site = StrSave(HostName());
16416         gameInfo.date = PGNDate();
16417         gameInfo.round = StrSave("-");
16418         gameInfo.white = StrSave(UserName());
16419         gameInfo.black = StrSave(first.tidy);
16420         gameInfo.timeControl = TimeControlTagValue();
16421         break;
16422
16423       case TwoMachinesPlay:
16424         gameInfo.event = StrSave( appData.pgnEventHeader );
16425         gameInfo.site = StrSave(HostName());
16426         gameInfo.date = PGNDate();
16427         if (roundNr > 0) {
16428             char buf[MSG_SIZ];
16429             snprintf(buf, MSG_SIZ, "%d", roundNr);
16430             gameInfo.round = StrSave(buf);
16431         } else {
16432             gameInfo.round = StrSave("-");
16433         }
16434         if (first.twoMachinesColor[0] == 'w') {
16435             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16436             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16437         } else {
16438             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16439             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16440         }
16441         gameInfo.timeControl = TimeControlTagValue();
16442         break;
16443
16444       case EditGame:
16445         gameInfo.event = StrSave("Edited game");
16446         gameInfo.site = StrSave(HostName());
16447         gameInfo.date = PGNDate();
16448         gameInfo.round = StrSave("-");
16449         gameInfo.white = StrSave("-");
16450         gameInfo.black = StrSave("-");
16451         gameInfo.result = r;
16452         gameInfo.resultDetails = p;
16453         break;
16454
16455       case EditPosition:
16456         gameInfo.event = StrSave("Edited position");
16457         gameInfo.site = StrSave(HostName());
16458         gameInfo.date = PGNDate();
16459         gameInfo.round = StrSave("-");
16460         gameInfo.white = StrSave("-");
16461         gameInfo.black = StrSave("-");
16462         break;
16463
16464       case IcsPlayingWhite:
16465       case IcsPlayingBlack:
16466       case IcsObserving:
16467       case IcsExamining:
16468         break;
16469
16470       case PlayFromGameFile:
16471         gameInfo.event = StrSave("Game from non-PGN file");
16472         gameInfo.site = StrSave(HostName());
16473         gameInfo.date = PGNDate();
16474         gameInfo.round = StrSave("-");
16475         gameInfo.white = StrSave("?");
16476         gameInfo.black = StrSave("?");
16477         break;
16478
16479       default:
16480         break;
16481     }
16482 }
16483
16484 void
16485 ReplaceComment (int index, char *text)
16486 {
16487     int len;
16488     char *p;
16489     float score;
16490
16491     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16492        pvInfoList[index-1].depth == len &&
16493        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16494        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16495     while (*text == '\n') text++;
16496     len = strlen(text);
16497     while (len > 0 && text[len - 1] == '\n') len--;
16498
16499     if (commentList[index] != NULL)
16500       free(commentList[index]);
16501
16502     if (len == 0) {
16503         commentList[index] = NULL;
16504         return;
16505     }
16506   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16507       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16508       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16509     commentList[index] = (char *) malloc(len + 2);
16510     strncpy(commentList[index], text, len);
16511     commentList[index][len] = '\n';
16512     commentList[index][len + 1] = NULLCHAR;
16513   } else {
16514     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16515     char *p;
16516     commentList[index] = (char *) malloc(len + 7);
16517     safeStrCpy(commentList[index], "{\n", 3);
16518     safeStrCpy(commentList[index]+2, text, len+1);
16519     commentList[index][len+2] = NULLCHAR;
16520     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16521     strcat(commentList[index], "\n}\n");
16522   }
16523 }
16524
16525 void
16526 CrushCRs (char *text)
16527 {
16528   char *p = text;
16529   char *q = text;
16530   char ch;
16531
16532   do {
16533     ch = *p++;
16534     if (ch == '\r') continue;
16535     *q++ = ch;
16536   } while (ch != '\0');
16537 }
16538
16539 void
16540 AppendComment (int index, char *text, Boolean addBraces)
16541 /* addBraces  tells if we should add {} */
16542 {
16543     int oldlen, len;
16544     char *old;
16545
16546 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16547     if(addBraces == 3) addBraces = 0; else // force appending literally
16548     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16549
16550     CrushCRs(text);
16551     while (*text == '\n') text++;
16552     len = strlen(text);
16553     while (len > 0 && text[len - 1] == '\n') len--;
16554     text[len] = NULLCHAR;
16555
16556     if (len == 0) return;
16557
16558     if (commentList[index] != NULL) {
16559       Boolean addClosingBrace = addBraces;
16560         old = commentList[index];
16561         oldlen = strlen(old);
16562         while(commentList[index][oldlen-1] ==  '\n')
16563           commentList[index][--oldlen] = NULLCHAR;
16564         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16565         safeStrCpy(commentList[index], old, oldlen + len + 6);
16566         free(old);
16567         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16568         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16569           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16570           while (*text == '\n') { text++; len--; }
16571           commentList[index][--oldlen] = NULLCHAR;
16572       }
16573         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16574         else          strcat(commentList[index], "\n");
16575         strcat(commentList[index], text);
16576         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16577         else          strcat(commentList[index], "\n");
16578     } else {
16579         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16580         if(addBraces)
16581           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16582         else commentList[index][0] = NULLCHAR;
16583         strcat(commentList[index], text);
16584         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16585         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16586     }
16587 }
16588
16589 static char *
16590 FindStr (char * text, char * sub_text)
16591 {
16592     char * result = strstr( text, sub_text );
16593
16594     if( result != NULL ) {
16595         result += strlen( sub_text );
16596     }
16597
16598     return result;
16599 }
16600
16601 /* [AS] Try to extract PV info from PGN comment */
16602 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16603 char *
16604 GetInfoFromComment (int index, char * text)
16605 {
16606     char * sep = text, *p;
16607
16608     if( text != NULL && index > 0 ) {
16609         int score = 0;
16610         int depth = 0;
16611         int time = -1, sec = 0, deci;
16612         char * s_eval = FindStr( text, "[%eval " );
16613         char * s_emt = FindStr( text, "[%emt " );
16614 #if 0
16615         if( s_eval != NULL || s_emt != NULL ) {
16616 #else
16617         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16618 #endif
16619             /* New style */
16620             char delim;
16621
16622             if( s_eval != NULL ) {
16623                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16624                     return text;
16625                 }
16626
16627                 if( delim != ']' ) {
16628                     return text;
16629                 }
16630             }
16631
16632             if( s_emt != NULL ) {
16633             }
16634                 return text;
16635         }
16636         else {
16637             /* We expect something like: [+|-]nnn.nn/dd */
16638             int score_lo = 0;
16639
16640             if(*text != '{') return text; // [HGM] braces: must be normal comment
16641
16642             sep = strchr( text, '/' );
16643             if( sep == NULL || sep < (text+4) ) {
16644                 return text;
16645             }
16646
16647             p = text;
16648             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16649             if(p[1] == '(') { // comment starts with PV
16650                p = strchr(p, ')'); // locate end of PV
16651                if(p == NULL || sep < p+5) return text;
16652                // at this point we have something like "{(.*) +0.23/6 ..."
16653                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16654                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16655                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16656             }
16657             time = -1; sec = -1; deci = -1;
16658             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16659                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16660                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16661                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16662                 return text;
16663             }
16664
16665             if( score_lo < 0 || score_lo >= 100 ) {
16666                 return text;
16667             }
16668
16669             if(sec >= 0) time = 600*time + 10*sec; else
16670             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16671
16672             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16673
16674             /* [HGM] PV time: now locate end of PV info */
16675             while( *++sep >= '0' && *sep <= '9'); // strip depth
16676             if(time >= 0)
16677             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16678             if(sec >= 0)
16679             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16680             if(deci >= 0)
16681             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16682             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16683         }
16684
16685         if( depth <= 0 ) {
16686             return text;
16687         }
16688
16689         if( time < 0 ) {
16690             time = -1;
16691         }
16692
16693         pvInfoList[index-1].depth = depth;
16694         pvInfoList[index-1].score = score;
16695         pvInfoList[index-1].time  = 10*time; // centi-sec
16696         if(*sep == '}') *sep = 0; else *--sep = '{';
16697         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16698     }
16699     return sep;
16700 }
16701
16702 void
16703 SendToProgram (char *message, ChessProgramState *cps)
16704 {
16705     int count, outCount, error;
16706     char buf[MSG_SIZ];
16707
16708     if (cps->pr == NoProc) return;
16709     Attention(cps);
16710
16711     if (appData.debugMode) {
16712         TimeMark now;
16713         GetTimeMark(&now);
16714         fprintf(debugFP, "%ld >%-6s: %s",
16715                 SubtractTimeMarks(&now, &programStartTime),
16716                 cps->which, message);
16717         if(serverFP)
16718             fprintf(serverFP, "%ld >%-6s: %s",
16719                 SubtractTimeMarks(&now, &programStartTime),
16720                 cps->which, message), fflush(serverFP);
16721     }
16722
16723     count = strlen(message);
16724     outCount = OutputToProcess(cps->pr, message, count, &error);
16725     if (outCount < count && !exiting
16726                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16727       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16728       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16729         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16730             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16731                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16732                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16733                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16734             } else {
16735                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16736                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16737                 gameInfo.result = res;
16738             }
16739             gameInfo.resultDetails = StrSave(buf);
16740         }
16741         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16742         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16743     }
16744 }
16745
16746 void
16747 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16748 {
16749     char *end_str;
16750     char buf[MSG_SIZ];
16751     ChessProgramState *cps = (ChessProgramState *)closure;
16752
16753     if (isr != cps->isr) return; /* Killed intentionally */
16754     if (count <= 0) {
16755         if (count == 0) {
16756             RemoveInputSource(cps->isr);
16757             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16758                     _(cps->which), cps->program);
16759             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16760             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16761                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16762                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16763                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16764                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16765                 } else {
16766                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16767                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16768                     gameInfo.result = res;
16769                 }
16770                 gameInfo.resultDetails = StrSave(buf);
16771             }
16772             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16773             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16774         } else {
16775             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16776                     _(cps->which), cps->program);
16777             RemoveInputSource(cps->isr);
16778
16779             /* [AS] Program is misbehaving badly... kill it */
16780             if( count == -2 ) {
16781                 DestroyChildProcess( cps->pr, 9 );
16782                 cps->pr = NoProc;
16783             }
16784
16785             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16786         }
16787         return;
16788     }
16789
16790     if ((end_str = strchr(message, '\r')) != NULL)
16791       *end_str = NULLCHAR;
16792     if ((end_str = strchr(message, '\n')) != NULL)
16793       *end_str = NULLCHAR;
16794
16795     if (appData.debugMode) {
16796         TimeMark now; int print = 1;
16797         char *quote = ""; char c; int i;
16798
16799         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16800                 char start = message[0];
16801                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16802                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16803                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16804                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16805                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16806                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16807                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16808                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16809                    sscanf(message, "hint: %c", &c)!=1 &&
16810                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16811                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16812                     print = (appData.engineComments >= 2);
16813                 }
16814                 message[0] = start; // restore original message
16815         }
16816         if(print) {
16817                 GetTimeMark(&now);
16818                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16819                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16820                         quote,
16821                         message);
16822                 if(serverFP)
16823                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16824                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16825                         quote,
16826                         message), fflush(serverFP);
16827         }
16828     }
16829
16830     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16831     if (appData.icsEngineAnalyze) {
16832         if (strstr(message, "whisper") != NULL ||
16833              strstr(message, "kibitz") != NULL ||
16834             strstr(message, "tellics") != NULL) return;
16835     }
16836
16837     HandleMachineMove(message, cps);
16838 }
16839
16840
16841 void
16842 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16843 {
16844     char buf[MSG_SIZ];
16845     int seconds;
16846
16847     if( timeControl_2 > 0 ) {
16848         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16849             tc = timeControl_2;
16850         }
16851     }
16852     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16853     inc /= cps->timeOdds;
16854     st  /= cps->timeOdds;
16855
16856     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16857
16858     if (st > 0) {
16859       /* Set exact time per move, normally using st command */
16860       if (cps->stKludge) {
16861         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16862         seconds = st % 60;
16863         if (seconds == 0) {
16864           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16865         } else {
16866           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16867         }
16868       } else {
16869         snprintf(buf, MSG_SIZ, "st %d\n", st);
16870       }
16871     } else {
16872       /* Set conventional or incremental time control, using level command */
16873       if (seconds == 0) {
16874         /* Note old gnuchess bug -- minutes:seconds used to not work.
16875            Fixed in later versions, but still avoid :seconds
16876            when seconds is 0. */
16877         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16878       } else {
16879         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16880                  seconds, inc/1000.);
16881       }
16882     }
16883     SendToProgram(buf, cps);
16884
16885     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16886     /* Orthogonally, limit search to given depth */
16887     if (sd > 0) {
16888       if (cps->sdKludge) {
16889         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16890       } else {
16891         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16892       }
16893       SendToProgram(buf, cps);
16894     }
16895
16896     if(cps->nps >= 0) { /* [HGM] nps */
16897         if(cps->supportsNPS == FALSE)
16898           cps->nps = -1; // don't use if engine explicitly says not supported!
16899         else {
16900           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16901           SendToProgram(buf, cps);
16902         }
16903     }
16904 }
16905
16906 ChessProgramState *
16907 WhitePlayer ()
16908 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16909 {
16910     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16911        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16912         return &second;
16913     return &first;
16914 }
16915
16916 void
16917 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16918 {
16919     char message[MSG_SIZ];
16920     long time, otime;
16921
16922     /* Note: this routine must be called when the clocks are stopped
16923        or when they have *just* been set or switched; otherwise
16924        it will be off by the time since the current tick started.
16925     */
16926     if (machineWhite) {
16927         time = whiteTimeRemaining / 10;
16928         otime = blackTimeRemaining / 10;
16929     } else {
16930         time = blackTimeRemaining / 10;
16931         otime = whiteTimeRemaining / 10;
16932     }
16933     /* [HGM] translate opponent's time by time-odds factor */
16934     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16935
16936     if (time <= 0) time = 1;
16937     if (otime <= 0) otime = 1;
16938
16939     snprintf(message, MSG_SIZ, "time %ld\n", time);
16940     SendToProgram(message, cps);
16941
16942     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16943     SendToProgram(message, cps);
16944 }
16945
16946 char *
16947 EngineDefinedVariant (ChessProgramState *cps, int n)
16948 {   // return name of n-th unknown variant that engine supports
16949     static char buf[MSG_SIZ];
16950     char *p, *s = cps->variants;
16951     if(!s) return NULL;
16952     do { // parse string from variants feature
16953       VariantClass v;
16954         p = strchr(s, ',');
16955         if(p) *p = NULLCHAR;
16956       v = StringToVariant(s);
16957       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16958         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16959             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16960                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16961                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16962                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16963             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16964         }
16965         if(p) *p++ = ',';
16966         if(n < 0) return buf;
16967     } while(s = p);
16968     return NULL;
16969 }
16970
16971 int
16972 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16973 {
16974   char buf[MSG_SIZ];
16975   int len = strlen(name);
16976   int val;
16977
16978   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16979     (*p) += len + 1;
16980     sscanf(*p, "%d", &val);
16981     *loc = (val != 0);
16982     while (**p && **p != ' ')
16983       (*p)++;
16984     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16985     SendToProgram(buf, cps);
16986     return TRUE;
16987   }
16988   return FALSE;
16989 }
16990
16991 int
16992 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16993 {
16994   char buf[MSG_SIZ];
16995   int len = strlen(name);
16996   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16997     (*p) += len + 1;
16998     sscanf(*p, "%d", loc);
16999     while (**p && **p != ' ') (*p)++;
17000     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17001     SendToProgram(buf, cps);
17002     return TRUE;
17003   }
17004   return FALSE;
17005 }
17006
17007 int
17008 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
17009 {
17010   char buf[MSG_SIZ];
17011   int len = strlen(name);
17012   if (strncmp((*p), name, len) == 0
17013       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
17014     (*p) += len + 2;
17015     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
17016     sscanf(*p, "%[^\"]", *loc);
17017     while (**p && **p != '\"') (*p)++;
17018     if (**p == '\"') (*p)++;
17019     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
17020     SendToProgram(buf, cps);
17021     return TRUE;
17022   }
17023   return FALSE;
17024 }
17025
17026 int
17027 ParseOption (Option *opt, ChessProgramState *cps)
17028 // [HGM] options: process the string that defines an engine option, and determine
17029 // name, type, default value, and allowed value range
17030 {
17031         char *p, *q, buf[MSG_SIZ];
17032         int n, min = (-1)<<31, max = 1<<31, def;
17033
17034         opt->target = &opt->value;   // OK for spin/slider and checkbox
17035         if(p = strstr(opt->name, " -spin ")) {
17036             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17037             if(max < min) max = min; // enforce consistency
17038             if(def < min) def = min;
17039             if(def > max) def = max;
17040             opt->value = def;
17041             opt->min = min;
17042             opt->max = max;
17043             opt->type = Spin;
17044         } else if((p = strstr(opt->name, " -slider "))) {
17045             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
17046             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
17047             if(max < min) max = min; // enforce consistency
17048             if(def < min) def = min;
17049             if(def > max) def = max;
17050             opt->value = def;
17051             opt->min = min;
17052             opt->max = max;
17053             opt->type = Spin; // Slider;
17054         } else if((p = strstr(opt->name, " -string "))) {
17055             opt->textValue = p+9;
17056             opt->type = TextBox;
17057             opt->target = &opt->textValue;
17058         } else if((p = strstr(opt->name, " -file "))) {
17059             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17060             opt->target = opt->textValue = p+7;
17061             opt->type = FileName; // FileName;
17062             opt->target = &opt->textValue;
17063         } else if((p = strstr(opt->name, " -path "))) {
17064             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17065             opt->target = opt->textValue = p+7;
17066             opt->type = PathName; // PathName;
17067             opt->target = &opt->textValue;
17068         } else if(p = strstr(opt->name, " -check ")) {
17069             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17070             opt->value = (def != 0);
17071             opt->type = CheckBox;
17072         } else if(p = strstr(opt->name, " -combo ")) {
17073             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17074             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17075             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17076             opt->value = n = 0;
17077             while(q = StrStr(q, " /// ")) {
17078                 n++; *q = 0;    // count choices, and null-terminate each of them
17079                 q += 5;
17080                 if(*q == '*') { // remember default, which is marked with * prefix
17081                     q++;
17082                     opt->value = n;
17083                 }
17084                 cps->comboList[cps->comboCnt++] = q;
17085             }
17086             cps->comboList[cps->comboCnt++] = NULL;
17087             opt->max = n + 1;
17088             opt->type = ComboBox;
17089         } else if(p = strstr(opt->name, " -button")) {
17090             opt->type = Button;
17091         } else if(p = strstr(opt->name, " -save")) {
17092             opt->type = SaveButton;
17093         } else return FALSE;
17094         *p = 0; // terminate option name
17095         // now look if the command-line options define a setting for this engine option.
17096         if(cps->optionSettings && cps->optionSettings[0])
17097             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17098         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17099           snprintf(buf, MSG_SIZ, "option %s", p);
17100                 if(p = strstr(buf, ",")) *p = 0;
17101                 if(q = strchr(buf, '=')) switch(opt->type) {
17102                     case ComboBox:
17103                         for(n=0; n<opt->max; n++)
17104                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17105                         break;
17106                     case TextBox:
17107                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17108                         break;
17109                     case Spin:
17110                     case CheckBox:
17111                         opt->value = atoi(q+1);
17112                     default:
17113                         break;
17114                 }
17115                 strcat(buf, "\n");
17116                 SendToProgram(buf, cps);
17117         }
17118         return TRUE;
17119 }
17120
17121 void
17122 FeatureDone (ChessProgramState *cps, int val)
17123 {
17124   DelayedEventCallback cb = GetDelayedEvent();
17125   if ((cb == InitBackEnd3 && cps == &first) ||
17126       (cb == SettingsMenuIfReady && cps == &second) ||
17127       (cb == LoadEngine) ||
17128       (cb == TwoMachinesEventIfReady)) {
17129     CancelDelayedEvent();
17130     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17131   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17132   cps->initDone = val;
17133   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17134 }
17135
17136 /* Parse feature command from engine */
17137 void
17138 ParseFeatures (char *args, ChessProgramState *cps)
17139 {
17140   char *p = args;
17141   char *q = NULL;
17142   int val;
17143   char buf[MSG_SIZ];
17144
17145   for (;;) {
17146     while (*p == ' ') p++;
17147     if (*p == NULLCHAR) return;
17148
17149     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17150     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17151     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17152     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17153     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17154     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17155     if (BoolFeature(&p, "reuse", &val, cps)) {
17156       /* Engine can disable reuse, but can't enable it if user said no */
17157       if (!val) cps->reuse = FALSE;
17158       continue;
17159     }
17160     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17161     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17162       if (gameMode == TwoMachinesPlay) {
17163         DisplayTwoMachinesTitle();
17164       } else {
17165         DisplayTitle("");
17166       }
17167       continue;
17168     }
17169     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17170     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17171     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17172     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17173     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17174     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17175     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17176     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17177     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17178     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17179     if (IntFeature(&p, "done", &val, cps)) {
17180       FeatureDone(cps, val);
17181       continue;
17182     }
17183     /* Added by Tord: */
17184     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17185     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17186     /* End of additions by Tord */
17187
17188     /* [HGM] added features: */
17189     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17190     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17191     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17192     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17193     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17194     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17195     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17196     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17197         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17198         FREE(cps->option[cps->nrOptions].name);
17199         cps->option[cps->nrOptions].name = q; q = NULL;
17200         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17201           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17202             SendToProgram(buf, cps);
17203             continue;
17204         }
17205         if(cps->nrOptions >= MAX_OPTIONS) {
17206             cps->nrOptions--;
17207             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17208             DisplayError(buf, 0);
17209         }
17210         continue;
17211     }
17212     /* End of additions by HGM */
17213
17214     /* unknown feature: complain and skip */
17215     q = p;
17216     while (*q && *q != '=') q++;
17217     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17218     SendToProgram(buf, cps);
17219     p = q;
17220     if (*p == '=') {
17221       p++;
17222       if (*p == '\"') {
17223         p++;
17224         while (*p && *p != '\"') p++;
17225         if (*p == '\"') p++;
17226       } else {
17227         while (*p && *p != ' ') p++;
17228       }
17229     }
17230   }
17231
17232 }
17233
17234 void
17235 PeriodicUpdatesEvent (int newState)
17236 {
17237     if (newState == appData.periodicUpdates)
17238       return;
17239
17240     appData.periodicUpdates=newState;
17241
17242     /* Display type changes, so update it now */
17243 //    DisplayAnalysis();
17244
17245     /* Get the ball rolling again... */
17246     if (newState) {
17247         AnalysisPeriodicEvent(1);
17248         StartAnalysisClock();
17249     }
17250 }
17251
17252 void
17253 PonderNextMoveEvent (int newState)
17254 {
17255     if (newState == appData.ponderNextMove) return;
17256     if (gameMode == EditPosition) EditPositionDone(TRUE);
17257     if (newState) {
17258         SendToProgram("hard\n", &first);
17259         if (gameMode == TwoMachinesPlay) {
17260             SendToProgram("hard\n", &second);
17261         }
17262     } else {
17263         SendToProgram("easy\n", &first);
17264         thinkOutput[0] = NULLCHAR;
17265         if (gameMode == TwoMachinesPlay) {
17266             SendToProgram("easy\n", &second);
17267         }
17268     }
17269     appData.ponderNextMove = newState;
17270 }
17271
17272 void
17273 NewSettingEvent (int option, int *feature, char *command, int value)
17274 {
17275     char buf[MSG_SIZ];
17276
17277     if (gameMode == EditPosition) EditPositionDone(TRUE);
17278     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17279     if(feature == NULL || *feature) SendToProgram(buf, &first);
17280     if (gameMode == TwoMachinesPlay) {
17281         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17282     }
17283 }
17284
17285 void
17286 ShowThinkingEvent ()
17287 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17288 {
17289     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17290     int newState = appData.showThinking
17291         // [HGM] thinking: other features now need thinking output as well
17292         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17293
17294     if (oldState == newState) return;
17295     oldState = newState;
17296     if (gameMode == EditPosition) EditPositionDone(TRUE);
17297     if (oldState) {
17298         SendToProgram("post\n", &first);
17299         if (gameMode == TwoMachinesPlay) {
17300             SendToProgram("post\n", &second);
17301         }
17302     } else {
17303         SendToProgram("nopost\n", &first);
17304         thinkOutput[0] = NULLCHAR;
17305         if (gameMode == TwoMachinesPlay) {
17306             SendToProgram("nopost\n", &second);
17307         }
17308     }
17309 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17310 }
17311
17312 void
17313 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17314 {
17315   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17316   if (pr == NoProc) return;
17317   AskQuestion(title, question, replyPrefix, pr);
17318 }
17319
17320 void
17321 TypeInEvent (char firstChar)
17322 {
17323     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17324         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17325         gameMode == AnalyzeMode || gameMode == EditGame ||
17326         gameMode == EditPosition || gameMode == IcsExamining ||
17327         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17328         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17329                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17330                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17331         gameMode == Training) PopUpMoveDialog(firstChar);
17332 }
17333
17334 void
17335 TypeInDoneEvent (char *move)
17336 {
17337         Board board;
17338         int n, fromX, fromY, toX, toY;
17339         char promoChar;
17340         ChessMove moveType;
17341
17342         // [HGM] FENedit
17343         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17344                 EditPositionPasteFEN(move);
17345                 return;
17346         }
17347         // [HGM] movenum: allow move number to be typed in any mode
17348         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17349           ToNrEvent(2*n-1);
17350           return;
17351         }
17352         // undocumented kludge: allow command-line option to be typed in!
17353         // (potentially fatal, and does not implement the effect of the option.)
17354         // should only be used for options that are values on which future decisions will be made,
17355         // and definitely not on options that would be used during initialization.
17356         if(strstr(move, "!!! -") == move) {
17357             ParseArgsFromString(move+4);
17358             return;
17359         }
17360
17361       if (gameMode != EditGame && currentMove != forwardMostMove &&
17362         gameMode != Training) {
17363         DisplayMoveError(_("Displayed move is not current"));
17364       } else {
17365         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17366           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17367         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17368         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17369           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17370           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17371         } else {
17372           DisplayMoveError(_("Could not parse move"));
17373         }
17374       }
17375 }
17376
17377 void
17378 DisplayMove (int moveNumber)
17379 {
17380     char message[MSG_SIZ];
17381     char res[MSG_SIZ];
17382     char cpThinkOutput[MSG_SIZ];
17383
17384     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17385
17386     if (moveNumber == forwardMostMove - 1 ||
17387         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17388
17389         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17390
17391         if (strchr(cpThinkOutput, '\n')) {
17392             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17393         }
17394     } else {
17395         *cpThinkOutput = NULLCHAR;
17396     }
17397
17398     /* [AS] Hide thinking from human user */
17399     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17400         *cpThinkOutput = NULLCHAR;
17401         if( thinkOutput[0] != NULLCHAR ) {
17402             int i;
17403
17404             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17405                 cpThinkOutput[i] = '.';
17406             }
17407             cpThinkOutput[i] = NULLCHAR;
17408             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17409         }
17410     }
17411
17412     if (moveNumber == forwardMostMove - 1 &&
17413         gameInfo.resultDetails != NULL) {
17414         if (gameInfo.resultDetails[0] == NULLCHAR) {
17415           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17416         } else {
17417           snprintf(res, MSG_SIZ, " {%s} %s",
17418                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17419         }
17420     } else {
17421         res[0] = NULLCHAR;
17422     }
17423
17424     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17425         DisplayMessage(res, cpThinkOutput);
17426     } else {
17427       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17428                 WhiteOnMove(moveNumber) ? " " : ".. ",
17429                 parseList[moveNumber], res);
17430         DisplayMessage(message, cpThinkOutput);
17431     }
17432 }
17433
17434 void
17435 DisplayComment (int moveNumber, char *text)
17436 {
17437     char title[MSG_SIZ];
17438
17439     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17440       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17441     } else {
17442       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17443               WhiteOnMove(moveNumber) ? " " : ".. ",
17444               parseList[moveNumber]);
17445     }
17446     if (text != NULL && (appData.autoDisplayComment || commentUp))
17447         CommentPopUp(title, text);
17448 }
17449
17450 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17451  * might be busy thinking or pondering.  It can be omitted if your
17452  * gnuchess is configured to stop thinking immediately on any user
17453  * input.  However, that gnuchess feature depends on the FIONREAD
17454  * ioctl, which does not work properly on some flavors of Unix.
17455  */
17456 void
17457 Attention (ChessProgramState *cps)
17458 {
17459 #if ATTENTION
17460     if (!cps->useSigint) return;
17461     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17462     switch (gameMode) {
17463       case MachinePlaysWhite:
17464       case MachinePlaysBlack:
17465       case TwoMachinesPlay:
17466       case IcsPlayingWhite:
17467       case IcsPlayingBlack:
17468       case AnalyzeMode:
17469       case AnalyzeFile:
17470         /* Skip if we know it isn't thinking */
17471         if (!cps->maybeThinking) return;
17472         if (appData.debugMode)
17473           fprintf(debugFP, "Interrupting %s\n", cps->which);
17474         InterruptChildProcess(cps->pr);
17475         cps->maybeThinking = FALSE;
17476         break;
17477       default:
17478         break;
17479     }
17480 #endif /*ATTENTION*/
17481 }
17482
17483 int
17484 CheckFlags ()
17485 {
17486     if (whiteTimeRemaining <= 0) {
17487         if (!whiteFlag) {
17488             whiteFlag = TRUE;
17489             if (appData.icsActive) {
17490                 if (appData.autoCallFlag &&
17491                     gameMode == IcsPlayingBlack && !blackFlag) {
17492                   SendToICS(ics_prefix);
17493                   SendToICS("flag\n");
17494                 }
17495             } else {
17496                 if (blackFlag) {
17497                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17498                 } else {
17499                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17500                     if (appData.autoCallFlag) {
17501                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17502                         return TRUE;
17503                     }
17504                 }
17505             }
17506         }
17507     }
17508     if (blackTimeRemaining <= 0) {
17509         if (!blackFlag) {
17510             blackFlag = TRUE;
17511             if (appData.icsActive) {
17512                 if (appData.autoCallFlag &&
17513                     gameMode == IcsPlayingWhite && !whiteFlag) {
17514                   SendToICS(ics_prefix);
17515                   SendToICS("flag\n");
17516                 }
17517             } else {
17518                 if (whiteFlag) {
17519                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17520                 } else {
17521                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17522                     if (appData.autoCallFlag) {
17523                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17524                         return TRUE;
17525                     }
17526                 }
17527             }
17528         }
17529     }
17530     return FALSE;
17531 }
17532
17533 void
17534 CheckTimeControl ()
17535 {
17536     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17537         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17538
17539     /*
17540      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17541      */
17542     if ( !WhiteOnMove(forwardMostMove) ) {
17543         /* White made time control */
17544         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17545         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17546         /* [HGM] time odds: correct new time quota for time odds! */
17547                                             / WhitePlayer()->timeOdds;
17548         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17549     } else {
17550         lastBlack -= blackTimeRemaining;
17551         /* Black made time control */
17552         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17553                                             / WhitePlayer()->other->timeOdds;
17554         lastWhite = whiteTimeRemaining;
17555     }
17556 }
17557
17558 void
17559 DisplayBothClocks ()
17560 {
17561     int wom = gameMode == EditPosition ?
17562       !blackPlaysFirst : WhiteOnMove(currentMove);
17563     DisplayWhiteClock(whiteTimeRemaining, wom);
17564     DisplayBlackClock(blackTimeRemaining, !wom);
17565 }
17566
17567
17568 /* Timekeeping seems to be a portability nightmare.  I think everyone
17569    has ftime(), but I'm really not sure, so I'm including some ifdefs
17570    to use other calls if you don't.  Clocks will be less accurate if
17571    you have neither ftime nor gettimeofday.
17572 */
17573
17574 /* VS 2008 requires the #include outside of the function */
17575 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17576 #include <sys/timeb.h>
17577 #endif
17578
17579 /* Get the current time as a TimeMark */
17580 void
17581 GetTimeMark (TimeMark *tm)
17582 {
17583 #if HAVE_GETTIMEOFDAY
17584
17585     struct timeval timeVal;
17586     struct timezone timeZone;
17587
17588     gettimeofday(&timeVal, &timeZone);
17589     tm->sec = (long) timeVal.tv_sec;
17590     tm->ms = (int) (timeVal.tv_usec / 1000L);
17591
17592 #else /*!HAVE_GETTIMEOFDAY*/
17593 #if HAVE_FTIME
17594
17595 // include <sys/timeb.h> / moved to just above start of function
17596     struct timeb timeB;
17597
17598     ftime(&timeB);
17599     tm->sec = (long) timeB.time;
17600     tm->ms = (int) timeB.millitm;
17601
17602 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17603     tm->sec = (long) time(NULL);
17604     tm->ms = 0;
17605 #endif
17606 #endif
17607 }
17608
17609 /* Return the difference in milliseconds between two
17610    time marks.  We assume the difference will fit in a long!
17611 */
17612 long
17613 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17614 {
17615     return 1000L*(tm2->sec - tm1->sec) +
17616            (long) (tm2->ms - tm1->ms);
17617 }
17618
17619
17620 /*
17621  * Code to manage the game clocks.
17622  *
17623  * In tournament play, black starts the clock and then white makes a move.
17624  * We give the human user a slight advantage if he is playing white---the
17625  * clocks don't run until he makes his first move, so it takes zero time.
17626  * Also, we don't account for network lag, so we could get out of sync
17627  * with GNU Chess's clock -- but then, referees are always right.
17628  */
17629
17630 static TimeMark tickStartTM;
17631 static long intendedTickLength;
17632
17633 long
17634 NextTickLength (long timeRemaining)
17635 {
17636     long nominalTickLength, nextTickLength;
17637
17638     if (timeRemaining > 0L && timeRemaining <= 10000L)
17639       nominalTickLength = 100L;
17640     else
17641       nominalTickLength = 1000L;
17642     nextTickLength = timeRemaining % nominalTickLength;
17643     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17644
17645     return nextTickLength;
17646 }
17647
17648 /* Adjust clock one minute up or down */
17649 void
17650 AdjustClock (Boolean which, int dir)
17651 {
17652     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17653     if(which) blackTimeRemaining += 60000*dir;
17654     else      whiteTimeRemaining += 60000*dir;
17655     DisplayBothClocks();
17656     adjustedClock = TRUE;
17657 }
17658
17659 /* Stop clocks and reset to a fresh time control */
17660 void
17661 ResetClocks ()
17662 {
17663     (void) StopClockTimer();
17664     if (appData.icsActive) {
17665         whiteTimeRemaining = blackTimeRemaining = 0;
17666     } else if (searchTime) {
17667         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17668         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17669     } else { /* [HGM] correct new time quote for time odds */
17670         whiteTC = blackTC = fullTimeControlString;
17671         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17672         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17673     }
17674     if (whiteFlag || blackFlag) {
17675         DisplayTitle("");
17676         whiteFlag = blackFlag = FALSE;
17677     }
17678     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17679     DisplayBothClocks();
17680     adjustedClock = FALSE;
17681 }
17682
17683 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17684
17685 /* Decrement running clock by amount of time that has passed */
17686 void
17687 DecrementClocks ()
17688 {
17689     long timeRemaining;
17690     long lastTickLength, fudge;
17691     TimeMark now;
17692
17693     if (!appData.clockMode) return;
17694     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17695
17696     GetTimeMark(&now);
17697
17698     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17699
17700     /* Fudge if we woke up a little too soon */
17701     fudge = intendedTickLength - lastTickLength;
17702     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17703
17704     if (WhiteOnMove(forwardMostMove)) {
17705         if(whiteNPS >= 0) lastTickLength = 0;
17706         timeRemaining = whiteTimeRemaining -= lastTickLength;
17707         if(timeRemaining < 0 && !appData.icsActive) {
17708             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17709             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17710                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17711                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17712             }
17713         }
17714         DisplayWhiteClock(whiteTimeRemaining - fudge,
17715                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17716     } else {
17717         if(blackNPS >= 0) lastTickLength = 0;
17718         timeRemaining = blackTimeRemaining -= lastTickLength;
17719         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17720             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17721             if(suddenDeath) {
17722                 blackStartMove = forwardMostMove;
17723                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17724             }
17725         }
17726         DisplayBlackClock(blackTimeRemaining - fudge,
17727                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17728     }
17729     if (CheckFlags()) return;
17730
17731     if(twoBoards) { // count down secondary board's clocks as well
17732         activePartnerTime -= lastTickLength;
17733         partnerUp = 1;
17734         if(activePartner == 'W')
17735             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17736         else
17737             DisplayBlackClock(activePartnerTime, TRUE);
17738         partnerUp = 0;
17739     }
17740
17741     tickStartTM = now;
17742     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17743     StartClockTimer(intendedTickLength);
17744
17745     /* if the time remaining has fallen below the alarm threshold, sound the
17746      * alarm. if the alarm has sounded and (due to a takeback or time control
17747      * with increment) the time remaining has increased to a level above the
17748      * threshold, reset the alarm so it can sound again.
17749      */
17750
17751     if (appData.icsActive && appData.icsAlarm) {
17752
17753         /* make sure we are dealing with the user's clock */
17754         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17755                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17756            )) return;
17757
17758         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17759             alarmSounded = FALSE;
17760         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17761             PlayAlarmSound();
17762             alarmSounded = TRUE;
17763         }
17764     }
17765 }
17766
17767
17768 /* A player has just moved, so stop the previously running
17769    clock and (if in clock mode) start the other one.
17770    We redisplay both clocks in case we're in ICS mode, because
17771    ICS gives us an update to both clocks after every move.
17772    Note that this routine is called *after* forwardMostMove
17773    is updated, so the last fractional tick must be subtracted
17774    from the color that is *not* on move now.
17775 */
17776 void
17777 SwitchClocks (int newMoveNr)
17778 {
17779     long lastTickLength;
17780     TimeMark now;
17781     int flagged = FALSE;
17782
17783     GetTimeMark(&now);
17784
17785     if (StopClockTimer() && appData.clockMode) {
17786         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17787         if (!WhiteOnMove(forwardMostMove)) {
17788             if(blackNPS >= 0) lastTickLength = 0;
17789             blackTimeRemaining -= lastTickLength;
17790            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17791 //         if(pvInfoList[forwardMostMove].time == -1)
17792                  pvInfoList[forwardMostMove].time =               // use GUI time
17793                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17794         } else {
17795            if(whiteNPS >= 0) lastTickLength = 0;
17796            whiteTimeRemaining -= lastTickLength;
17797            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17798 //         if(pvInfoList[forwardMostMove].time == -1)
17799                  pvInfoList[forwardMostMove].time =
17800                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17801         }
17802         flagged = CheckFlags();
17803     }
17804     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17805     CheckTimeControl();
17806
17807     if (flagged || !appData.clockMode) return;
17808
17809     switch (gameMode) {
17810       case MachinePlaysBlack:
17811       case MachinePlaysWhite:
17812       case BeginningOfGame:
17813         if (pausing) return;
17814         break;
17815
17816       case EditGame:
17817       case PlayFromGameFile:
17818       case IcsExamining:
17819         return;
17820
17821       default:
17822         break;
17823     }
17824
17825     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17826         if(WhiteOnMove(forwardMostMove))
17827              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17828         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17829     }
17830
17831     tickStartTM = now;
17832     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17833       whiteTimeRemaining : blackTimeRemaining);
17834     StartClockTimer(intendedTickLength);
17835 }
17836
17837
17838 /* Stop both clocks */
17839 void
17840 StopClocks ()
17841 {
17842     long lastTickLength;
17843     TimeMark now;
17844
17845     if (!StopClockTimer()) return;
17846     if (!appData.clockMode) return;
17847
17848     GetTimeMark(&now);
17849
17850     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17851     if (WhiteOnMove(forwardMostMove)) {
17852         if(whiteNPS >= 0) lastTickLength = 0;
17853         whiteTimeRemaining -= lastTickLength;
17854         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17855     } else {
17856         if(blackNPS >= 0) lastTickLength = 0;
17857         blackTimeRemaining -= lastTickLength;
17858         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17859     }
17860     CheckFlags();
17861 }
17862
17863 /* Start clock of player on move.  Time may have been reset, so
17864    if clock is already running, stop and restart it. */
17865 void
17866 StartClocks ()
17867 {
17868     (void) StopClockTimer(); /* in case it was running already */
17869     DisplayBothClocks();
17870     if (CheckFlags()) return;
17871
17872     if (!appData.clockMode) return;
17873     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17874
17875     GetTimeMark(&tickStartTM);
17876     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17877       whiteTimeRemaining : blackTimeRemaining);
17878
17879    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17880     whiteNPS = blackNPS = -1;
17881     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17882        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17883         whiteNPS = first.nps;
17884     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17885        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17886         blackNPS = first.nps;
17887     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17888         whiteNPS = second.nps;
17889     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17890         blackNPS = second.nps;
17891     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17892
17893     StartClockTimer(intendedTickLength);
17894 }
17895
17896 char *
17897 TimeString (long ms)
17898 {
17899     long second, minute, hour, day;
17900     char *sign = "";
17901     static char buf[32];
17902
17903     if (ms > 0 && ms <= 9900) {
17904       /* convert milliseconds to tenths, rounding up */
17905       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17906
17907       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17908       return buf;
17909     }
17910
17911     /* convert milliseconds to seconds, rounding up */
17912     /* use floating point to avoid strangeness of integer division
17913        with negative dividends on many machines */
17914     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17915
17916     if (second < 0) {
17917         sign = "-";
17918         second = -second;
17919     }
17920
17921     day = second / (60 * 60 * 24);
17922     second = second % (60 * 60 * 24);
17923     hour = second / (60 * 60);
17924     second = second % (60 * 60);
17925     minute = second / 60;
17926     second = second % 60;
17927
17928     if (day > 0)
17929       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17930               sign, day, hour, minute, second);
17931     else if (hour > 0)
17932       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17933     else
17934       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17935
17936     return buf;
17937 }
17938
17939
17940 /*
17941  * This is necessary because some C libraries aren't ANSI C compliant yet.
17942  */
17943 char *
17944 StrStr (char *string, char *match)
17945 {
17946     int i, length;
17947
17948     length = strlen(match);
17949
17950     for (i = strlen(string) - length; i >= 0; i--, string++)
17951       if (!strncmp(match, string, length))
17952         return string;
17953
17954     return NULL;
17955 }
17956
17957 char *
17958 StrCaseStr (char *string, char *match)
17959 {
17960     int i, j, length;
17961
17962     length = strlen(match);
17963
17964     for (i = strlen(string) - length; i >= 0; i--, string++) {
17965         for (j = 0; j < length; j++) {
17966             if (ToLower(match[j]) != ToLower(string[j]))
17967               break;
17968         }
17969         if (j == length) return string;
17970     }
17971
17972     return NULL;
17973 }
17974
17975 #ifndef _amigados
17976 int
17977 StrCaseCmp (char *s1, char *s2)
17978 {
17979     char c1, c2;
17980
17981     for (;;) {
17982         c1 = ToLower(*s1++);
17983         c2 = ToLower(*s2++);
17984         if (c1 > c2) return 1;
17985         if (c1 < c2) return -1;
17986         if (c1 == NULLCHAR) return 0;
17987     }
17988 }
17989
17990
17991 int
17992 ToLower (int c)
17993 {
17994     return isupper(c) ? tolower(c) : c;
17995 }
17996
17997
17998 int
17999 ToUpper (int c)
18000 {
18001     return islower(c) ? toupper(c) : c;
18002 }
18003 #endif /* !_amigados    */
18004
18005 char *
18006 StrSave (char *s)
18007 {
18008   char *ret;
18009
18010   if ((ret = (char *) malloc(strlen(s) + 1)))
18011     {
18012       safeStrCpy(ret, s, strlen(s)+1);
18013     }
18014   return ret;
18015 }
18016
18017 char *
18018 StrSavePtr (char *s, char **savePtr)
18019 {
18020     if (*savePtr) {
18021         free(*savePtr);
18022     }
18023     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
18024       safeStrCpy(*savePtr, s, strlen(s)+1);
18025     }
18026     return(*savePtr);
18027 }
18028
18029 char *
18030 PGNDate ()
18031 {
18032     time_t clock;
18033     struct tm *tm;
18034     char buf[MSG_SIZ];
18035
18036     clock = time((time_t *)NULL);
18037     tm = localtime(&clock);
18038     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
18039             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
18040     return StrSave(buf);
18041 }
18042
18043
18044 char *
18045 PositionToFEN (int move, char *overrideCastling, int moveCounts)
18046 {
18047     int i, j, fromX, fromY, toX, toY;
18048     int whiteToPlay, haveRights = nrCastlingRights;
18049     char buf[MSG_SIZ];
18050     char *p, *q;
18051     int emptycount;
18052     ChessSquare piece;
18053
18054     whiteToPlay = (gameMode == EditPosition) ?
18055       !blackPlaysFirst : (move % 2 == 0);
18056     p = buf;
18057
18058     /* Piece placement data */
18059     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18060         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18061         emptycount = 0;
18062         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18063             if (boards[move][i][j] == EmptySquare) {
18064                 emptycount++;
18065             } else { ChessSquare piece = boards[move][i][j];
18066                 if (emptycount > 0) {
18067                     if(emptycount<10) /* [HGM] can be >= 10 */
18068                         *p++ = '0' + emptycount;
18069                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18070                     emptycount = 0;
18071                 }
18072                 if(PieceToChar(piece) == '+') {
18073                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18074                     *p++ = '+';
18075                     piece = (ChessSquare)(CHUDEMOTED(piece));
18076                 }
18077                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18078                 if(*p = PieceSuffix(piece)) p++;
18079                 if(p[-1] == '~') {
18080                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18081                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18082                     *p++ = '~';
18083                 }
18084             }
18085         }
18086         if (emptycount > 0) {
18087             if(emptycount<10) /* [HGM] can be >= 10 */
18088                 *p++ = '0' + emptycount;
18089             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18090             emptycount = 0;
18091         }
18092         *p++ = '/';
18093     }
18094     *(p - 1) = ' ';
18095
18096     /* [HGM] print Crazyhouse or Shogi holdings */
18097     if( gameInfo.holdingsWidth ) {
18098         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18099         q = p;
18100         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18101             piece = boards[move][i][BOARD_WIDTH-1];
18102             if( piece != EmptySquare )
18103               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18104                   *p++ = PieceToChar(piece);
18105         }
18106         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18107             piece = boards[move][BOARD_HEIGHT-i-1][0];
18108             if( piece != EmptySquare )
18109               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18110                   *p++ = PieceToChar(piece);
18111         }
18112
18113         if( q == p ) *p++ = '-';
18114         *p++ = ']';
18115         *p++ = ' ';
18116     }
18117
18118     /* Active color */
18119     *p++ = whiteToPlay ? 'w' : 'b';
18120     *p++ = ' ';
18121
18122   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18123     haveRights = 0; q = p;
18124     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18125       piece = boards[move][0][i];
18126       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18127         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18128       }
18129     }
18130     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18131       piece = boards[move][BOARD_HEIGHT-1][i];
18132       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18133         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18134       }
18135     }
18136     if(p == q) *p++ = '-';
18137     *p++ = ' ';
18138   }
18139
18140   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18141     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18142   } else {
18143   if(haveRights) {
18144      int handW=0, handB=0;
18145      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18146         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18147         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18148      }
18149      q = p;
18150      if(appData.fischerCastling) {
18151         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18152            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18153                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18154         } else {
18155        /* [HGM] write directly from rights */
18156            if(boards[move][CASTLING][2] != NoRights &&
18157               boards[move][CASTLING][0] != NoRights   )
18158                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18159            if(boards[move][CASTLING][2] != NoRights &&
18160               boards[move][CASTLING][1] != NoRights   )
18161                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18162         }
18163         if(handB) {
18164            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18165                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18166         } else {
18167            if(boards[move][CASTLING][5] != NoRights &&
18168               boards[move][CASTLING][3] != NoRights   )
18169                 *p++ = boards[move][CASTLING][3] + AAA;
18170            if(boards[move][CASTLING][5] != NoRights &&
18171               boards[move][CASTLING][4] != NoRights   )
18172                 *p++ = boards[move][CASTLING][4] + AAA;
18173         }
18174      } else {
18175
18176         /* [HGM] write true castling rights */
18177         if( nrCastlingRights == 6 ) {
18178             int q, k=0;
18179             if(boards[move][CASTLING][0] != NoRights &&
18180                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18181             q = (boards[move][CASTLING][1] != NoRights &&
18182                  boards[move][CASTLING][2] != NoRights  );
18183             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18184                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18185                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18186                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18187             }
18188             if(q) *p++ = 'Q';
18189             k = 0;
18190             if(boards[move][CASTLING][3] != NoRights &&
18191                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18192             q = (boards[move][CASTLING][4] != NoRights &&
18193                  boards[move][CASTLING][5] != NoRights  );
18194             if(handB) {
18195                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18196                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18197                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18198             }
18199             if(q) *p++ = 'q';
18200         }
18201      }
18202      if (q == p) *p++ = '-'; /* No castling rights */
18203      *p++ = ' ';
18204   }
18205
18206   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18207      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18208      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18209     /* En passant target square */
18210     if (move > backwardMostMove) {
18211         fromX = moveList[move - 1][0] - AAA;
18212         fromY = moveList[move - 1][1] - ONE;
18213         toX = moveList[move - 1][2] - AAA;
18214         toY = moveList[move - 1][3] - ONE;
18215         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18216             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18217             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18218             fromX == toX) {
18219             /* 2-square pawn move just happened */
18220             *p++ = toX + AAA;
18221             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18222         } else {
18223             *p++ = '-';
18224         }
18225     } else if(move == backwardMostMove) {
18226         // [HGM] perhaps we should always do it like this, and forget the above?
18227         if((signed char)boards[move][EP_STATUS] >= 0) {
18228             *p++ = boards[move][EP_STATUS] + AAA;
18229             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18230         } else {
18231             *p++ = '-';
18232         }
18233     } else {
18234         *p++ = '-';
18235     }
18236     *p++ = ' ';
18237   }
18238   }
18239
18240     if(moveCounts)
18241     {   int i = 0, j=move;
18242
18243         /* [HGM] find reversible plies */
18244         if (appData.debugMode) { int k;
18245             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18246             for(k=backwardMostMove; k<=forwardMostMove; k++)
18247                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18248
18249         }
18250
18251         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18252         if( j == backwardMostMove ) i += initialRulePlies;
18253         sprintf(p, "%d ", i);
18254         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18255
18256         /* Fullmove number */
18257         sprintf(p, "%d", (move / 2) + 1);
18258     } else *--p = NULLCHAR;
18259
18260     return StrSave(buf);
18261 }
18262
18263 Boolean
18264 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18265 {
18266     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18267     char *p, c;
18268     int emptycount, virgin[BOARD_FILES];
18269     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18270
18271     p = fen;
18272
18273     /* Piece placement data */
18274     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18275         j = 0;
18276         for (;;) {
18277             if (*p == '/' || *p == ' ' || *p == '[' ) {
18278                 if(j > w) w = j;
18279                 emptycount = gameInfo.boardWidth - j;
18280                 while (emptycount--)
18281                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18282                 if (*p == '/') p++;
18283                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18284                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18285                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18286                     }
18287                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18288                 }
18289                 break;
18290 #if(BOARD_FILES >= 10)*0
18291             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18292                 p++; emptycount=10;
18293                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18294                 while (emptycount--)
18295                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18296 #endif
18297             } else if (*p == '*') {
18298                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18299             } else if (isdigit(*p)) {
18300                 emptycount = *p++ - '0';
18301                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18302                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18303                 while (emptycount--)
18304                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18305             } else if (*p == '<') {
18306                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18307                 else if (i != 0 || !shuffle) return FALSE;
18308                 p++;
18309             } else if (shuffle && *p == '>') {
18310                 p++; // for now ignore closing shuffle range, and assume rank-end
18311             } else if (*p == '?') {
18312                 if (j >= gameInfo.boardWidth) return FALSE;
18313                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18314                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18315             } else if (*p == '+' || isalpha(*p)) {
18316                 char *q, *s = SUFFIXES;
18317                 if (j >= gameInfo.boardWidth) return FALSE;
18318                 if(*p=='+') {
18319                     char c = *++p;
18320                     if(q = strchr(s, p[1])) p++;
18321                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18322                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18323                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18324                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18325                 } else {
18326                     char c = *p++;
18327                     if(q = strchr(s, *p)) p++;
18328                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18329                 }
18330
18331                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18332                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18333                     piece = (ChessSquare) (PROMOTED(piece));
18334                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18335                     p++;
18336                 }
18337                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18338                 if(piece == king) wKingRank = i;
18339                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18340             } else {
18341                 return FALSE;
18342             }
18343         }
18344     }
18345     while (*p == '/' || *p == ' ') p++;
18346
18347     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18348
18349     /* [HGM] by default clear Crazyhouse holdings, if present */
18350     if(gameInfo.holdingsWidth) {
18351        for(i=0; i<BOARD_HEIGHT; i++) {
18352            board[i][0]             = EmptySquare; /* black holdings */
18353            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18354            board[i][1]             = (ChessSquare) 0; /* black counts */
18355            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18356        }
18357     }
18358
18359     /* [HGM] look for Crazyhouse holdings here */
18360     while(*p==' ') p++;
18361     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18362         int swap=0, wcnt=0, bcnt=0;
18363         if(*p == '[') p++;
18364         if(*p == '<') swap++, p++;
18365         if(*p == '-' ) p++; /* empty holdings */ else {
18366             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18367             /* if we would allow FEN reading to set board size, we would   */
18368             /* have to add holdings and shift the board read so far here   */
18369             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18370                 p++;
18371                 if((int) piece >= (int) BlackPawn ) {
18372                     i = (int)piece - (int)BlackPawn;
18373                     i = PieceToNumber((ChessSquare)i);
18374                     if( i >= gameInfo.holdingsSize ) return FALSE;
18375                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18376                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18377                     bcnt++;
18378                 } else {
18379                     i = (int)piece - (int)WhitePawn;
18380                     i = PieceToNumber((ChessSquare)i);
18381                     if( i >= gameInfo.holdingsSize ) return FALSE;
18382                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18383                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18384                     wcnt++;
18385                 }
18386             }
18387             if(subst) { // substitute back-rank question marks by holdings pieces
18388                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18389                     int k, m, n = bcnt + 1;
18390                     if(board[0][j] == ClearBoard) {
18391                         if(!wcnt) return FALSE;
18392                         n = rand() % wcnt;
18393                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18394                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18395                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18396                             break;
18397                         }
18398                     }
18399                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18400                         if(!bcnt) return FALSE;
18401                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18402                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18403                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18404                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18405                             break;
18406                         }
18407                     }
18408                 }
18409                 subst = 0;
18410             }
18411         }
18412         if(*p == ']') p++;
18413     }
18414
18415     if(subst) return FALSE; // substitution requested, but no holdings
18416
18417     while(*p == ' ') p++;
18418
18419     /* Active color */
18420     c = *p++;
18421     if(appData.colorNickNames) {
18422       if( c == appData.colorNickNames[0] ) c = 'w'; else
18423       if( c == appData.colorNickNames[1] ) c = 'b';
18424     }
18425     switch (c) {
18426       case 'w':
18427         *blackPlaysFirst = FALSE;
18428         break;
18429       case 'b':
18430         *blackPlaysFirst = TRUE;
18431         break;
18432       default:
18433         return FALSE;
18434     }
18435
18436     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18437     /* return the extra info in global variiables             */
18438
18439     while(*p==' ') p++;
18440
18441     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18442         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18443         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18444     }
18445
18446     /* set defaults in case FEN is incomplete */
18447     board[EP_STATUS] = EP_UNKNOWN;
18448     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18449     for(i=0; i<nrCastlingRights; i++ ) {
18450         board[CASTLING][i] =
18451             appData.fischerCastling ? NoRights : initialRights[i];
18452     }   /* assume possible unless obviously impossible */
18453     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18454     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18455     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18456                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18457     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18458     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18459     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18460                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18461     FENrulePlies = 0;
18462
18463     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18464       char *q = p;
18465       int w=0, b=0;
18466       while(isalpha(*p)) {
18467         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18468         if(islower(*p)) b |= 1 << (*p++ - 'a');
18469       }
18470       if(*p == '-') p++;
18471       if(p != q) {
18472         board[TOUCHED_W] = ~w;
18473         board[TOUCHED_B] = ~b;
18474         while(*p == ' ') p++;
18475       }
18476     } else
18477
18478     if(nrCastlingRights) {
18479       int fischer = 0;
18480       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18481       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18482           /* castling indicator present, so default becomes no castlings */
18483           for(i=0; i<nrCastlingRights; i++ ) {
18484                  board[CASTLING][i] = NoRights;
18485           }
18486       }
18487       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18488              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18489              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18490              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18491         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18492
18493         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18494             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18495             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18496         }
18497         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18498             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18499         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18500                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18501         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18502                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18503         switch(c) {
18504           case'K':
18505               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18506               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18507               board[CASTLING][2] = whiteKingFile;
18508               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18509               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18510               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18511               break;
18512           case'Q':
18513               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18514               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18515               board[CASTLING][2] = whiteKingFile;
18516               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18517               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18518               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18519               break;
18520           case'k':
18521               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18522               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18523               board[CASTLING][5] = blackKingFile;
18524               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18525               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18526               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18527               break;
18528           case'q':
18529               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18530               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18531               board[CASTLING][5] = blackKingFile;
18532               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18533               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18534               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18535           case '-':
18536               break;
18537           default: /* FRC castlings */
18538               if(c >= 'a') { /* black rights */
18539                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18540                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18541                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18542                   if(i == BOARD_RGHT) break;
18543                   board[CASTLING][5] = i;
18544                   c -= AAA;
18545                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18546                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18547                   if(c > i)
18548                       board[CASTLING][3] = c;
18549                   else
18550                       board[CASTLING][4] = c;
18551               } else { /* white rights */
18552                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18553                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18554                     if(board[0][i] == WhiteKing) break;
18555                   if(i == BOARD_RGHT) break;
18556                   board[CASTLING][2] = i;
18557                   c -= AAA - 'a' + 'A';
18558                   if(board[0][c] >= WhiteKing) break;
18559                   if(c > i)
18560                       board[CASTLING][0] = c;
18561                   else
18562                       board[CASTLING][1] = c;
18563               }
18564         }
18565       }
18566       for(i=0; i<nrCastlingRights; i++)
18567         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18568       if(gameInfo.variant == VariantSChess)
18569         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18570       if(fischer && shuffle) appData.fischerCastling = TRUE;
18571     if (appData.debugMode) {
18572         fprintf(debugFP, "FEN castling rights:");
18573         for(i=0; i<nrCastlingRights; i++)
18574         fprintf(debugFP, " %d", board[CASTLING][i]);
18575         fprintf(debugFP, "\n");
18576     }
18577
18578       while(*p==' ') p++;
18579     }
18580
18581     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18582
18583     /* read e.p. field in games that know e.p. capture */
18584     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18585        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18586        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18587       if(*p=='-') {
18588         p++; board[EP_STATUS] = EP_NONE;
18589       } else {
18590          char c = *p++ - AAA;
18591
18592          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18593          if(*p >= '0' && *p <='9') p++;
18594          board[EP_STATUS] = c;
18595       }
18596     }
18597
18598
18599     if(sscanf(p, "%d", &i) == 1) {
18600         FENrulePlies = i; /* 50-move ply counter */
18601         /* (The move number is still ignored)    */
18602     }
18603
18604     return TRUE;
18605 }
18606
18607 void
18608 EditPositionPasteFEN (char *fen)
18609 {
18610   if (fen != NULL) {
18611     Board initial_position;
18612
18613     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18614       DisplayError(_("Bad FEN position in clipboard"), 0);
18615       return ;
18616     } else {
18617       int savedBlackPlaysFirst = blackPlaysFirst;
18618       EditPositionEvent();
18619       blackPlaysFirst = savedBlackPlaysFirst;
18620       CopyBoard(boards[0], initial_position);
18621       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18622       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18623       DisplayBothClocks();
18624       DrawPosition(FALSE, boards[currentMove]);
18625     }
18626   }
18627 }
18628
18629 static char cseq[12] = "\\   ";
18630
18631 Boolean
18632 set_cont_sequence (char *new_seq)
18633 {
18634     int len;
18635     Boolean ret;
18636
18637     // handle bad attempts to set the sequence
18638         if (!new_seq)
18639                 return 0; // acceptable error - no debug
18640
18641     len = strlen(new_seq);
18642     ret = (len > 0) && (len < sizeof(cseq));
18643     if (ret)
18644       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18645     else if (appData.debugMode)
18646       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18647     return ret;
18648 }
18649
18650 /*
18651     reformat a source message so words don't cross the width boundary.  internal
18652     newlines are not removed.  returns the wrapped size (no null character unless
18653     included in source message).  If dest is NULL, only calculate the size required
18654     for the dest buffer.  lp argument indicats line position upon entry, and it's
18655     passed back upon exit.
18656 */
18657 int
18658 wrap (char *dest, char *src, int count, int width, int *lp)
18659 {
18660     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18661
18662     cseq_len = strlen(cseq);
18663     old_line = line = *lp;
18664     ansi = len = clen = 0;
18665
18666     for (i=0; i < count; i++)
18667     {
18668         if (src[i] == '\033')
18669             ansi = 1;
18670
18671         // if we hit the width, back up
18672         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18673         {
18674             // store i & len in case the word is too long
18675             old_i = i, old_len = len;
18676
18677             // find the end of the last word
18678             while (i && src[i] != ' ' && src[i] != '\n')
18679             {
18680                 i--;
18681                 len--;
18682             }
18683
18684             // word too long?  restore i & len before splitting it
18685             if ((old_i-i+clen) >= width)
18686             {
18687                 i = old_i;
18688                 len = old_len;
18689             }
18690
18691             // extra space?
18692             if (i && src[i-1] == ' ')
18693                 len--;
18694
18695             if (src[i] != ' ' && src[i] != '\n')
18696             {
18697                 i--;
18698                 if (len)
18699                     len--;
18700             }
18701
18702             // now append the newline and continuation sequence
18703             if (dest)
18704                 dest[len] = '\n';
18705             len++;
18706             if (dest)
18707                 strncpy(dest+len, cseq, cseq_len);
18708             len += cseq_len;
18709             line = cseq_len;
18710             clen = cseq_len;
18711             continue;
18712         }
18713
18714         if (dest)
18715             dest[len] = src[i];
18716         len++;
18717         if (!ansi)
18718             line++;
18719         if (src[i] == '\n')
18720             line = 0;
18721         if (src[i] == 'm')
18722             ansi = 0;
18723     }
18724     if (dest && appData.debugMode)
18725     {
18726         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18727             count, width, line, len, *lp);
18728         show_bytes(debugFP, src, count);
18729         fprintf(debugFP, "\ndest: ");
18730         show_bytes(debugFP, dest, len);
18731         fprintf(debugFP, "\n");
18732     }
18733     *lp = dest ? line : old_line;
18734
18735     return len;
18736 }
18737
18738 // [HGM] vari: routines for shelving variations
18739 Boolean modeRestore = FALSE;
18740
18741 void
18742 PushInner (int firstMove, int lastMove)
18743 {
18744         int i, j, nrMoves = lastMove - firstMove;
18745
18746         // push current tail of game on stack
18747         savedResult[storedGames] = gameInfo.result;
18748         savedDetails[storedGames] = gameInfo.resultDetails;
18749         gameInfo.resultDetails = NULL;
18750         savedFirst[storedGames] = firstMove;
18751         savedLast [storedGames] = lastMove;
18752         savedFramePtr[storedGames] = framePtr;
18753         framePtr -= nrMoves; // reserve space for the boards
18754         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18755             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18756             for(j=0; j<MOVE_LEN; j++)
18757                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18758             for(j=0; j<2*MOVE_LEN; j++)
18759                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18760             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18761             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18762             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18763             pvInfoList[firstMove+i-1].depth = 0;
18764             commentList[framePtr+i] = commentList[firstMove+i];
18765             commentList[firstMove+i] = NULL;
18766         }
18767
18768         storedGames++;
18769         forwardMostMove = firstMove; // truncate game so we can start variation
18770 }
18771
18772 void
18773 PushTail (int firstMove, int lastMove)
18774 {
18775         if(appData.icsActive) { // only in local mode
18776                 forwardMostMove = currentMove; // mimic old ICS behavior
18777                 return;
18778         }
18779         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18780
18781         PushInner(firstMove, lastMove);
18782         if(storedGames == 1) GreyRevert(FALSE);
18783         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18784 }
18785
18786 void
18787 PopInner (Boolean annotate)
18788 {
18789         int i, j, nrMoves;
18790         char buf[8000], moveBuf[20];
18791
18792         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18793         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18794         nrMoves = savedLast[storedGames] - currentMove;
18795         if(annotate) {
18796                 int cnt = 10;
18797                 if(!WhiteOnMove(currentMove))
18798                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18799                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18800                 for(i=currentMove; i<forwardMostMove; i++) {
18801                         if(WhiteOnMove(i))
18802                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18803                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18804                         strcat(buf, moveBuf);
18805                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18806                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18807                 }
18808                 strcat(buf, ")");
18809         }
18810         for(i=1; i<=nrMoves; i++) { // copy last variation back
18811             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18812             for(j=0; j<MOVE_LEN; j++)
18813                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18814             for(j=0; j<2*MOVE_LEN; j++)
18815                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18816             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18817             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18818             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18819             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18820             commentList[currentMove+i] = commentList[framePtr+i];
18821             commentList[framePtr+i] = NULL;
18822         }
18823         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18824         framePtr = savedFramePtr[storedGames];
18825         gameInfo.result = savedResult[storedGames];
18826         if(gameInfo.resultDetails != NULL) {
18827             free(gameInfo.resultDetails);
18828       }
18829         gameInfo.resultDetails = savedDetails[storedGames];
18830         forwardMostMove = currentMove + nrMoves;
18831 }
18832
18833 Boolean
18834 PopTail (Boolean annotate)
18835 {
18836         if(appData.icsActive) return FALSE; // only in local mode
18837         if(!storedGames) return FALSE; // sanity
18838         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18839
18840         PopInner(annotate);
18841         if(currentMove < forwardMostMove) ForwardEvent(); else
18842         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18843
18844         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18845         return TRUE;
18846 }
18847
18848 void
18849 CleanupTail ()
18850 {       // remove all shelved variations
18851         int i;
18852         for(i=0; i<storedGames; i++) {
18853             if(savedDetails[i])
18854                 free(savedDetails[i]);
18855             savedDetails[i] = NULL;
18856         }
18857         for(i=framePtr; i<MAX_MOVES; i++) {
18858                 if(commentList[i]) free(commentList[i]);
18859                 commentList[i] = NULL;
18860         }
18861         framePtr = MAX_MOVES-1;
18862         storedGames = 0;
18863 }
18864
18865 void
18866 LoadVariation (int index, char *text)
18867 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18868         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18869         int level = 0, move;
18870
18871         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18872         // first find outermost bracketing variation
18873         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18874             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18875                 if(*p == '{') wait = '}'; else
18876                 if(*p == '[') wait = ']'; else
18877                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18878                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18879             }
18880             if(*p == wait) wait = NULLCHAR; // closing ]} found
18881             p++;
18882         }
18883         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18884         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18885         end[1] = NULLCHAR; // clip off comment beyond variation
18886         ToNrEvent(currentMove-1);
18887         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18888         // kludge: use ParsePV() to append variation to game
18889         move = currentMove;
18890         ParsePV(start, TRUE, TRUE);
18891         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18892         ClearPremoveHighlights();
18893         CommentPopDown();
18894         ToNrEvent(currentMove+1);
18895 }
18896
18897 void
18898 LoadTheme ()
18899 {
18900     char *p, *q, buf[MSG_SIZ];
18901     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18902         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18903         ParseArgsFromString(buf);
18904         ActivateTheme(TRUE); // also redo colors
18905         return;
18906     }
18907     p = nickName;
18908     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18909     {
18910         int len;
18911         q = appData.themeNames;
18912         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18913       if(appData.useBitmaps) {
18914         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18915                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18916                 appData.liteBackTextureMode,
18917                 appData.darkBackTextureMode );
18918       } else {
18919         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18920                 Col2Text(2),   // lightSquareColor
18921                 Col2Text(3) ); // darkSquareColor
18922       }
18923       if(appData.useBorder) {
18924         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18925                 appData.border);
18926       } else {
18927         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18928       }
18929       if(appData.useFont) {
18930         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18931                 appData.renderPiecesWithFont,
18932                 appData.fontToPieceTable,
18933                 Col2Text(9),    // appData.fontBackColorWhite
18934                 Col2Text(10) ); // appData.fontForeColorBlack
18935       } else {
18936         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18937                 appData.pieceDirectory);
18938         if(!appData.pieceDirectory[0])
18939           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18940                 Col2Text(0),   // whitePieceColor
18941                 Col2Text(1) ); // blackPieceColor
18942       }
18943       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18944                 Col2Text(4),   // highlightSquareColor
18945                 Col2Text(5) ); // premoveHighlightColor
18946         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18947         if(insert != q) insert[-1] = NULLCHAR;
18948         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18949         if(q)   free(q);
18950     }
18951     ActivateTheme(FALSE);
18952 }