6d2e706787f01e087076a0dcac977dc0ace9a64e
[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 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 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 Boolean abortMatch;
246
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 int endPV = -1;
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
254 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
258 Boolean partnerUp;
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
270 int chattingPartner;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
278
279 /* States for ics_getting_history */
280 #define H_FALSE 0
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
286
287 /* whosays values for GameEnds */
288 #define GE_ICS 0
289 #define GE_ENGINE 1
290 #define GE_PLAYER 2
291 #define GE_FILE 3
292 #define GE_XBOARD 4
293 #define GE_ENGINE1 5
294 #define GE_ENGINE2 6
295
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
298
299 /* Different types of move when calling RegisterMove */
300 #define CMAIL_MOVE   0
301 #define CMAIL_RESIGN 1
302 #define CMAIL_DRAW   2
303 #define CMAIL_ACCEPT 3
304
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
309
310 /* Telnet protocol constants */
311 #define TN_WILL 0373
312 #define TN_WONT 0374
313 #define TN_DO   0375
314 #define TN_DONT 0376
315 #define TN_IAC  0377
316 #define TN_ECHO 0001
317 #define TN_SGA  0003
318 #define TN_PORT 23
319
320 char*
321 safeStrCpy (char *dst, const char *src, size_t count)
322 { // [HGM] made safe
323   int i;
324   assert( dst != NULL );
325   assert( src != NULL );
326   assert( count > 0 );
327
328   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329   if(  i == count && dst[count-1] != NULLCHAR)
330     {
331       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332       if(appData.debugMode)
333         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334     }
335
336   return dst;
337 }
338
339 /* Some compiler can't cast u64 to double
340  * This function do the job for us:
341
342  * We use the highest bit for cast, this only
343  * works if the highest bit is not
344  * in use (This should not happen)
345  *
346  * We used this for all compiler
347  */
348 double
349 u64ToDouble (u64 value)
350 {
351   double r;
352   u64 tmp = value & u64Const(0x7fffffffffffffff);
353   r = (double)(s64)tmp;
354   if (value & u64Const(0x8000000000000000))
355        r +=  9.2233720368547758080e18; /* 2^63 */
356  return r;
357 }
358
359 /* Fake up flags for now, as we aren't keeping track of castling
360    availability yet. [HGM] Change of logic: the flag now only
361    indicates the type of castlings allowed by the rule of the game.
362    The actual rights themselves are maintained in the array
363    castlingRights, as part of the game history, and are not probed
364    by this function.
365  */
366 int
367 PosFlags (index)
368 {
369   int flags = F_ALL_CASTLE_OK;
370   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371   switch (gameInfo.variant) {
372   case VariantSuicide:
373     flags &= ~F_ALL_CASTLE_OK;
374   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375     flags |= F_IGNORE_CHECK;
376   case VariantLosers:
377     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
378     break;
379   case VariantAtomic:
380     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
381     break;
382   case VariantKriegspiel:
383     flags |= F_KRIEGSPIEL_CAPTURE;
384     break;
385   case VariantCapaRandom:
386   case VariantFischeRandom:
387     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388   case VariantNoCastle:
389   case VariantShatranj:
390   case VariantCourier:
391   case VariantMakruk:
392   case VariantASEAN:
393   case VariantGrand:
394     flags &= ~F_ALL_CASTLE_OK;
395     break;
396   default:
397     break;
398   }
399   return flags;
400 }
401
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
404
405 /*
406     [AS] Note: sometimes, the sscanf() function is used to parse the input
407     into a fixed-size buffer. Because of this, we must be prepared to
408     receive strings as long as the size of the input buffer, which is currently
409     set to 4K for Windows and 8K for the rest.
410     So, we must either allocate sufficiently large buffers here, or
411     reduce the size of the input buffer in the input reading part.
412 */
413
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
417
418 ChessProgramState first, second, pairing;
419
420 /* premove variables */
421 int premoveToX = 0;
422 int premoveToY = 0;
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
426 int gotPremove = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
429
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
432
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
460
461 int have_sent_ICS_logon = 0;
462 int movesPerSession;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
474
475 /* animateTraining preserves the state of appData.animate
476  * when Training mode is activated. This allows the
477  * response to be animated when appData.animate == TRUE and
478  * appData.animateDragging == TRUE.
479  */
480 Boolean animateTraining;
481
482 GameInfo gameInfo;
483
484 AppData appData;
485
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char  initialRights[BOARD_FILES];
490 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int   initialRulePlies, FENrulePlies;
492 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
493 int loadFlag = 0;
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
496
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
500 int storedGames = 0;
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
506
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
512
513 ChessSquare  FIDEArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackBishop, BlackKnight, BlackRook }
518 };
519
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524         BlackKing, BlackKing, BlackKnight, BlackRook }
525 };
526
527 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530     { BlackRook, BlackMan, BlackBishop, BlackQueen,
531         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 };
533
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 };
540
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 };
547
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 };
554
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackMan, BlackFerz,
559         BlackKing, BlackMan, BlackKnight, BlackRook }
560 };
561
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackMan, BlackFerz,
566         BlackKing, BlackMan, BlackKnight, BlackRook }
567 };
568
569 ChessSquare  lionArray[2][BOARD_FILES] = {
570     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
571         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
572     { BlackRook, BlackLion, BlackBishop, BlackQueen,
573         BlackKing, BlackBishop, BlackKnight, BlackRook }
574 };
575
576
577 #if (BOARD_FILES>=10)
578 ChessSquare ShogiArray[2][BOARD_FILES] = {
579     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
580         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
581     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
582         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
583 };
584
585 ChessSquare XiangqiArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
587         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
589         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
590 };
591
592 ChessSquare CapablancaArray[2][BOARD_FILES] = {
593     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
594         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
596         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
597 };
598
599 ChessSquare GreatArray[2][BOARD_FILES] = {
600     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
601         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
602     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
603         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
604 };
605
606 ChessSquare JanusArray[2][BOARD_FILES] = {
607     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
608         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
609     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
610         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
611 };
612
613 ChessSquare GrandArray[2][BOARD_FILES] = {
614     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
615         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
616     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
617         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
618 };
619
620 #ifdef GOTHIC
621 ChessSquare GothicArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
623         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
625         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
626 };
627 #else // !GOTHIC
628 #define GothicArray CapablancaArray
629 #endif // !GOTHIC
630
631 #ifdef FALCON
632 ChessSquare FalconArray[2][BOARD_FILES] = {
633     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
634         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
635     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
636         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
637 };
638 #else // !FALCON
639 #define FalconArray CapablancaArray
640 #endif // !FALCON
641
642 #else // !(BOARD_FILES>=10)
643 #define XiangqiPosition FIDEArray
644 #define CapablancaArray FIDEArray
645 #define GothicArray FIDEArray
646 #define GreatArray FIDEArray
647 #endif // !(BOARD_FILES>=10)
648
649 #if (BOARD_FILES>=12)
650 ChessSquare CourierArray[2][BOARD_FILES] = {
651     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
652         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
653     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
654         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
655 };
656 ChessSquare ChuArray[6][BOARD_FILES] = {
657     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
658       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
659     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
660       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
661     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
662       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
663     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
664       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
665     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
666       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
667     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
668       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
669 };
670 #else // !(BOARD_FILES>=12)
671 #define CourierArray CapablancaArray
672 #define ChuArray CapablancaArray
673 #endif // !(BOARD_FILES>=12)
674
675
676 Board initialPosition;
677
678
679 /* Convert str to a rating. Checks for special cases of "----",
680
681    "++++", etc. Also strips ()'s */
682 int
683 string_to_rating (char *str)
684 {
685   while(*str && !isdigit(*str)) ++str;
686   if (!*str)
687     return 0;   /* One of the special "no rating" cases */
688   else
689     return atoi(str);
690 }
691
692 void
693 ClearProgramStats ()
694 {
695     /* Init programStats */
696     programStats.movelist[0] = 0;
697     programStats.depth = 0;
698     programStats.nr_moves = 0;
699     programStats.moves_left = 0;
700     programStats.nodes = 0;
701     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
702     programStats.score = 0;
703     programStats.got_only_move = 0;
704     programStats.got_fail = 0;
705     programStats.line_is_book = 0;
706 }
707
708 void
709 CommonEngineInit ()
710 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
711     if (appData.firstPlaysBlack) {
712         first.twoMachinesColor = "black\n";
713         second.twoMachinesColor = "white\n";
714     } else {
715         first.twoMachinesColor = "white\n";
716         second.twoMachinesColor = "black\n";
717     }
718
719     first.other = &second;
720     second.other = &first;
721
722     { float norm = 1;
723         if(appData.timeOddsMode) {
724             norm = appData.timeOdds[0];
725             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
726         }
727         first.timeOdds  = appData.timeOdds[0]/norm;
728         second.timeOdds = appData.timeOdds[1]/norm;
729     }
730
731     if(programVersion) free(programVersion);
732     if (appData.noChessProgram) {
733         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
734         sprintf(programVersion, "%s", PACKAGE_STRING);
735     } else {
736       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
737       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
738       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
739     }
740 }
741
742 void
743 UnloadEngine (ChessProgramState *cps)
744 {
745         /* Kill off first chess program */
746         if (cps->isr != NULL)
747           RemoveInputSource(cps->isr);
748         cps->isr = NULL;
749
750         if (cps->pr != NoProc) {
751             ExitAnalyzeMode();
752             DoSleep( appData.delayBeforeQuit );
753             SendToProgram("quit\n", cps);
754             DoSleep( appData.delayAfterQuit );
755             DestroyChildProcess(cps->pr, cps->useSigterm);
756         }
757         cps->pr = NoProc;
758         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
759 }
760
761 void
762 ClearOptions (ChessProgramState *cps)
763 {
764     int i;
765     cps->nrOptions = cps->comboCnt = 0;
766     for(i=0; i<MAX_OPTIONS; i++) {
767         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
768         cps->option[i].textValue = 0;
769     }
770 }
771
772 char *engineNames[] = {
773   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
774      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
775 N_("first"),
776   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
777      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
778 N_("second")
779 };
780
781 void
782 InitEngine (ChessProgramState *cps, int n)
783 {   // [HGM] all engine initialiation put in a function that does one engine
784
785     ClearOptions(cps);
786
787     cps->which = engineNames[n];
788     cps->maybeThinking = FALSE;
789     cps->pr = NoProc;
790     cps->isr = NULL;
791     cps->sendTime = 2;
792     cps->sendDrawOffers = 1;
793
794     cps->program = appData.chessProgram[n];
795     cps->host = appData.host[n];
796     cps->dir = appData.directory[n];
797     cps->initString = appData.engInitString[n];
798     cps->computerString = appData.computerString[n];
799     cps->useSigint  = TRUE;
800     cps->useSigterm = TRUE;
801     cps->reuse = appData.reuse[n];
802     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
803     cps->useSetboard = FALSE;
804     cps->useSAN = FALSE;
805     cps->usePing = FALSE;
806     cps->lastPing = 0;
807     cps->lastPong = 0;
808     cps->usePlayother = FALSE;
809     cps->useColors = TRUE;
810     cps->useUsermove = FALSE;
811     cps->sendICS = FALSE;
812     cps->sendName = appData.icsActive;
813     cps->sdKludge = FALSE;
814     cps->stKludge = FALSE;
815     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
816     TidyProgramName(cps->program, cps->host, cps->tidy);
817     cps->matchWins = 0;
818     ASSIGN(cps->variants, appData.variant);
819     cps->analysisSupport = 2; /* detect */
820     cps->analyzing = FALSE;
821     cps->initDone = FALSE;
822     cps->reload = FALSE;
823
824     /* New features added by Tord: */
825     cps->useFEN960 = FALSE;
826     cps->useOOCastle = TRUE;
827     /* End of new features added by Tord. */
828     cps->fenOverride  = appData.fenOverride[n];
829
830     /* [HGM] time odds: set factor for each machine */
831     cps->timeOdds  = appData.timeOdds[n];
832
833     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
834     cps->accumulateTC = appData.accumulateTC[n];
835     cps->maxNrOfSessions = 1;
836
837     /* [HGM] debug */
838     cps->debug = FALSE;
839
840     cps->supportsNPS = UNKNOWN;
841     cps->memSize = FALSE;
842     cps->maxCores = FALSE;
843     ASSIGN(cps->egtFormats, "");
844
845     /* [HGM] options */
846     cps->optionSettings  = appData.engOptions[n];
847
848     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
849     cps->isUCI = appData.isUCI[n]; /* [AS] */
850     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
851     cps->highlight = 0;
852
853     if (appData.protocolVersion[n] > PROTOVER
854         || appData.protocolVersion[n] < 1)
855       {
856         char buf[MSG_SIZ];
857         int len;
858
859         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
860                        appData.protocolVersion[n]);
861         if( (len >= MSG_SIZ) && appData.debugMode )
862           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
863
864         DisplayFatalError(buf, 0, 2);
865       }
866     else
867       {
868         cps->protocolVersion = appData.protocolVersion[n];
869       }
870
871     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
872     ParseFeatures(appData.featureDefaults, cps);
873 }
874
875 ChessProgramState *savCps;
876
877 GameMode oldMode;
878
879 void
880 LoadEngine ()
881 {
882     int i;
883     if(WaitForEngine(savCps, LoadEngine)) return;
884     CommonEngineInit(); // recalculate time odds
885     if(gameInfo.variant != StringToVariant(appData.variant)) {
886         // we changed variant when loading the engine; this forces us to reset
887         Reset(TRUE, savCps != &first);
888         oldMode = BeginningOfGame; // to prevent restoring old mode
889     }
890     InitChessProgram(savCps, FALSE);
891     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
892     DisplayMessage("", "");
893     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
894     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
895     ThawUI();
896     SetGNUMode();
897     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
898 }
899
900 void
901 ReplaceEngine (ChessProgramState *cps, int n)
902 {
903     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
904     keepInfo = 1;
905     if(oldMode != BeginningOfGame) EditGameEvent();
906     keepInfo = 0;
907     UnloadEngine(cps);
908     appData.noChessProgram = FALSE;
909     appData.clockMode = TRUE;
910     InitEngine(cps, n);
911     UpdateLogos(TRUE);
912     if(n) return; // only startup first engine immediately; second can wait
913     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
914     LoadEngine();
915 }
916
917 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
918 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
919
920 static char resetOptions[] =
921         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
922         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
923         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
924         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
925
926 void
927 FloatToFront(char **list, char *engineLine)
928 {
929     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
930     int i=0;
931     if(appData.recentEngines <= 0) return;
932     TidyProgramName(engineLine, "localhost", tidy+1);
933     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
934     strncpy(buf+1, *list, MSG_SIZ-50);
935     if(p = strstr(buf, tidy)) { // tidy name appears in list
936         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
937         while(*p++ = *++q); // squeeze out
938     }
939     strcat(tidy, buf+1); // put list behind tidy name
940     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
941     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
942     ASSIGN(*list, tidy+1);
943 }
944
945 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
946
947 void
948 Load (ChessProgramState *cps, int i)
949 {
950     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
951     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
952         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
953         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
954         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
955         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
956         appData.firstProtocolVersion = PROTOVER;
957         ParseArgsFromString(buf);
958         SwapEngines(i);
959         ReplaceEngine(cps, i);
960         FloatToFront(&appData.recentEngineList, engineLine);
961         return;
962     }
963     p = engineName;
964     while(q = strchr(p, SLASH)) p = q+1;
965     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
966     if(engineDir[0] != NULLCHAR) {
967         ASSIGN(appData.directory[i], engineDir); p = engineName;
968     } else if(p != engineName) { // derive directory from engine path, when not given
969         p[-1] = 0;
970         ASSIGN(appData.directory[i], engineName);
971         p[-1] = SLASH;
972         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
973     } else { ASSIGN(appData.directory[i], "."); }
974     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
975     if(params[0]) {
976         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
977         snprintf(command, MSG_SIZ, "%s %s", p, params);
978         p = command;
979     }
980     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
981     ASSIGN(appData.chessProgram[i], p);
982     appData.isUCI[i] = isUCI;
983     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
984     appData.hasOwnBookUCI[i] = hasBook;
985     if(!nickName[0]) useNick = FALSE;
986     if(useNick) ASSIGN(appData.pgnName[i], nickName);
987     if(addToList) {
988         int len;
989         char quote;
990         q = firstChessProgramNames;
991         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
992         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
993         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
994                         quote, p, quote, appData.directory[i],
995                         useNick ? " -fn \"" : "",
996                         useNick ? nickName : "",
997                         useNick ? "\"" : "",
998                         v1 ? " -firstProtocolVersion 1" : "",
999                         hasBook ? "" : " -fNoOwnBookUCI",
1000                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1001                         storeVariant ? " -variant " : "",
1002                         storeVariant ? VariantName(gameInfo.variant) : "");
1003         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1004         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1005         if(insert != q) insert[-1] = NULLCHAR;
1006         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1007         if(q)   free(q);
1008         FloatToFront(&appData.recentEngineList, buf);
1009     }
1010     ReplaceEngine(cps, i);
1011 }
1012
1013 void
1014 InitTimeControls ()
1015 {
1016     int matched, min, sec;
1017     /*
1018      * Parse timeControl resource
1019      */
1020     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1021                           appData.movesPerSession)) {
1022         char buf[MSG_SIZ];
1023         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1024         DisplayFatalError(buf, 0, 2);
1025     }
1026
1027     /*
1028      * Parse searchTime resource
1029      */
1030     if (*appData.searchTime != NULLCHAR) {
1031         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1032         if (matched == 1) {
1033             searchTime = min * 60;
1034         } else if (matched == 2) {
1035             searchTime = min * 60 + sec;
1036         } else {
1037             char buf[MSG_SIZ];
1038             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1039             DisplayFatalError(buf, 0, 2);
1040         }
1041     }
1042 }
1043
1044 void
1045 InitBackEnd1 ()
1046 {
1047
1048     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1049     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1050
1051     GetTimeMark(&programStartTime);
1052     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1053     appData.seedBase = random() + (random()<<15);
1054     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1055
1056     ClearProgramStats();
1057     programStats.ok_to_send = 1;
1058     programStats.seen_stat = 0;
1059
1060     /*
1061      * Initialize game list
1062      */
1063     ListNew(&gameList);
1064
1065
1066     /*
1067      * Internet chess server status
1068      */
1069     if (appData.icsActive) {
1070         appData.matchMode = FALSE;
1071         appData.matchGames = 0;
1072 #if ZIPPY
1073         appData.noChessProgram = !appData.zippyPlay;
1074 #else
1075         appData.zippyPlay = FALSE;
1076         appData.zippyTalk = FALSE;
1077         appData.noChessProgram = TRUE;
1078 #endif
1079         if (*appData.icsHelper != NULLCHAR) {
1080             appData.useTelnet = TRUE;
1081             appData.telnetProgram = appData.icsHelper;
1082         }
1083     } else {
1084         appData.zippyTalk = appData.zippyPlay = FALSE;
1085     }
1086
1087     /* [AS] Initialize pv info list [HGM] and game state */
1088     {
1089         int i, j;
1090
1091         for( i=0; i<=framePtr; i++ ) {
1092             pvInfoList[i].depth = -1;
1093             boards[i][EP_STATUS] = EP_NONE;
1094             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1095         }
1096     }
1097
1098     InitTimeControls();
1099
1100     /* [AS] Adjudication threshold */
1101     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1102
1103     InitEngine(&first, 0);
1104     InitEngine(&second, 1);
1105     CommonEngineInit();
1106
1107     pairing.which = "pairing"; // pairing engine
1108     pairing.pr = NoProc;
1109     pairing.isr = NULL;
1110     pairing.program = appData.pairingEngine;
1111     pairing.host = "localhost";
1112     pairing.dir = ".";
1113
1114     if (appData.icsActive) {
1115         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1116     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1117         appData.clockMode = FALSE;
1118         first.sendTime = second.sendTime = 0;
1119     }
1120
1121 #if ZIPPY
1122     /* Override some settings from environment variables, for backward
1123        compatibility.  Unfortunately it's not feasible to have the env
1124        vars just set defaults, at least in xboard.  Ugh.
1125     */
1126     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1127       ZippyInit();
1128     }
1129 #endif
1130
1131     if (!appData.icsActive) {
1132       char buf[MSG_SIZ];
1133       int len;
1134
1135       /* Check for variants that are supported only in ICS mode,
1136          or not at all.  Some that are accepted here nevertheless
1137          have bugs; see comments below.
1138       */
1139       VariantClass variant = StringToVariant(appData.variant);
1140       switch (variant) {
1141       case VariantBughouse:     /* need four players and two boards */
1142       case VariantKriegspiel:   /* need to hide pieces and move details */
1143         /* case VariantFischeRandom: (Fabien: moved below) */
1144         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1145         if( (len >= MSG_SIZ) && appData.debugMode )
1146           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1147
1148         DisplayFatalError(buf, 0, 2);
1149         return;
1150
1151       case VariantUnknown:
1152       case VariantLoadable:
1153       case Variant29:
1154       case Variant30:
1155       case Variant31:
1156       case Variant32:
1157       case Variant33:
1158       case Variant34:
1159       case Variant35:
1160       case Variant36:
1161       default:
1162         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1163         if( (len >= MSG_SIZ) && appData.debugMode )
1164           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1165
1166         DisplayFatalError(buf, 0, 2);
1167         return;
1168
1169       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1170       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1171       case VariantGothic:     /* [HGM] should work */
1172       case VariantCapablanca: /* [HGM] should work */
1173       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1174       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1175       case VariantChu:        /* [HGM] experimental */
1176       case VariantKnightmate: /* [HGM] should work */
1177       case VariantCylinder:   /* [HGM] untested */
1178       case VariantFalcon:     /* [HGM] untested */
1179       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1180                                  offboard interposition not understood */
1181       case VariantNormal:     /* definitely works! */
1182       case VariantWildCastle: /* pieces not automatically shuffled */
1183       case VariantNoCastle:   /* pieces not automatically shuffled */
1184       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1185       case VariantLosers:     /* should work except for win condition,
1186                                  and doesn't know captures are mandatory */
1187       case VariantSuicide:    /* should work except for win condition,
1188                                  and doesn't know captures are mandatory */
1189       case VariantGiveaway:   /* should work except for win condition,
1190                                  and doesn't know captures are mandatory */
1191       case VariantTwoKings:   /* should work */
1192       case VariantAtomic:     /* should work except for win condition */
1193       case Variant3Check:     /* should work except for win condition */
1194       case VariantShatranj:   /* should work except for all win conditions */
1195       case VariantMakruk:     /* should work except for draw countdown */
1196       case VariantASEAN :     /* should work except for draw countdown */
1197       case VariantBerolina:   /* might work if TestLegality is off */
1198       case VariantCapaRandom: /* should work */
1199       case VariantJanus:      /* should work */
1200       case VariantSuper:      /* experimental */
1201       case VariantGreat:      /* experimental, requires legality testing to be off */
1202       case VariantSChess:     /* S-Chess, should work */
1203       case VariantGrand:      /* should work */
1204       case VariantSpartan:    /* should work */
1205       case VariantLion:       /* should work */
1206         break;
1207       }
1208     }
1209
1210 }
1211
1212 int
1213 NextIntegerFromString (char ** str, long * value)
1214 {
1215     int result = -1;
1216     char * s = *str;
1217
1218     while( *s == ' ' || *s == '\t' ) {
1219         s++;
1220     }
1221
1222     *value = 0;
1223
1224     if( *s >= '0' && *s <= '9' ) {
1225         while( *s >= '0' && *s <= '9' ) {
1226             *value = *value * 10 + (*s - '0');
1227             s++;
1228         }
1229
1230         result = 0;
1231     }
1232
1233     *str = s;
1234
1235     return result;
1236 }
1237
1238 int
1239 NextTimeControlFromString (char ** str, long * value)
1240 {
1241     long temp;
1242     int result = NextIntegerFromString( str, &temp );
1243
1244     if( result == 0 ) {
1245         *value = temp * 60; /* Minutes */
1246         if( **str == ':' ) {
1247             (*str)++;
1248             result = NextIntegerFromString( str, &temp );
1249             *value += temp; /* Seconds */
1250         }
1251     }
1252
1253     return result;
1254 }
1255
1256 int
1257 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1258 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1259     int result = -1, type = 0; long temp, temp2;
1260
1261     if(**str != ':') return -1; // old params remain in force!
1262     (*str)++;
1263     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1264     if( NextIntegerFromString( str, &temp ) ) return -1;
1265     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1266
1267     if(**str != '/') {
1268         /* time only: incremental or sudden-death time control */
1269         if(**str == '+') { /* increment follows; read it */
1270             (*str)++;
1271             if(**str == '!') type = *(*str)++; // Bronstein TC
1272             if(result = NextIntegerFromString( str, &temp2)) return -1;
1273             *inc = temp2 * 1000;
1274             if(**str == '.') { // read fraction of increment
1275                 char *start = ++(*str);
1276                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1277                 temp2 *= 1000;
1278                 while(start++ < *str) temp2 /= 10;
1279                 *inc += temp2;
1280             }
1281         } else *inc = 0;
1282         *moves = 0; *tc = temp * 1000; *incType = type;
1283         return 0;
1284     }
1285
1286     (*str)++; /* classical time control */
1287     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1288
1289     if(result == 0) {
1290         *moves = temp;
1291         *tc    = temp2 * 1000;
1292         *inc   = 0;
1293         *incType = type;
1294     }
1295     return result;
1296 }
1297
1298 int
1299 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1300 {   /* [HGM] get time to add from the multi-session time-control string */
1301     int incType, moves=1; /* kludge to force reading of first session */
1302     long time, increment;
1303     char *s = tcString;
1304
1305     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1306     do {
1307         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1308         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1309         if(movenr == -1) return time;    /* last move before new session     */
1310         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1311         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1312         if(!moves) return increment;     /* current session is incremental   */
1313         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1314     } while(movenr >= -1);               /* try again for next session       */
1315
1316     return 0; // no new time quota on this move
1317 }
1318
1319 int
1320 ParseTimeControl (char *tc, float ti, int mps)
1321 {
1322   long tc1;
1323   long tc2;
1324   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1325   int min, sec=0;
1326
1327   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1328   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1329       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1330   if(ti > 0) {
1331
1332     if(mps)
1333       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1334     else
1335       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1336   } else {
1337     if(mps)
1338       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1339     else
1340       snprintf(buf, MSG_SIZ, ":%s", mytc);
1341   }
1342   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1343
1344   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1345     return FALSE;
1346   }
1347
1348   if( *tc == '/' ) {
1349     /* Parse second time control */
1350     tc++;
1351
1352     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1353       return FALSE;
1354     }
1355
1356     if( tc2 == 0 ) {
1357       return FALSE;
1358     }
1359
1360     timeControl_2 = tc2 * 1000;
1361   }
1362   else {
1363     timeControl_2 = 0;
1364   }
1365
1366   if( tc1 == 0 ) {
1367     return FALSE;
1368   }
1369
1370   timeControl = tc1 * 1000;
1371
1372   if (ti >= 0) {
1373     timeIncrement = ti * 1000;  /* convert to ms */
1374     movesPerSession = 0;
1375   } else {
1376     timeIncrement = 0;
1377     movesPerSession = mps;
1378   }
1379   return TRUE;
1380 }
1381
1382 void
1383 InitBackEnd2 ()
1384 {
1385     if (appData.debugMode) {
1386 #    ifdef __GIT_VERSION
1387       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1388 #    else
1389       fprintf(debugFP, "Version: %s\n", programVersion);
1390 #    endif
1391     }
1392     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1393
1394     set_cont_sequence(appData.wrapContSeq);
1395     if (appData.matchGames > 0) {
1396         appData.matchMode = TRUE;
1397     } else if (appData.matchMode) {
1398         appData.matchGames = 1;
1399     }
1400     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1401         appData.matchGames = appData.sameColorGames;
1402     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1403         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1404         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1405     }
1406     Reset(TRUE, FALSE);
1407     if (appData.noChessProgram || first.protocolVersion == 1) {
1408       InitBackEnd3();
1409     } else {
1410       /* kludge: allow timeout for initial "feature" commands */
1411       FreezeUI();
1412       DisplayMessage("", _("Starting chess program"));
1413       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1414     }
1415 }
1416
1417 int
1418 CalculateIndex (int index, int gameNr)
1419 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1420     int res;
1421     if(index > 0) return index; // fixed nmber
1422     if(index == 0) return 1;
1423     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1424     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1425     return res;
1426 }
1427
1428 int
1429 LoadGameOrPosition (int gameNr)
1430 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1431     if (*appData.loadGameFile != NULLCHAR) {
1432         if (!LoadGameFromFile(appData.loadGameFile,
1433                 CalculateIndex(appData.loadGameIndex, gameNr),
1434                               appData.loadGameFile, FALSE)) {
1435             DisplayFatalError(_("Bad game file"), 0, 1);
1436             return 0;
1437         }
1438     } else if (*appData.loadPositionFile != NULLCHAR) {
1439         if (!LoadPositionFromFile(appData.loadPositionFile,
1440                 CalculateIndex(appData.loadPositionIndex, gameNr),
1441                                   appData.loadPositionFile)) {
1442             DisplayFatalError(_("Bad position file"), 0, 1);
1443             return 0;
1444         }
1445     }
1446     return 1;
1447 }
1448
1449 void
1450 ReserveGame (int gameNr, char resChar)
1451 {
1452     FILE *tf = fopen(appData.tourneyFile, "r+");
1453     char *p, *q, c, buf[MSG_SIZ];
1454     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1455     safeStrCpy(buf, lastMsg, MSG_SIZ);
1456     DisplayMessage(_("Pick new game"), "");
1457     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1458     ParseArgsFromFile(tf);
1459     p = q = appData.results;
1460     if(appData.debugMode) {
1461       char *r = appData.participants;
1462       fprintf(debugFP, "results = '%s'\n", p);
1463       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1464       fprintf(debugFP, "\n");
1465     }
1466     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1467     nextGame = q - p;
1468     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1469     safeStrCpy(q, p, strlen(p) + 2);
1470     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1471     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1472     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1473         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1474         q[nextGame] = '*';
1475     }
1476     fseek(tf, -(strlen(p)+4), SEEK_END);
1477     c = fgetc(tf);
1478     if(c != '"') // depending on DOS or Unix line endings we can be one off
1479          fseek(tf, -(strlen(p)+2), SEEK_END);
1480     else fseek(tf, -(strlen(p)+3), SEEK_END);
1481     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1482     DisplayMessage(buf, "");
1483     free(p); appData.results = q;
1484     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1485        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1486       int round = appData.defaultMatchGames * appData.tourneyType;
1487       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1488          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1489         UnloadEngine(&first);  // next game belongs to other pairing;
1490         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1491     }
1492     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1493 }
1494
1495 void
1496 MatchEvent (int mode)
1497 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1498         int dummy;
1499         if(matchMode) { // already in match mode: switch it off
1500             abortMatch = TRUE;
1501             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1502             return;
1503         }
1504 //      if(gameMode != BeginningOfGame) {
1505 //          DisplayError(_("You can only start a match from the initial position."), 0);
1506 //          return;
1507 //      }
1508         abortMatch = FALSE;
1509         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1510         /* Set up machine vs. machine match */
1511         nextGame = 0;
1512         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1513         if(appData.tourneyFile[0]) {
1514             ReserveGame(-1, 0);
1515             if(nextGame > appData.matchGames) {
1516                 char buf[MSG_SIZ];
1517                 if(strchr(appData.results, '*') == NULL) {
1518                     FILE *f;
1519                     appData.tourneyCycles++;
1520                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1521                         fclose(f);
1522                         NextTourneyGame(-1, &dummy);
1523                         ReserveGame(-1, 0);
1524                         if(nextGame <= appData.matchGames) {
1525                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1526                             matchMode = mode;
1527                             ScheduleDelayedEvent(NextMatchGame, 10000);
1528                             return;
1529                         }
1530                     }
1531                 }
1532                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1533                 DisplayError(buf, 0);
1534                 appData.tourneyFile[0] = 0;
1535                 return;
1536             }
1537         } else
1538         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1539             DisplayFatalError(_("Can't have a match with no chess programs"),
1540                               0, 2);
1541             return;
1542         }
1543         matchMode = mode;
1544         matchGame = roundNr = 1;
1545         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1546         NextMatchGame();
1547 }
1548
1549 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1550
1551 void
1552 InitBackEnd3 P((void))
1553 {
1554     GameMode initialMode;
1555     char buf[MSG_SIZ];
1556     int err, len;
1557
1558     InitChessProgram(&first, startedFromSetupPosition);
1559
1560     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1561         free(programVersion);
1562         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1563         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1564         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1565     }
1566
1567     if (appData.icsActive) {
1568 #ifdef WIN32
1569         /* [DM] Make a console window if needed [HGM] merged ifs */
1570         ConsoleCreate();
1571 #endif
1572         err = establish();
1573         if (err != 0)
1574           {
1575             if (*appData.icsCommPort != NULLCHAR)
1576               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1577                              appData.icsCommPort);
1578             else
1579               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1580                         appData.icsHost, appData.icsPort);
1581
1582             if( (len >= MSG_SIZ) && appData.debugMode )
1583               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1584
1585             DisplayFatalError(buf, err, 1);
1586             return;
1587         }
1588         SetICSMode();
1589         telnetISR =
1590           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1591         fromUserISR =
1592           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1593         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1594             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1595     } else if (appData.noChessProgram) {
1596         SetNCPMode();
1597     } else {
1598         SetGNUMode();
1599     }
1600
1601     if (*appData.cmailGameName != NULLCHAR) {
1602         SetCmailMode();
1603         OpenLoopback(&cmailPR);
1604         cmailISR =
1605           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1606     }
1607
1608     ThawUI();
1609     DisplayMessage("", "");
1610     if (StrCaseCmp(appData.initialMode, "") == 0) {
1611       initialMode = BeginningOfGame;
1612       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1613         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1614         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1615         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1616         ModeHighlight();
1617       }
1618     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1619       initialMode = TwoMachinesPlay;
1620     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1621       initialMode = AnalyzeFile;
1622     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1623       initialMode = AnalyzeMode;
1624     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1625       initialMode = MachinePlaysWhite;
1626     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1627       initialMode = MachinePlaysBlack;
1628     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1629       initialMode = EditGame;
1630     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1631       initialMode = EditPosition;
1632     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1633       initialMode = Training;
1634     } else {
1635       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1636       if( (len >= MSG_SIZ) && appData.debugMode )
1637         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1638
1639       DisplayFatalError(buf, 0, 2);
1640       return;
1641     }
1642
1643     if (appData.matchMode) {
1644         if(appData.tourneyFile[0]) { // start tourney from command line
1645             FILE *f;
1646             if(f = fopen(appData.tourneyFile, "r")) {
1647                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1648                 fclose(f);
1649                 appData.clockMode = TRUE;
1650                 SetGNUMode();
1651             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1652         }
1653         MatchEvent(TRUE);
1654     } else if (*appData.cmailGameName != NULLCHAR) {
1655         /* Set up cmail mode */
1656         ReloadCmailMsgEvent(TRUE);
1657     } else {
1658         /* Set up other modes */
1659         if (initialMode == AnalyzeFile) {
1660           if (*appData.loadGameFile == NULLCHAR) {
1661             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1662             return;
1663           }
1664         }
1665         if (*appData.loadGameFile != NULLCHAR) {
1666             (void) LoadGameFromFile(appData.loadGameFile,
1667                                     appData.loadGameIndex,
1668                                     appData.loadGameFile, TRUE);
1669         } else if (*appData.loadPositionFile != NULLCHAR) {
1670             (void) LoadPositionFromFile(appData.loadPositionFile,
1671                                         appData.loadPositionIndex,
1672                                         appData.loadPositionFile);
1673             /* [HGM] try to make self-starting even after FEN load */
1674             /* to allow automatic setup of fairy variants with wtm */
1675             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1676                 gameMode = BeginningOfGame;
1677                 setboardSpoiledMachineBlack = 1;
1678             }
1679             /* [HGM] loadPos: make that every new game uses the setup */
1680             /* from file as long as we do not switch variant          */
1681             if(!blackPlaysFirst) {
1682                 startedFromPositionFile = TRUE;
1683                 CopyBoard(filePosition, boards[0]);
1684             }
1685         }
1686         if (initialMode == AnalyzeMode) {
1687           if (appData.noChessProgram) {
1688             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1689             return;
1690           }
1691           if (appData.icsActive) {
1692             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1693             return;
1694           }
1695           AnalyzeModeEvent();
1696         } else if (initialMode == AnalyzeFile) {
1697           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1698           ShowThinkingEvent();
1699           AnalyzeFileEvent();
1700           AnalysisPeriodicEvent(1);
1701         } else if (initialMode == MachinePlaysWhite) {
1702           if (appData.noChessProgram) {
1703             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1704                               0, 2);
1705             return;
1706           }
1707           if (appData.icsActive) {
1708             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1709                               0, 2);
1710             return;
1711           }
1712           MachineWhiteEvent();
1713         } else if (initialMode == MachinePlaysBlack) {
1714           if (appData.noChessProgram) {
1715             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1716                               0, 2);
1717             return;
1718           }
1719           if (appData.icsActive) {
1720             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1721                               0, 2);
1722             return;
1723           }
1724           MachineBlackEvent();
1725         } else if (initialMode == TwoMachinesPlay) {
1726           if (appData.noChessProgram) {
1727             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1728                               0, 2);
1729             return;
1730           }
1731           if (appData.icsActive) {
1732             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1733                               0, 2);
1734             return;
1735           }
1736           TwoMachinesEvent();
1737         } else if (initialMode == EditGame) {
1738           EditGameEvent();
1739         } else if (initialMode == EditPosition) {
1740           EditPositionEvent();
1741         } else if (initialMode == Training) {
1742           if (*appData.loadGameFile == NULLCHAR) {
1743             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1744             return;
1745           }
1746           TrainingEvent();
1747         }
1748     }
1749 }
1750
1751 void
1752 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1753 {
1754     DisplayBook(current+1);
1755
1756     MoveHistorySet( movelist, first, last, current, pvInfoList );
1757
1758     EvalGraphSet( first, last, current, pvInfoList );
1759
1760     MakeEngineOutputTitle();
1761 }
1762
1763 /*
1764  * Establish will establish a contact to a remote host.port.
1765  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1766  *  used to talk to the host.
1767  * Returns 0 if okay, error code if not.
1768  */
1769 int
1770 establish ()
1771 {
1772     char buf[MSG_SIZ];
1773
1774     if (*appData.icsCommPort != NULLCHAR) {
1775         /* Talk to the host through a serial comm port */
1776         return OpenCommPort(appData.icsCommPort, &icsPR);
1777
1778     } else if (*appData.gateway != NULLCHAR) {
1779         if (*appData.remoteShell == NULLCHAR) {
1780             /* Use the rcmd protocol to run telnet program on a gateway host */
1781             snprintf(buf, sizeof(buf), "%s %s %s",
1782                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1783             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1784
1785         } else {
1786             /* Use the rsh program to run telnet program on a gateway host */
1787             if (*appData.remoteUser == NULLCHAR) {
1788                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1789                         appData.gateway, appData.telnetProgram,
1790                         appData.icsHost, appData.icsPort);
1791             } else {
1792                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1793                         appData.remoteShell, appData.gateway,
1794                         appData.remoteUser, appData.telnetProgram,
1795                         appData.icsHost, appData.icsPort);
1796             }
1797             return StartChildProcess(buf, "", &icsPR);
1798
1799         }
1800     } else if (appData.useTelnet) {
1801         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1802
1803     } else {
1804         /* TCP socket interface differs somewhat between
1805            Unix and NT; handle details in the front end.
1806            */
1807         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1808     }
1809 }
1810
1811 void
1812 EscapeExpand (char *p, char *q)
1813 {       // [HGM] initstring: routine to shape up string arguments
1814         while(*p++ = *q++) if(p[-1] == '\\')
1815             switch(*q++) {
1816                 case 'n': p[-1] = '\n'; break;
1817                 case 'r': p[-1] = '\r'; break;
1818                 case 't': p[-1] = '\t'; break;
1819                 case '\\': p[-1] = '\\'; break;
1820                 case 0: *p = 0; return;
1821                 default: p[-1] = q[-1]; break;
1822             }
1823 }
1824
1825 void
1826 show_bytes (FILE *fp, char *buf, int count)
1827 {
1828     while (count--) {
1829         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1830             fprintf(fp, "\\%03o", *buf & 0xff);
1831         } else {
1832             putc(*buf, fp);
1833         }
1834         buf++;
1835     }
1836     fflush(fp);
1837 }
1838
1839 /* Returns an errno value */
1840 int
1841 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1842 {
1843     char buf[8192], *p, *q, *buflim;
1844     int left, newcount, outcount;
1845
1846     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1847         *appData.gateway != NULLCHAR) {
1848         if (appData.debugMode) {
1849             fprintf(debugFP, ">ICS: ");
1850             show_bytes(debugFP, message, count);
1851             fprintf(debugFP, "\n");
1852         }
1853         return OutputToProcess(pr, message, count, outError);
1854     }
1855
1856     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1857     p = message;
1858     q = buf;
1859     left = count;
1860     newcount = 0;
1861     while (left) {
1862         if (q >= buflim) {
1863             if (appData.debugMode) {
1864                 fprintf(debugFP, ">ICS: ");
1865                 show_bytes(debugFP, buf, newcount);
1866                 fprintf(debugFP, "\n");
1867             }
1868             outcount = OutputToProcess(pr, buf, newcount, outError);
1869             if (outcount < newcount) return -1; /* to be sure */
1870             q = buf;
1871             newcount = 0;
1872         }
1873         if (*p == '\n') {
1874             *q++ = '\r';
1875             newcount++;
1876         } else if (((unsigned char) *p) == TN_IAC) {
1877             *q++ = (char) TN_IAC;
1878             newcount ++;
1879         }
1880         *q++ = *p++;
1881         newcount++;
1882         left--;
1883     }
1884     if (appData.debugMode) {
1885         fprintf(debugFP, ">ICS: ");
1886         show_bytes(debugFP, buf, newcount);
1887         fprintf(debugFP, "\n");
1888     }
1889     outcount = OutputToProcess(pr, buf, newcount, outError);
1890     if (outcount < newcount) return -1; /* to be sure */
1891     return count;
1892 }
1893
1894 void
1895 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1896 {
1897     int outError, outCount;
1898     static int gotEof = 0;
1899     static FILE *ini;
1900
1901     /* Pass data read from player on to ICS */
1902     if (count > 0) {
1903         gotEof = 0;
1904         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1905         if (outCount < count) {
1906             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1907         }
1908         if(have_sent_ICS_logon == 2) {
1909           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1910             fprintf(ini, "%s", message);
1911             have_sent_ICS_logon = 3;
1912           } else
1913             have_sent_ICS_logon = 1;
1914         } else if(have_sent_ICS_logon == 3) {
1915             fprintf(ini, "%s", message);
1916             fclose(ini);
1917           have_sent_ICS_logon = 1;
1918         }
1919     } else if (count < 0) {
1920         RemoveInputSource(isr);
1921         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1922     } else if (gotEof++ > 0) {
1923         RemoveInputSource(isr);
1924         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1925     }
1926 }
1927
1928 void
1929 KeepAlive ()
1930 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1931     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1932     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1933     SendToICS("date\n");
1934     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1935 }
1936
1937 /* added routine for printf style output to ics */
1938 void
1939 ics_printf (char *format, ...)
1940 {
1941     char buffer[MSG_SIZ];
1942     va_list args;
1943
1944     va_start(args, format);
1945     vsnprintf(buffer, sizeof(buffer), format, args);
1946     buffer[sizeof(buffer)-1] = '\0';
1947     SendToICS(buffer);
1948     va_end(args);
1949 }
1950
1951 void
1952 SendToICS (char *s)
1953 {
1954     int count, outCount, outError;
1955
1956     if (icsPR == NoProc) return;
1957
1958     count = strlen(s);
1959     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1960     if (outCount < count) {
1961         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1962     }
1963 }
1964
1965 /* This is used for sending logon scripts to the ICS. Sending
1966    without a delay causes problems when using timestamp on ICC
1967    (at least on my machine). */
1968 void
1969 SendToICSDelayed (char *s, long msdelay)
1970 {
1971     int count, outCount, outError;
1972
1973     if (icsPR == NoProc) return;
1974
1975     count = strlen(s);
1976     if (appData.debugMode) {
1977         fprintf(debugFP, ">ICS: ");
1978         show_bytes(debugFP, s, count);
1979         fprintf(debugFP, "\n");
1980     }
1981     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1982                                       msdelay);
1983     if (outCount < count) {
1984         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1985     }
1986 }
1987
1988
1989 /* Remove all highlighting escape sequences in s
1990    Also deletes any suffix starting with '('
1991    */
1992 char *
1993 StripHighlightAndTitle (char *s)
1994 {
1995     static char retbuf[MSG_SIZ];
1996     char *p = retbuf;
1997
1998     while (*s != NULLCHAR) {
1999         while (*s == '\033') {
2000             while (*s != NULLCHAR && !isalpha(*s)) s++;
2001             if (*s != NULLCHAR) s++;
2002         }
2003         while (*s != NULLCHAR && *s != '\033') {
2004             if (*s == '(' || *s == '[') {
2005                 *p = NULLCHAR;
2006                 return retbuf;
2007             }
2008             *p++ = *s++;
2009         }
2010     }
2011     *p = NULLCHAR;
2012     return retbuf;
2013 }
2014
2015 /* Remove all highlighting escape sequences in s */
2016 char *
2017 StripHighlight (char *s)
2018 {
2019     static char retbuf[MSG_SIZ];
2020     char *p = retbuf;
2021
2022     while (*s != NULLCHAR) {
2023         while (*s == '\033') {
2024             while (*s != NULLCHAR && !isalpha(*s)) s++;
2025             if (*s != NULLCHAR) s++;
2026         }
2027         while (*s != NULLCHAR && *s != '\033') {
2028             *p++ = *s++;
2029         }
2030     }
2031     *p = NULLCHAR;
2032     return retbuf;
2033 }
2034
2035 char engineVariant[MSG_SIZ];
2036 char *variantNames[] = VARIANT_NAMES;
2037 char *
2038 VariantName (VariantClass v)
2039 {
2040     if(v == VariantUnknown || *engineVariant) return engineVariant;
2041     return variantNames[v];
2042 }
2043
2044
2045 /* Identify a variant from the strings the chess servers use or the
2046    PGN Variant tag names we use. */
2047 VariantClass
2048 StringToVariant (char *e)
2049 {
2050     char *p;
2051     int wnum = -1;
2052     VariantClass v = VariantNormal;
2053     int i, found = FALSE;
2054     char buf[MSG_SIZ];
2055     int len;
2056
2057     if (!e) return v;
2058
2059     /* [HGM] skip over optional board-size prefixes */
2060     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2061         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2062         while( *e++ != '_');
2063     }
2064
2065     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2066         v = VariantNormal;
2067         found = TRUE;
2068     } else
2069     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2070       if (StrCaseStr(e, variantNames[i])) {
2071         v = (VariantClass) i;
2072         found = TRUE;
2073         break;
2074       }
2075     }
2076
2077     if (!found) {
2078       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2079           || StrCaseStr(e, "wild/fr")
2080           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2081         v = VariantFischeRandom;
2082       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2083                  (i = 1, p = StrCaseStr(e, "w"))) {
2084         p += i;
2085         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2086         if (isdigit(*p)) {
2087           wnum = atoi(p);
2088         } else {
2089           wnum = -1;
2090         }
2091         switch (wnum) {
2092         case 0: /* FICS only, actually */
2093         case 1:
2094           /* Castling legal even if K starts on d-file */
2095           v = VariantWildCastle;
2096           break;
2097         case 2:
2098         case 3:
2099         case 4:
2100           /* Castling illegal even if K & R happen to start in
2101              normal positions. */
2102           v = VariantNoCastle;
2103           break;
2104         case 5:
2105         case 7:
2106         case 8:
2107         case 10:
2108         case 11:
2109         case 12:
2110         case 13:
2111         case 14:
2112         case 15:
2113         case 18:
2114         case 19:
2115           /* Castling legal iff K & R start in normal positions */
2116           v = VariantNormal;
2117           break;
2118         case 6:
2119         case 20:
2120         case 21:
2121           /* Special wilds for position setup; unclear what to do here */
2122           v = VariantLoadable;
2123           break;
2124         case 9:
2125           /* Bizarre ICC game */
2126           v = VariantTwoKings;
2127           break;
2128         case 16:
2129           v = VariantKriegspiel;
2130           break;
2131         case 17:
2132           v = VariantLosers;
2133           break;
2134         case 22:
2135           v = VariantFischeRandom;
2136           break;
2137         case 23:
2138           v = VariantCrazyhouse;
2139           break;
2140         case 24:
2141           v = VariantBughouse;
2142           break;
2143         case 25:
2144           v = Variant3Check;
2145           break;
2146         case 26:
2147           /* Not quite the same as FICS suicide! */
2148           v = VariantGiveaway;
2149           break;
2150         case 27:
2151           v = VariantAtomic;
2152           break;
2153         case 28:
2154           v = VariantShatranj;
2155           break;
2156
2157         /* Temporary names for future ICC types.  The name *will* change in
2158            the next xboard/WinBoard release after ICC defines it. */
2159         case 29:
2160           v = Variant29;
2161           break;
2162         case 30:
2163           v = Variant30;
2164           break;
2165         case 31:
2166           v = Variant31;
2167           break;
2168         case 32:
2169           v = Variant32;
2170           break;
2171         case 33:
2172           v = Variant33;
2173           break;
2174         case 34:
2175           v = Variant34;
2176           break;
2177         case 35:
2178           v = Variant35;
2179           break;
2180         case 36:
2181           v = Variant36;
2182           break;
2183         case 37:
2184           v = VariantShogi;
2185           break;
2186         case 38:
2187           v = VariantXiangqi;
2188           break;
2189         case 39:
2190           v = VariantCourier;
2191           break;
2192         case 40:
2193           v = VariantGothic;
2194           break;
2195         case 41:
2196           v = VariantCapablanca;
2197           break;
2198         case 42:
2199           v = VariantKnightmate;
2200           break;
2201         case 43:
2202           v = VariantFairy;
2203           break;
2204         case 44:
2205           v = VariantCylinder;
2206           break;
2207         case 45:
2208           v = VariantFalcon;
2209           break;
2210         case 46:
2211           v = VariantCapaRandom;
2212           break;
2213         case 47:
2214           v = VariantBerolina;
2215           break;
2216         case 48:
2217           v = VariantJanus;
2218           break;
2219         case 49:
2220           v = VariantSuper;
2221           break;
2222         case 50:
2223           v = VariantGreat;
2224           break;
2225         case -1:
2226           /* Found "wild" or "w" in the string but no number;
2227              must assume it's normal chess. */
2228           v = VariantNormal;
2229           break;
2230         default:
2231           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2232           if( (len >= MSG_SIZ) && appData.debugMode )
2233             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2234
2235           DisplayError(buf, 0);
2236           v = VariantUnknown;
2237           break;
2238         }
2239       }
2240     }
2241     if (appData.debugMode) {
2242       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2243               e, wnum, VariantName(v));
2244     }
2245     return v;
2246 }
2247
2248 static int leftover_start = 0, leftover_len = 0;
2249 char star_match[STAR_MATCH_N][MSG_SIZ];
2250
2251 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2252    advance *index beyond it, and set leftover_start to the new value of
2253    *index; else return FALSE.  If pattern contains the character '*', it
2254    matches any sequence of characters not containing '\r', '\n', or the
2255    character following the '*' (if any), and the matched sequence(s) are
2256    copied into star_match.
2257    */
2258 int
2259 looking_at ( char *buf, int *index, char *pattern)
2260 {
2261     char *bufp = &buf[*index], *patternp = pattern;
2262     int star_count = 0;
2263     char *matchp = star_match[0];
2264
2265     for (;;) {
2266         if (*patternp == NULLCHAR) {
2267             *index = leftover_start = bufp - buf;
2268             *matchp = NULLCHAR;
2269             return TRUE;
2270         }
2271         if (*bufp == NULLCHAR) return FALSE;
2272         if (*patternp == '*') {
2273             if (*bufp == *(patternp + 1)) {
2274                 *matchp = NULLCHAR;
2275                 matchp = star_match[++star_count];
2276                 patternp += 2;
2277                 bufp++;
2278                 continue;
2279             } else if (*bufp == '\n' || *bufp == '\r') {
2280                 patternp++;
2281                 if (*patternp == NULLCHAR)
2282                   continue;
2283                 else
2284                   return FALSE;
2285             } else {
2286                 *matchp++ = *bufp++;
2287                 continue;
2288             }
2289         }
2290         if (*patternp != *bufp) return FALSE;
2291         patternp++;
2292         bufp++;
2293     }
2294 }
2295
2296 void
2297 SendToPlayer (char *data, int length)
2298 {
2299     int error, outCount;
2300     outCount = OutputToProcess(NoProc, data, length, &error);
2301     if (outCount < length) {
2302         DisplayFatalError(_("Error writing to display"), error, 1);
2303     }
2304 }
2305
2306 void
2307 PackHolding (char packed[], char *holding)
2308 {
2309     char *p = holding;
2310     char *q = packed;
2311     int runlength = 0;
2312     int curr = 9999;
2313     do {
2314         if (*p == curr) {
2315             runlength++;
2316         } else {
2317             switch (runlength) {
2318               case 0:
2319                 break;
2320               case 1:
2321                 *q++ = curr;
2322                 break;
2323               case 2:
2324                 *q++ = curr;
2325                 *q++ = curr;
2326                 break;
2327               default:
2328                 sprintf(q, "%d", runlength);
2329                 while (*q) q++;
2330                 *q++ = curr;
2331                 break;
2332             }
2333             runlength = 1;
2334             curr = *p;
2335         }
2336     } while (*p++);
2337     *q = NULLCHAR;
2338 }
2339
2340 /* Telnet protocol requests from the front end */
2341 void
2342 TelnetRequest (unsigned char ddww, unsigned char option)
2343 {
2344     unsigned char msg[3];
2345     int outCount, outError;
2346
2347     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2348
2349     if (appData.debugMode) {
2350         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2351         switch (ddww) {
2352           case TN_DO:
2353             ddwwStr = "DO";
2354             break;
2355           case TN_DONT:
2356             ddwwStr = "DONT";
2357             break;
2358           case TN_WILL:
2359             ddwwStr = "WILL";
2360             break;
2361           case TN_WONT:
2362             ddwwStr = "WONT";
2363             break;
2364           default:
2365             ddwwStr = buf1;
2366             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2367             break;
2368         }
2369         switch (option) {
2370           case TN_ECHO:
2371             optionStr = "ECHO";
2372             break;
2373           default:
2374             optionStr = buf2;
2375             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2376             break;
2377         }
2378         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2379     }
2380     msg[0] = TN_IAC;
2381     msg[1] = ddww;
2382     msg[2] = option;
2383     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2384     if (outCount < 3) {
2385         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2386     }
2387 }
2388
2389 void
2390 DoEcho ()
2391 {
2392     if (!appData.icsActive) return;
2393     TelnetRequest(TN_DO, TN_ECHO);
2394 }
2395
2396 void
2397 DontEcho ()
2398 {
2399     if (!appData.icsActive) return;
2400     TelnetRequest(TN_DONT, TN_ECHO);
2401 }
2402
2403 void
2404 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2405 {
2406     /* put the holdings sent to us by the server on the board holdings area */
2407     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2408     char p;
2409     ChessSquare piece;
2410
2411     if(gameInfo.holdingsWidth < 2)  return;
2412     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2413         return; // prevent overwriting by pre-board holdings
2414
2415     if( (int)lowestPiece >= BlackPawn ) {
2416         holdingsColumn = 0;
2417         countsColumn = 1;
2418         holdingsStartRow = BOARD_HEIGHT-1;
2419         direction = -1;
2420     } else {
2421         holdingsColumn = BOARD_WIDTH-1;
2422         countsColumn = BOARD_WIDTH-2;
2423         holdingsStartRow = 0;
2424         direction = 1;
2425     }
2426
2427     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2428         board[i][holdingsColumn] = EmptySquare;
2429         board[i][countsColumn]   = (ChessSquare) 0;
2430     }
2431     while( (p=*holdings++) != NULLCHAR ) {
2432         piece = CharToPiece( ToUpper(p) );
2433         if(piece == EmptySquare) continue;
2434         /*j = (int) piece - (int) WhitePawn;*/
2435         j = PieceToNumber(piece);
2436         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2437         if(j < 0) continue;               /* should not happen */
2438         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2439         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2440         board[holdingsStartRow+j*direction][countsColumn]++;
2441     }
2442 }
2443
2444
2445 void
2446 VariantSwitch (Board board, VariantClass newVariant)
2447 {
2448    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2449    static Board oldBoard;
2450
2451    startedFromPositionFile = FALSE;
2452    if(gameInfo.variant == newVariant) return;
2453
2454    /* [HGM] This routine is called each time an assignment is made to
2455     * gameInfo.variant during a game, to make sure the board sizes
2456     * are set to match the new variant. If that means adding or deleting
2457     * holdings, we shift the playing board accordingly
2458     * This kludge is needed because in ICS observe mode, we get boards
2459     * of an ongoing game without knowing the variant, and learn about the
2460     * latter only later. This can be because of the move list we requested,
2461     * in which case the game history is refilled from the beginning anyway,
2462     * but also when receiving holdings of a crazyhouse game. In the latter
2463     * case we want to add those holdings to the already received position.
2464     */
2465
2466
2467    if (appData.debugMode) {
2468      fprintf(debugFP, "Switch board from %s to %s\n",
2469              VariantName(gameInfo.variant), VariantName(newVariant));
2470      setbuf(debugFP, NULL);
2471    }
2472    shuffleOpenings = 0;       /* [HGM] shuffle */
2473    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2474    switch(newVariant)
2475      {
2476      case VariantShogi:
2477        newWidth = 9;  newHeight = 9;
2478        gameInfo.holdingsSize = 7;
2479      case VariantBughouse:
2480      case VariantCrazyhouse:
2481        newHoldingsWidth = 2; break;
2482      case VariantGreat:
2483        newWidth = 10;
2484      case VariantSuper:
2485        newHoldingsWidth = 2;
2486        gameInfo.holdingsSize = 8;
2487        break;
2488      case VariantGothic:
2489      case VariantCapablanca:
2490      case VariantCapaRandom:
2491        newWidth = 10;
2492      default:
2493        newHoldingsWidth = gameInfo.holdingsSize = 0;
2494      };
2495
2496    if(newWidth  != gameInfo.boardWidth  ||
2497       newHeight != gameInfo.boardHeight ||
2498       newHoldingsWidth != gameInfo.holdingsWidth ) {
2499
2500      /* shift position to new playing area, if needed */
2501      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2502        for(i=0; i<BOARD_HEIGHT; i++)
2503          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2504            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2505              board[i][j];
2506        for(i=0; i<newHeight; i++) {
2507          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2508          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2509        }
2510      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2511        for(i=0; i<BOARD_HEIGHT; i++)
2512          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2513            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2514              board[i][j];
2515      }
2516      board[HOLDINGS_SET] = 0;
2517      gameInfo.boardWidth  = newWidth;
2518      gameInfo.boardHeight = newHeight;
2519      gameInfo.holdingsWidth = newHoldingsWidth;
2520      gameInfo.variant = newVariant;
2521      InitDrawingSizes(-2, 0);
2522    } else gameInfo.variant = newVariant;
2523    CopyBoard(oldBoard, board);   // remember correctly formatted board
2524      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2525    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2526 }
2527
2528 static int loggedOn = FALSE;
2529
2530 /*-- Game start info cache: --*/
2531 int gs_gamenum;
2532 char gs_kind[MSG_SIZ];
2533 static char player1Name[128] = "";
2534 static char player2Name[128] = "";
2535 static char cont_seq[] = "\n\\   ";
2536 static int player1Rating = -1;
2537 static int player2Rating = -1;
2538 /*----------------------------*/
2539
2540 ColorClass curColor = ColorNormal;
2541 int suppressKibitz = 0;
2542
2543 // [HGM] seekgraph
2544 Boolean soughtPending = FALSE;
2545 Boolean seekGraphUp;
2546 #define MAX_SEEK_ADS 200
2547 #define SQUARE 0x80
2548 char *seekAdList[MAX_SEEK_ADS];
2549 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2550 float tcList[MAX_SEEK_ADS];
2551 char colorList[MAX_SEEK_ADS];
2552 int nrOfSeekAds = 0;
2553 int minRating = 1010, maxRating = 2800;
2554 int hMargin = 10, vMargin = 20, h, w;
2555 extern int squareSize, lineGap;
2556
2557 void
2558 PlotSeekAd (int i)
2559 {
2560         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2561         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2562         if(r < minRating+100 && r >=0 ) r = minRating+100;
2563         if(r > maxRating) r = maxRating;
2564         if(tc < 1.f) tc = 1.f;
2565         if(tc > 95.f) tc = 95.f;
2566         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2567         y = ((double)r - minRating)/(maxRating - minRating)
2568             * (h-vMargin-squareSize/8-1) + vMargin;
2569         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2570         if(strstr(seekAdList[i], " u ")) color = 1;
2571         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2572            !strstr(seekAdList[i], "bullet") &&
2573            !strstr(seekAdList[i], "blitz") &&
2574            !strstr(seekAdList[i], "standard") ) color = 2;
2575         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2576         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2577 }
2578
2579 void
2580 PlotSingleSeekAd (int i)
2581 {
2582         PlotSeekAd(i);
2583 }
2584
2585 void
2586 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2587 {
2588         char buf[MSG_SIZ], *ext = "";
2589         VariantClass v = StringToVariant(type);
2590         if(strstr(type, "wild")) {
2591             ext = type + 4; // append wild number
2592             if(v == VariantFischeRandom) type = "chess960"; else
2593             if(v == VariantLoadable) type = "setup"; else
2594             type = VariantName(v);
2595         }
2596         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2597         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2598             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2599             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2600             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2601             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2602             seekNrList[nrOfSeekAds] = nr;
2603             zList[nrOfSeekAds] = 0;
2604             seekAdList[nrOfSeekAds++] = StrSave(buf);
2605             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2606         }
2607 }
2608
2609 void
2610 EraseSeekDot (int i)
2611 {
2612     int x = xList[i], y = yList[i], d=squareSize/4, k;
2613     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2614     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2615     // now replot every dot that overlapped
2616     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2617         int xx = xList[k], yy = yList[k];
2618         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2619             DrawSeekDot(xx, yy, colorList[k]);
2620     }
2621 }
2622
2623 void
2624 RemoveSeekAd (int nr)
2625 {
2626         int i;
2627         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2628             EraseSeekDot(i);
2629             if(seekAdList[i]) free(seekAdList[i]);
2630             seekAdList[i] = seekAdList[--nrOfSeekAds];
2631             seekNrList[i] = seekNrList[nrOfSeekAds];
2632             ratingList[i] = ratingList[nrOfSeekAds];
2633             colorList[i]  = colorList[nrOfSeekAds];
2634             tcList[i] = tcList[nrOfSeekAds];
2635             xList[i]  = xList[nrOfSeekAds];
2636             yList[i]  = yList[nrOfSeekAds];
2637             zList[i]  = zList[nrOfSeekAds];
2638             seekAdList[nrOfSeekAds] = NULL;
2639             break;
2640         }
2641 }
2642
2643 Boolean
2644 MatchSoughtLine (char *line)
2645 {
2646     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2647     int nr, base, inc, u=0; char dummy;
2648
2649     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2650        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2651        (u=1) &&
2652        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2653         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2654         // match: compact and save the line
2655         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2656         return TRUE;
2657     }
2658     return FALSE;
2659 }
2660
2661 int
2662 DrawSeekGraph ()
2663 {
2664     int i;
2665     if(!seekGraphUp) return FALSE;
2666     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2667     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2668
2669     DrawSeekBackground(0, 0, w, h);
2670     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2671     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2672     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2673         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2674         yy = h-1-yy;
2675         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2676         if(i%500 == 0) {
2677             char buf[MSG_SIZ];
2678             snprintf(buf, MSG_SIZ, "%d", i);
2679             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2680         }
2681     }
2682     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2683     for(i=1; i<100; i+=(i<10?1:5)) {
2684         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2685         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2686         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2687             char buf[MSG_SIZ];
2688             snprintf(buf, MSG_SIZ, "%d", i);
2689             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2690         }
2691     }
2692     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2693     return TRUE;
2694 }
2695
2696 int
2697 SeekGraphClick (ClickType click, int x, int y, int moving)
2698 {
2699     static int lastDown = 0, displayed = 0, lastSecond;
2700     if(y < 0) return FALSE;
2701     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2702         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2703         if(!seekGraphUp) return FALSE;
2704         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2705         DrawPosition(TRUE, NULL);
2706         return TRUE;
2707     }
2708     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2709         if(click == Release || moving) return FALSE;
2710         nrOfSeekAds = 0;
2711         soughtPending = TRUE;
2712         SendToICS(ics_prefix);
2713         SendToICS("sought\n"); // should this be "sought all"?
2714     } else { // issue challenge based on clicked ad
2715         int dist = 10000; int i, closest = 0, second = 0;
2716         for(i=0; i<nrOfSeekAds; i++) {
2717             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2718             if(d < dist) { dist = d; closest = i; }
2719             second += (d - zList[i] < 120); // count in-range ads
2720             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2721         }
2722         if(dist < 120) {
2723             char buf[MSG_SIZ];
2724             second = (second > 1);
2725             if(displayed != closest || second != lastSecond) {
2726                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2727                 lastSecond = second; displayed = closest;
2728             }
2729             if(click == Press) {
2730                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2731                 lastDown = closest;
2732                 return TRUE;
2733             } // on press 'hit', only show info
2734             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2735             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2736             SendToICS(ics_prefix);
2737             SendToICS(buf);
2738             return TRUE; // let incoming board of started game pop down the graph
2739         } else if(click == Release) { // release 'miss' is ignored
2740             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2741             if(moving == 2) { // right up-click
2742                 nrOfSeekAds = 0; // refresh graph
2743                 soughtPending = TRUE;
2744                 SendToICS(ics_prefix);
2745                 SendToICS("sought\n"); // should this be "sought all"?
2746             }
2747             return TRUE;
2748         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2749         // press miss or release hit 'pop down' seek graph
2750         seekGraphUp = FALSE;
2751         DrawPosition(TRUE, NULL);
2752     }
2753     return TRUE;
2754 }
2755
2756 void
2757 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2758 {
2759 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2760 #define STARTED_NONE 0
2761 #define STARTED_MOVES 1
2762 #define STARTED_BOARD 2
2763 #define STARTED_OBSERVE 3
2764 #define STARTED_HOLDINGS 4
2765 #define STARTED_CHATTER 5
2766 #define STARTED_COMMENT 6
2767 #define STARTED_MOVES_NOHIDE 7
2768
2769     static int started = STARTED_NONE;
2770     static char parse[20000];
2771     static int parse_pos = 0;
2772     static char buf[BUF_SIZE + 1];
2773     static int firstTime = TRUE, intfSet = FALSE;
2774     static ColorClass prevColor = ColorNormal;
2775     static int savingComment = FALSE;
2776     static int cmatch = 0; // continuation sequence match
2777     char *bp;
2778     char str[MSG_SIZ];
2779     int i, oldi;
2780     int buf_len;
2781     int next_out;
2782     int tkind;
2783     int backup;    /* [DM] For zippy color lines */
2784     char *p;
2785     char talker[MSG_SIZ]; // [HGM] chat
2786     int channel;
2787
2788     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2789
2790     if (appData.debugMode) {
2791       if (!error) {
2792         fprintf(debugFP, "<ICS: ");
2793         show_bytes(debugFP, data, count);
2794         fprintf(debugFP, "\n");
2795       }
2796     }
2797
2798     if (appData.debugMode) { int f = forwardMostMove;
2799         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2800                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2801                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2802     }
2803     if (count > 0) {
2804         /* If last read ended with a partial line that we couldn't parse,
2805            prepend it to the new read and try again. */
2806         if (leftover_len > 0) {
2807             for (i=0; i<leftover_len; i++)
2808               buf[i] = buf[leftover_start + i];
2809         }
2810
2811     /* copy new characters into the buffer */
2812     bp = buf + leftover_len;
2813     buf_len=leftover_len;
2814     for (i=0; i<count; i++)
2815     {
2816         // ignore these
2817         if (data[i] == '\r')
2818             continue;
2819
2820         // join lines split by ICS?
2821         if (!appData.noJoin)
2822         {
2823             /*
2824                 Joining just consists of finding matches against the
2825                 continuation sequence, and discarding that sequence
2826                 if found instead of copying it.  So, until a match
2827                 fails, there's nothing to do since it might be the
2828                 complete sequence, and thus, something we don't want
2829                 copied.
2830             */
2831             if (data[i] == cont_seq[cmatch])
2832             {
2833                 cmatch++;
2834                 if (cmatch == strlen(cont_seq))
2835                 {
2836                     cmatch = 0; // complete match.  just reset the counter
2837
2838                     /*
2839                         it's possible for the ICS to not include the space
2840                         at the end of the last word, making our [correct]
2841                         join operation fuse two separate words.  the server
2842                         does this when the space occurs at the width setting.
2843                     */
2844                     if (!buf_len || buf[buf_len-1] != ' ')
2845                     {
2846                         *bp++ = ' ';
2847                         buf_len++;
2848                     }
2849                 }
2850                 continue;
2851             }
2852             else if (cmatch)
2853             {
2854                 /*
2855                     match failed, so we have to copy what matched before
2856                     falling through and copying this character.  In reality,
2857                     this will only ever be just the newline character, but
2858                     it doesn't hurt to be precise.
2859                 */
2860                 strncpy(bp, cont_seq, cmatch);
2861                 bp += cmatch;
2862                 buf_len += cmatch;
2863                 cmatch = 0;
2864             }
2865         }
2866
2867         // copy this char
2868         *bp++ = data[i];
2869         buf_len++;
2870     }
2871
2872         buf[buf_len] = NULLCHAR;
2873 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2874         next_out = 0;
2875         leftover_start = 0;
2876
2877         i = 0;
2878         while (i < buf_len) {
2879             /* Deal with part of the TELNET option negotiation
2880                protocol.  We refuse to do anything beyond the
2881                defaults, except that we allow the WILL ECHO option,
2882                which ICS uses to turn off password echoing when we are
2883                directly connected to it.  We reject this option
2884                if localLineEditing mode is on (always on in xboard)
2885                and we are talking to port 23, which might be a real
2886                telnet server that will try to keep WILL ECHO on permanently.
2887              */
2888             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2889                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2890                 unsigned char option;
2891                 oldi = i;
2892                 switch ((unsigned char) buf[++i]) {
2893                   case TN_WILL:
2894                     if (appData.debugMode)
2895                       fprintf(debugFP, "\n<WILL ");
2896                     switch (option = (unsigned char) buf[++i]) {
2897                       case TN_ECHO:
2898                         if (appData.debugMode)
2899                           fprintf(debugFP, "ECHO ");
2900                         /* Reply only if this is a change, according
2901                            to the protocol rules. */
2902                         if (remoteEchoOption) break;
2903                         if (appData.localLineEditing &&
2904                             atoi(appData.icsPort) == TN_PORT) {
2905                             TelnetRequest(TN_DONT, TN_ECHO);
2906                         } else {
2907                             EchoOff();
2908                             TelnetRequest(TN_DO, TN_ECHO);
2909                             remoteEchoOption = TRUE;
2910                         }
2911                         break;
2912                       default:
2913                         if (appData.debugMode)
2914                           fprintf(debugFP, "%d ", option);
2915                         /* Whatever this is, we don't want it. */
2916                         TelnetRequest(TN_DONT, option);
2917                         break;
2918                     }
2919                     break;
2920                   case TN_WONT:
2921                     if (appData.debugMode)
2922                       fprintf(debugFP, "\n<WONT ");
2923                     switch (option = (unsigned char) buf[++i]) {
2924                       case TN_ECHO:
2925                         if (appData.debugMode)
2926                           fprintf(debugFP, "ECHO ");
2927                         /* Reply only if this is a change, according
2928                            to the protocol rules. */
2929                         if (!remoteEchoOption) break;
2930                         EchoOn();
2931                         TelnetRequest(TN_DONT, TN_ECHO);
2932                         remoteEchoOption = FALSE;
2933                         break;
2934                       default:
2935                         if (appData.debugMode)
2936                           fprintf(debugFP, "%d ", (unsigned char) option);
2937                         /* Whatever this is, it must already be turned
2938                            off, because we never agree to turn on
2939                            anything non-default, so according to the
2940                            protocol rules, we don't reply. */
2941                         break;
2942                     }
2943                     break;
2944                   case TN_DO:
2945                     if (appData.debugMode)
2946                       fprintf(debugFP, "\n<DO ");
2947                     switch (option = (unsigned char) buf[++i]) {
2948                       default:
2949                         /* Whatever this is, we refuse to do it. */
2950                         if (appData.debugMode)
2951                           fprintf(debugFP, "%d ", option);
2952                         TelnetRequest(TN_WONT, option);
2953                         break;
2954                     }
2955                     break;
2956                   case TN_DONT:
2957                     if (appData.debugMode)
2958                       fprintf(debugFP, "\n<DONT ");
2959                     switch (option = (unsigned char) buf[++i]) {
2960                       default:
2961                         if (appData.debugMode)
2962                           fprintf(debugFP, "%d ", option);
2963                         /* Whatever this is, we are already not doing
2964                            it, because we never agree to do anything
2965                            non-default, so according to the protocol
2966                            rules, we don't reply. */
2967                         break;
2968                     }
2969                     break;
2970                   case TN_IAC:
2971                     if (appData.debugMode)
2972                       fprintf(debugFP, "\n<IAC ");
2973                     /* Doubled IAC; pass it through */
2974                     i--;
2975                     break;
2976                   default:
2977                     if (appData.debugMode)
2978                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2979                     /* Drop all other telnet commands on the floor */
2980                     break;
2981                 }
2982                 if (oldi > next_out)
2983                   SendToPlayer(&buf[next_out], oldi - next_out);
2984                 if (++i > next_out)
2985                   next_out = i;
2986                 continue;
2987             }
2988
2989             /* OK, this at least will *usually* work */
2990             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2991                 loggedOn = TRUE;
2992             }
2993
2994             if (loggedOn && !intfSet) {
2995                 if (ics_type == ICS_ICC) {
2996                   snprintf(str, MSG_SIZ,
2997                           "/set-quietly interface %s\n/set-quietly style 12\n",
2998                           programVersion);
2999                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3000                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3001                 } else if (ics_type == ICS_CHESSNET) {
3002                   snprintf(str, MSG_SIZ, "/style 12\n");
3003                 } else {
3004                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3005                   strcat(str, programVersion);
3006                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3007                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3008                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3009 #ifdef WIN32
3010                   strcat(str, "$iset nohighlight 1\n");
3011 #endif
3012                   strcat(str, "$iset lock 1\n$style 12\n");
3013                 }
3014                 SendToICS(str);
3015                 NotifyFrontendLogin();
3016                 intfSet = TRUE;
3017             }
3018
3019             if (started == STARTED_COMMENT) {
3020                 /* Accumulate characters in comment */
3021                 parse[parse_pos++] = buf[i];
3022                 if (buf[i] == '\n') {
3023                     parse[parse_pos] = NULLCHAR;
3024                     if(chattingPartner>=0) {
3025                         char mess[MSG_SIZ];
3026                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3027                         OutputChatMessage(chattingPartner, mess);
3028                         chattingPartner = -1;
3029                         next_out = i+1; // [HGM] suppress printing in ICS window
3030                     } else
3031                     if(!suppressKibitz) // [HGM] kibitz
3032                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3033                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3034                         int nrDigit = 0, nrAlph = 0, j;
3035                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3036                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3037                         parse[parse_pos] = NULLCHAR;
3038                         // try to be smart: if it does not look like search info, it should go to
3039                         // ICS interaction window after all, not to engine-output window.
3040                         for(j=0; j<parse_pos; j++) { // count letters and digits
3041                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3042                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3043                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3044                         }
3045                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3046                             int depth=0; float score;
3047                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3048                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3049                                 pvInfoList[forwardMostMove-1].depth = depth;
3050                                 pvInfoList[forwardMostMove-1].score = 100*score;
3051                             }
3052                             OutputKibitz(suppressKibitz, parse);
3053                         } else {
3054                             char tmp[MSG_SIZ];
3055                             if(gameMode == IcsObserving) // restore original ICS messages
3056                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3057                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3058                             else
3059                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3060                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3061                             SendToPlayer(tmp, strlen(tmp));
3062                         }
3063                         next_out = i+1; // [HGM] suppress printing in ICS window
3064                     }
3065                     started = STARTED_NONE;
3066                 } else {
3067                     /* Don't match patterns against characters in comment */
3068                     i++;
3069                     continue;
3070                 }
3071             }
3072             if (started == STARTED_CHATTER) {
3073                 if (buf[i] != '\n') {
3074                     /* Don't match patterns against characters in chatter */
3075                     i++;
3076                     continue;
3077                 }
3078                 started = STARTED_NONE;
3079                 if(suppressKibitz) next_out = i+1;
3080             }
3081
3082             /* Kludge to deal with rcmd protocol */
3083             if (firstTime && looking_at(buf, &i, "\001*")) {
3084                 DisplayFatalError(&buf[1], 0, 1);
3085                 continue;
3086             } else {
3087                 firstTime = FALSE;
3088             }
3089
3090             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3091                 ics_type = ICS_ICC;
3092                 ics_prefix = "/";
3093                 if (appData.debugMode)
3094                   fprintf(debugFP, "ics_type %d\n", ics_type);
3095                 continue;
3096             }
3097             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3098                 ics_type = ICS_FICS;
3099                 ics_prefix = "$";
3100                 if (appData.debugMode)
3101                   fprintf(debugFP, "ics_type %d\n", ics_type);
3102                 continue;
3103             }
3104             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3105                 ics_type = ICS_CHESSNET;
3106                 ics_prefix = "/";
3107                 if (appData.debugMode)
3108                   fprintf(debugFP, "ics_type %d\n", ics_type);
3109                 continue;
3110             }
3111
3112             if (!loggedOn &&
3113                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3114                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3115                  looking_at(buf, &i, "will be \"*\""))) {
3116               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3117               continue;
3118             }
3119
3120             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3121               char buf[MSG_SIZ];
3122               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3123               DisplayIcsInteractionTitle(buf);
3124               have_set_title = TRUE;
3125             }
3126
3127             /* skip finger notes */
3128             if (started == STARTED_NONE &&
3129                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3130                  (buf[i] == '1' && buf[i+1] == '0')) &&
3131                 buf[i+2] == ':' && buf[i+3] == ' ') {
3132               started = STARTED_CHATTER;
3133               i += 3;
3134               continue;
3135             }
3136
3137             oldi = i;
3138             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3139             if(appData.seekGraph) {
3140                 if(soughtPending && MatchSoughtLine(buf+i)) {
3141                     i = strstr(buf+i, "rated") - buf;
3142                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3143                     next_out = leftover_start = i;
3144                     started = STARTED_CHATTER;
3145                     suppressKibitz = TRUE;
3146                     continue;
3147                 }
3148                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3149                         && looking_at(buf, &i, "* ads displayed")) {
3150                     soughtPending = FALSE;
3151                     seekGraphUp = TRUE;
3152                     DrawSeekGraph();
3153                     continue;
3154                 }
3155                 if(appData.autoRefresh) {
3156                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3157                         int s = (ics_type == ICS_ICC); // ICC format differs
3158                         if(seekGraphUp)
3159                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3160                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3161                         looking_at(buf, &i, "*% "); // eat prompt
3162                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3163                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3164                         next_out = i; // suppress
3165                         continue;
3166                     }
3167                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3168                         char *p = star_match[0];
3169                         while(*p) {
3170                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3171                             while(*p && *p++ != ' '); // next
3172                         }
3173                         looking_at(buf, &i, "*% "); // eat prompt
3174                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3175                         next_out = i;
3176                         continue;
3177                     }
3178                 }
3179             }
3180
3181             /* skip formula vars */
3182             if (started == STARTED_NONE &&
3183                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3184               started = STARTED_CHATTER;
3185               i += 3;
3186               continue;
3187             }
3188
3189             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3190             if (appData.autoKibitz && started == STARTED_NONE &&
3191                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3192                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3193                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3194                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3195                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3196                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3197                         suppressKibitz = TRUE;
3198                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3199                         next_out = i;
3200                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3201                                 && (gameMode == IcsPlayingWhite)) ||
3202                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3203                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3204                             started = STARTED_CHATTER; // own kibitz we simply discard
3205                         else {
3206                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3207                             parse_pos = 0; parse[0] = NULLCHAR;
3208                             savingComment = TRUE;
3209                             suppressKibitz = gameMode != IcsObserving ? 2 :
3210                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3211                         }
3212                         continue;
3213                 } else
3214                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3215                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3216                          && atoi(star_match[0])) {
3217                     // suppress the acknowledgements of our own autoKibitz
3218                     char *p;
3219                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3221                     SendToPlayer(star_match[0], strlen(star_match[0]));
3222                     if(looking_at(buf, &i, "*% ")) // eat prompt
3223                         suppressKibitz = FALSE;
3224                     next_out = i;
3225                     continue;
3226                 }
3227             } // [HGM] kibitz: end of patch
3228
3229             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3230
3231             // [HGM] chat: intercept tells by users for which we have an open chat window
3232             channel = -1;
3233             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3234                                            looking_at(buf, &i, "* whispers:") ||
3235                                            looking_at(buf, &i, "* kibitzes:") ||
3236                                            looking_at(buf, &i, "* shouts:") ||
3237                                            looking_at(buf, &i, "* c-shouts:") ||
3238                                            looking_at(buf, &i, "--> * ") ||
3239                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3240                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3241                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3242                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3243                 int p;
3244                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3245                 chattingPartner = -1;
3246
3247                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3248                 for(p=0; p<MAX_CHAT; p++) {
3249                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3250                     talker[0] = '['; strcat(talker, "] ");
3251                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3252                     chattingPartner = p; break;
3253                     }
3254                 } else
3255                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3256                 for(p=0; p<MAX_CHAT; p++) {
3257                     if(!strcmp("kibitzes", chatPartner[p])) {
3258                         talker[0] = '['; strcat(talker, "] ");
3259                         chattingPartner = p; break;
3260                     }
3261                 } else
3262                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3263                 for(p=0; p<MAX_CHAT; p++) {
3264                     if(!strcmp("whispers", chatPartner[p])) {
3265                         talker[0] = '['; strcat(talker, "] ");
3266                         chattingPartner = p; break;
3267                     }
3268                 } else
3269                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3270                   if(buf[i-8] == '-' && buf[i-3] == 't')
3271                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3272                     if(!strcmp("c-shouts", chatPartner[p])) {
3273                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3274                         chattingPartner = p; break;
3275                     }
3276                   }
3277                   if(chattingPartner < 0)
3278                   for(p=0; p<MAX_CHAT; p++) {
3279                     if(!strcmp("shouts", chatPartner[p])) {
3280                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3281                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3282                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3283                         chattingPartner = p; break;
3284                     }
3285                   }
3286                 }
3287                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3288                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3289                     talker[0] = 0; Colorize(ColorTell, FALSE);
3290                     chattingPartner = p; break;
3291                 }
3292                 if(chattingPartner<0) i = oldi; else {
3293                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3294                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3295                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3296                     started = STARTED_COMMENT;
3297                     parse_pos = 0; parse[0] = NULLCHAR;
3298                     savingComment = 3 + chattingPartner; // counts as TRUE
3299                     suppressKibitz = TRUE;
3300                     continue;
3301                 }
3302             } // [HGM] chat: end of patch
3303
3304           backup = i;
3305             if (appData.zippyTalk || appData.zippyPlay) {
3306                 /* [DM] Backup address for color zippy lines */
3307 #if ZIPPY
3308                if (loggedOn == TRUE)
3309                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3310                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3311 #endif
3312             } // [DM] 'else { ' deleted
3313                 if (
3314                     /* Regular tells and says */
3315                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3316                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3317                     looking_at(buf, &i, "* says: ") ||
3318                     /* Don't color "message" or "messages" output */
3319                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3320                     looking_at(buf, &i, "*. * at *:*: ") ||
3321                     looking_at(buf, &i, "--* (*:*): ") ||
3322                     /* Message notifications (same color as tells) */
3323                     looking_at(buf, &i, "* has left a message ") ||
3324                     looking_at(buf, &i, "* just sent you a message:\n") ||
3325                     /* Whispers and kibitzes */
3326                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3327                     looking_at(buf, &i, "* kibitzes: ") ||
3328                     /* Channel tells */
3329                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3330
3331                   if (tkind == 1 && strchr(star_match[0], ':')) {
3332                       /* Avoid "tells you:" spoofs in channels */
3333                      tkind = 3;
3334                   }
3335                   if (star_match[0][0] == NULLCHAR ||
3336                       strchr(star_match[0], ' ') ||
3337                       (tkind == 3 && strchr(star_match[1], ' '))) {
3338                     /* Reject bogus matches */
3339                     i = oldi;
3340                   } else {
3341                     if (appData.colorize) {
3342                       if (oldi > next_out) {
3343                         SendToPlayer(&buf[next_out], oldi - next_out);
3344                         next_out = oldi;
3345                       }
3346                       switch (tkind) {
3347                       case 1:
3348                         Colorize(ColorTell, FALSE);
3349                         curColor = ColorTell;
3350                         break;
3351                       case 2:
3352                         Colorize(ColorKibitz, FALSE);
3353                         curColor = ColorKibitz;
3354                         break;
3355                       case 3:
3356                         p = strrchr(star_match[1], '(');
3357                         if (p == NULL) {
3358                           p = star_match[1];
3359                         } else {
3360                           p++;
3361                         }
3362                         if (atoi(p) == 1) {
3363                           Colorize(ColorChannel1, FALSE);
3364                           curColor = ColorChannel1;
3365                         } else {
3366                           Colorize(ColorChannel, FALSE);
3367                           curColor = ColorChannel;
3368                         }
3369                         break;
3370                       case 5:
3371                         curColor = ColorNormal;
3372                         break;
3373                       }
3374                     }
3375                     if (started == STARTED_NONE && appData.autoComment &&
3376                         (gameMode == IcsObserving ||
3377                          gameMode == IcsPlayingWhite ||
3378                          gameMode == IcsPlayingBlack)) {
3379                       parse_pos = i - oldi;
3380                       memcpy(parse, &buf[oldi], parse_pos);
3381                       parse[parse_pos] = NULLCHAR;
3382                       started = STARTED_COMMENT;
3383                       savingComment = TRUE;
3384                     } else {
3385                       started = STARTED_CHATTER;
3386                       savingComment = FALSE;
3387                     }
3388                     loggedOn = TRUE;
3389                     continue;
3390                   }
3391                 }
3392
3393                 if (looking_at(buf, &i, "* s-shouts: ") ||
3394                     looking_at(buf, &i, "* c-shouts: ")) {
3395                     if (appData.colorize) {
3396                         if (oldi > next_out) {
3397                             SendToPlayer(&buf[next_out], oldi - next_out);
3398                             next_out = oldi;
3399                         }
3400                         Colorize(ColorSShout, FALSE);
3401                         curColor = ColorSShout;
3402                     }
3403                     loggedOn = TRUE;
3404                     started = STARTED_CHATTER;
3405                     continue;
3406                 }
3407
3408                 if (looking_at(buf, &i, "--->")) {
3409                     loggedOn = TRUE;
3410                     continue;
3411                 }
3412
3413                 if (looking_at(buf, &i, "* shouts: ") ||
3414                     looking_at(buf, &i, "--> ")) {
3415                     if (appData.colorize) {
3416                         if (oldi > next_out) {
3417                             SendToPlayer(&buf[next_out], oldi - next_out);
3418                             next_out = oldi;
3419                         }
3420                         Colorize(ColorShout, FALSE);
3421                         curColor = ColorShout;
3422                     }
3423                     loggedOn = TRUE;
3424                     started = STARTED_CHATTER;
3425                     continue;
3426                 }
3427
3428                 if (looking_at( buf, &i, "Challenge:")) {
3429                     if (appData.colorize) {
3430                         if (oldi > next_out) {
3431                             SendToPlayer(&buf[next_out], oldi - next_out);
3432                             next_out = oldi;
3433                         }
3434                         Colorize(ColorChallenge, FALSE);
3435                         curColor = ColorChallenge;
3436                     }
3437                     loggedOn = TRUE;
3438                     continue;
3439                 }
3440
3441                 if (looking_at(buf, &i, "* offers you") ||
3442                     looking_at(buf, &i, "* offers to be") ||
3443                     looking_at(buf, &i, "* would like to") ||
3444                     looking_at(buf, &i, "* requests to") ||
3445                     looking_at(buf, &i, "Your opponent offers") ||
3446                     looking_at(buf, &i, "Your opponent requests")) {
3447
3448                     if (appData.colorize) {
3449                         if (oldi > next_out) {
3450                             SendToPlayer(&buf[next_out], oldi - next_out);
3451                             next_out = oldi;
3452                         }
3453                         Colorize(ColorRequest, FALSE);
3454                         curColor = ColorRequest;
3455                     }
3456                     continue;
3457                 }
3458
3459                 if (looking_at(buf, &i, "* (*) seeking")) {
3460                     if (appData.colorize) {
3461                         if (oldi > next_out) {
3462                             SendToPlayer(&buf[next_out], oldi - next_out);
3463                             next_out = oldi;
3464                         }
3465                         Colorize(ColorSeek, FALSE);
3466                         curColor = ColorSeek;
3467                     }
3468                     continue;
3469             }
3470
3471           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3472
3473             if (looking_at(buf, &i, "\\   ")) {
3474                 if (prevColor != ColorNormal) {
3475                     if (oldi > next_out) {
3476                         SendToPlayer(&buf[next_out], oldi - next_out);
3477                         next_out = oldi;
3478                     }
3479                     Colorize(prevColor, TRUE);
3480                     curColor = prevColor;
3481                 }
3482                 if (savingComment) {
3483                     parse_pos = i - oldi;
3484                     memcpy(parse, &buf[oldi], parse_pos);
3485                     parse[parse_pos] = NULLCHAR;
3486                     started = STARTED_COMMENT;
3487                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3488                         chattingPartner = savingComment - 3; // kludge to remember the box
3489                 } else {
3490                     started = STARTED_CHATTER;
3491                 }
3492                 continue;
3493             }
3494
3495             if (looking_at(buf, &i, "Black Strength :") ||
3496                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3497                 looking_at(buf, &i, "<10>") ||
3498                 looking_at(buf, &i, "#@#")) {
3499                 /* Wrong board style */
3500                 loggedOn = TRUE;
3501                 SendToICS(ics_prefix);
3502                 SendToICS("set style 12\n");
3503                 SendToICS(ics_prefix);
3504                 SendToICS("refresh\n");
3505                 continue;
3506             }
3507
3508             if (looking_at(buf, &i, "login:")) {
3509               if (!have_sent_ICS_logon) {
3510                 if(ICSInitScript())
3511                   have_sent_ICS_logon = 1;
3512                 else // no init script was found
3513                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3514               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3515                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3516               }
3517                 continue;
3518             }
3519
3520             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3521                 (looking_at(buf, &i, "\n<12> ") ||
3522                  looking_at(buf, &i, "<12> "))) {
3523                 loggedOn = TRUE;
3524                 if (oldi > next_out) {
3525                     SendToPlayer(&buf[next_out], oldi - next_out);
3526                 }
3527                 next_out = i;
3528                 started = STARTED_BOARD;
3529                 parse_pos = 0;
3530                 continue;
3531             }
3532
3533             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3534                 looking_at(buf, &i, "<b1> ")) {
3535                 if (oldi > next_out) {
3536                     SendToPlayer(&buf[next_out], oldi - next_out);
3537                 }
3538                 next_out = i;
3539                 started = STARTED_HOLDINGS;
3540                 parse_pos = 0;
3541                 continue;
3542             }
3543
3544             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3545                 loggedOn = TRUE;
3546                 /* Header for a move list -- first line */
3547
3548                 switch (ics_getting_history) {
3549                   case H_FALSE:
3550                     switch (gameMode) {
3551                       case IcsIdle:
3552                       case BeginningOfGame:
3553                         /* User typed "moves" or "oldmoves" while we
3554                            were idle.  Pretend we asked for these
3555                            moves and soak them up so user can step
3556                            through them and/or save them.
3557                            */
3558                         Reset(FALSE, TRUE);
3559                         gameMode = IcsObserving;
3560                         ModeHighlight();
3561                         ics_gamenum = -1;
3562                         ics_getting_history = H_GOT_UNREQ_HEADER;
3563                         break;
3564                       case EditGame: /*?*/
3565                       case EditPosition: /*?*/
3566                         /* Should above feature work in these modes too? */
3567                         /* For now it doesn't */
3568                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3569                         break;
3570                       default:
3571                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3572                         break;
3573                     }
3574                     break;
3575                   case H_REQUESTED:
3576                     /* Is this the right one? */
3577                     if (gameInfo.white && gameInfo.black &&
3578                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3579                         strcmp(gameInfo.black, star_match[2]) == 0) {
3580                         /* All is well */
3581                         ics_getting_history = H_GOT_REQ_HEADER;
3582                     }
3583                     break;
3584                   case H_GOT_REQ_HEADER:
3585                   case H_GOT_UNREQ_HEADER:
3586                   case H_GOT_UNWANTED_HEADER:
3587                   case H_GETTING_MOVES:
3588                     /* Should not happen */
3589                     DisplayError(_("Error gathering move list: two headers"), 0);
3590                     ics_getting_history = H_FALSE;
3591                     break;
3592                 }
3593
3594                 /* Save player ratings into gameInfo if needed */
3595                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3596                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3597                     (gameInfo.whiteRating == -1 ||
3598                      gameInfo.blackRating == -1)) {
3599
3600                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3601                     gameInfo.blackRating = string_to_rating(star_match[3]);
3602                     if (appData.debugMode)
3603                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3604                               gameInfo.whiteRating, gameInfo.blackRating);
3605                 }
3606                 continue;
3607             }
3608
3609             if (looking_at(buf, &i,
3610               "* * match, initial time: * minute*, increment: * second")) {
3611                 /* Header for a move list -- second line */
3612                 /* Initial board will follow if this is a wild game */
3613                 if (gameInfo.event != NULL) free(gameInfo.event);
3614                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3615                 gameInfo.event = StrSave(str);
3616                 /* [HGM] we switched variant. Translate boards if needed. */
3617                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3618                 continue;
3619             }
3620
3621             if (looking_at(buf, &i, "Move  ")) {
3622                 /* Beginning of a move list */
3623                 switch (ics_getting_history) {
3624                   case H_FALSE:
3625                     /* Normally should not happen */
3626                     /* Maybe user hit reset while we were parsing */
3627                     break;
3628                   case H_REQUESTED:
3629                     /* Happens if we are ignoring a move list that is not
3630                      * the one we just requested.  Common if the user
3631                      * tries to observe two games without turning off
3632                      * getMoveList */
3633                     break;
3634                   case H_GETTING_MOVES:
3635                     /* Should not happen */
3636                     DisplayError(_("Error gathering move list: nested"), 0);
3637                     ics_getting_history = H_FALSE;
3638                     break;
3639                   case H_GOT_REQ_HEADER:
3640                     ics_getting_history = H_GETTING_MOVES;
3641                     started = STARTED_MOVES;
3642                     parse_pos = 0;
3643                     if (oldi > next_out) {
3644                         SendToPlayer(&buf[next_out], oldi - next_out);
3645                     }
3646                     break;
3647                   case H_GOT_UNREQ_HEADER:
3648                     ics_getting_history = H_GETTING_MOVES;
3649                     started = STARTED_MOVES_NOHIDE;
3650                     parse_pos = 0;
3651                     break;
3652                   case H_GOT_UNWANTED_HEADER:
3653                     ics_getting_history = H_FALSE;
3654                     break;
3655                 }
3656                 continue;
3657             }
3658
3659             if (looking_at(buf, &i, "% ") ||
3660                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3661                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3662                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3663                     soughtPending = FALSE;
3664                     seekGraphUp = TRUE;
3665                     DrawSeekGraph();
3666                 }
3667                 if(suppressKibitz) next_out = i;
3668                 savingComment = FALSE;
3669                 suppressKibitz = 0;
3670                 switch (started) {
3671                   case STARTED_MOVES:
3672                   case STARTED_MOVES_NOHIDE:
3673                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3674                     parse[parse_pos + i - oldi] = NULLCHAR;
3675                     ParseGameHistory(parse);
3676 #if ZIPPY
3677                     if (appData.zippyPlay && first.initDone) {
3678                         FeedMovesToProgram(&first, forwardMostMove);
3679                         if (gameMode == IcsPlayingWhite) {
3680                             if (WhiteOnMove(forwardMostMove)) {
3681                                 if (first.sendTime) {
3682                                   if (first.useColors) {
3683                                     SendToProgram("black\n", &first);
3684                                   }
3685                                   SendTimeRemaining(&first, TRUE);
3686                                 }
3687                                 if (first.useColors) {
3688                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3689                                 }
3690                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3691                                 first.maybeThinking = TRUE;
3692                             } else {
3693                                 if (first.usePlayother) {
3694                                   if (first.sendTime) {
3695                                     SendTimeRemaining(&first, TRUE);
3696                                   }
3697                                   SendToProgram("playother\n", &first);
3698                                   firstMove = FALSE;
3699                                 } else {
3700                                   firstMove = TRUE;
3701                                 }
3702                             }
3703                         } else if (gameMode == IcsPlayingBlack) {
3704                             if (!WhiteOnMove(forwardMostMove)) {
3705                                 if (first.sendTime) {
3706                                   if (first.useColors) {
3707                                     SendToProgram("white\n", &first);
3708                                   }
3709                                   SendTimeRemaining(&first, FALSE);
3710                                 }
3711                                 if (first.useColors) {
3712                                   SendToProgram("black\n", &first);
3713                                 }
3714                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3715                                 first.maybeThinking = TRUE;
3716                             } else {
3717                                 if (first.usePlayother) {
3718                                   if (first.sendTime) {
3719                                     SendTimeRemaining(&first, FALSE);
3720                                   }
3721                                   SendToProgram("playother\n", &first);
3722                                   firstMove = FALSE;
3723                                 } else {
3724                                   firstMove = TRUE;
3725                                 }
3726                             }
3727                         }
3728                     }
3729 #endif
3730                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3731                         /* Moves came from oldmoves or moves command
3732                            while we weren't doing anything else.
3733                            */
3734                         currentMove = forwardMostMove;
3735                         ClearHighlights();/*!!could figure this out*/
3736                         flipView = appData.flipView;
3737                         DrawPosition(TRUE, boards[currentMove]);
3738                         DisplayBothClocks();
3739                         snprintf(str, MSG_SIZ, "%s %s %s",
3740                                 gameInfo.white, _("vs."),  gameInfo.black);
3741                         DisplayTitle(str);
3742                         gameMode = IcsIdle;
3743                     } else {
3744                         /* Moves were history of an active game */
3745                         if (gameInfo.resultDetails != NULL) {
3746                             free(gameInfo.resultDetails);
3747                             gameInfo.resultDetails = NULL;
3748                         }
3749                     }
3750                     HistorySet(parseList, backwardMostMove,
3751                                forwardMostMove, currentMove-1);
3752                     DisplayMove(currentMove - 1);
3753                     if (started == STARTED_MOVES) next_out = i;
3754                     started = STARTED_NONE;
3755                     ics_getting_history = H_FALSE;
3756                     break;
3757
3758                   case STARTED_OBSERVE:
3759                     started = STARTED_NONE;
3760                     SendToICS(ics_prefix);
3761                     SendToICS("refresh\n");
3762                     break;
3763
3764                   default:
3765                     break;
3766                 }
3767                 if(bookHit) { // [HGM] book: simulate book reply
3768                     static char bookMove[MSG_SIZ]; // a bit generous?
3769
3770                     programStats.nodes = programStats.depth = programStats.time =
3771                     programStats.score = programStats.got_only_move = 0;
3772                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3773
3774                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3775                     strcat(bookMove, bookHit);
3776                     HandleMachineMove(bookMove, &first);
3777                 }
3778                 continue;
3779             }
3780
3781             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3782                  started == STARTED_HOLDINGS ||
3783                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3784                 /* Accumulate characters in move list or board */
3785                 parse[parse_pos++] = buf[i];
3786             }
3787
3788             /* Start of game messages.  Mostly we detect start of game
3789                when the first board image arrives.  On some versions
3790                of the ICS, though, we need to do a "refresh" after starting
3791                to observe in order to get the current board right away. */
3792             if (looking_at(buf, &i, "Adding game * to observation list")) {
3793                 started = STARTED_OBSERVE;
3794                 continue;
3795             }
3796
3797             /* Handle auto-observe */
3798             if (appData.autoObserve &&
3799                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3800                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3801                 char *player;
3802                 /* Choose the player that was highlighted, if any. */
3803                 if (star_match[0][0] == '\033' ||
3804                     star_match[1][0] != '\033') {
3805                     player = star_match[0];
3806                 } else {
3807                     player = star_match[2];
3808                 }
3809                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3810                         ics_prefix, StripHighlightAndTitle(player));
3811                 SendToICS(str);
3812
3813                 /* Save ratings from notify string */
3814                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3815                 player1Rating = string_to_rating(star_match[1]);
3816                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3817                 player2Rating = string_to_rating(star_match[3]);
3818
3819                 if (appData.debugMode)
3820                   fprintf(debugFP,
3821                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3822                           player1Name, player1Rating,
3823                           player2Name, player2Rating);
3824
3825                 continue;
3826             }
3827
3828             /* Deal with automatic examine mode after a game,
3829                and with IcsObserving -> IcsExamining transition */
3830             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3831                 looking_at(buf, &i, "has made you an examiner of game *")) {
3832
3833                 int gamenum = atoi(star_match[0]);
3834                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3835                     gamenum == ics_gamenum) {
3836                     /* We were already playing or observing this game;
3837                        no need to refetch history */
3838                     gameMode = IcsExamining;
3839                     if (pausing) {
3840                         pauseExamForwardMostMove = forwardMostMove;
3841                     } else if (currentMove < forwardMostMove) {
3842                         ForwardInner(forwardMostMove);
3843                     }
3844                 } else {
3845                     /* I don't think this case really can happen */
3846                     SendToICS(ics_prefix);
3847                     SendToICS("refresh\n");
3848                 }
3849                 continue;
3850             }
3851
3852             /* Error messages */
3853 //          if (ics_user_moved) {
3854             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3855                 if (looking_at(buf, &i, "Illegal move") ||
3856                     looking_at(buf, &i, "Not a legal move") ||
3857                     looking_at(buf, &i, "Your king is in check") ||
3858                     looking_at(buf, &i, "It isn't your turn") ||
3859                     looking_at(buf, &i, "It is not your move")) {
3860                     /* Illegal move */
3861                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3862                         currentMove = forwardMostMove-1;
3863                         DisplayMove(currentMove - 1); /* before DMError */
3864                         DrawPosition(FALSE, boards[currentMove]);
3865                         SwitchClocks(forwardMostMove-1); // [HGM] race
3866                         DisplayBothClocks();
3867                     }
3868                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3869                     ics_user_moved = 0;
3870                     continue;
3871                 }
3872             }
3873
3874             if (looking_at(buf, &i, "still have time") ||
3875                 looking_at(buf, &i, "not out of time") ||
3876                 looking_at(buf, &i, "either player is out of time") ||
3877                 looking_at(buf, &i, "has timeseal; checking")) {
3878                 /* We must have called his flag a little too soon */
3879                 whiteFlag = blackFlag = FALSE;
3880                 continue;
3881             }
3882
3883             if (looking_at(buf, &i, "added * seconds to") ||
3884                 looking_at(buf, &i, "seconds were added to")) {
3885                 /* Update the clocks */
3886                 SendToICS(ics_prefix);
3887                 SendToICS("refresh\n");
3888                 continue;
3889             }
3890
3891             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3892                 ics_clock_paused = TRUE;
3893                 StopClocks();
3894                 continue;
3895             }
3896
3897             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3898                 ics_clock_paused = FALSE;
3899                 StartClocks();
3900                 continue;
3901             }
3902
3903             /* Grab player ratings from the Creating: message.
3904                Note we have to check for the special case when
3905                the ICS inserts things like [white] or [black]. */
3906             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3907                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3908                 /* star_matches:
3909                    0    player 1 name (not necessarily white)
3910                    1    player 1 rating
3911                    2    empty, white, or black (IGNORED)
3912                    3    player 2 name (not necessarily black)
3913                    4    player 2 rating
3914
3915                    The names/ratings are sorted out when the game
3916                    actually starts (below).
3917                 */
3918                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3919                 player1Rating = string_to_rating(star_match[1]);
3920                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3921                 player2Rating = string_to_rating(star_match[4]);
3922
3923                 if (appData.debugMode)
3924                   fprintf(debugFP,
3925                           "Ratings from 'Creating:' %s %d, %s %d\n",
3926                           player1Name, player1Rating,
3927                           player2Name, player2Rating);
3928
3929                 continue;
3930             }
3931
3932             /* Improved generic start/end-of-game messages */
3933             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3934                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3935                 /* If tkind == 0: */
3936                 /* star_match[0] is the game number */
3937                 /*           [1] is the white player's name */
3938                 /*           [2] is the black player's name */
3939                 /* For end-of-game: */
3940                 /*           [3] is the reason for the game end */
3941                 /*           [4] is a PGN end game-token, preceded by " " */
3942                 /* For start-of-game: */
3943                 /*           [3] begins with "Creating" or "Continuing" */
3944                 /*           [4] is " *" or empty (don't care). */
3945                 int gamenum = atoi(star_match[0]);
3946                 char *whitename, *blackname, *why, *endtoken;
3947                 ChessMove endtype = EndOfFile;
3948
3949                 if (tkind == 0) {
3950                   whitename = star_match[1];
3951                   blackname = star_match[2];
3952                   why = star_match[3];
3953                   endtoken = star_match[4];
3954                 } else {
3955                   whitename = star_match[1];
3956                   blackname = star_match[3];
3957                   why = star_match[5];
3958                   endtoken = star_match[6];
3959                 }
3960
3961                 /* Game start messages */
3962                 if (strncmp(why, "Creating ", 9) == 0 ||
3963                     strncmp(why, "Continuing ", 11) == 0) {
3964                     gs_gamenum = gamenum;
3965                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3966                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3967                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3968 #if ZIPPY
3969                     if (appData.zippyPlay) {
3970                         ZippyGameStart(whitename, blackname);
3971                     }
3972 #endif /*ZIPPY*/
3973                     partnerBoardValid = FALSE; // [HGM] bughouse
3974                     continue;
3975                 }
3976
3977                 /* Game end messages */
3978                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3979                     ics_gamenum != gamenum) {
3980                     continue;
3981                 }
3982                 while (endtoken[0] == ' ') endtoken++;
3983                 switch (endtoken[0]) {
3984                   case '*':
3985                   default:
3986                     endtype = GameUnfinished;
3987                     break;
3988                   case '0':
3989                     endtype = BlackWins;
3990                     break;
3991                   case '1':
3992                     if (endtoken[1] == '/')
3993                       endtype = GameIsDrawn;
3994                     else
3995                       endtype = WhiteWins;
3996                     break;
3997                 }
3998                 GameEnds(endtype, why, GE_ICS);
3999 #if ZIPPY
4000                 if (appData.zippyPlay && first.initDone) {
4001                     ZippyGameEnd(endtype, why);
4002                     if (first.pr == NoProc) {
4003                       /* Start the next process early so that we'll
4004                          be ready for the next challenge */
4005                       StartChessProgram(&first);
4006                     }
4007                     /* Send "new" early, in case this command takes
4008                        a long time to finish, so that we'll be ready
4009                        for the next challenge. */
4010                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4011                     Reset(TRUE, TRUE);
4012                 }
4013 #endif /*ZIPPY*/
4014                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4015                 continue;
4016             }
4017
4018             if (looking_at(buf, &i, "Removing game * from observation") ||
4019                 looking_at(buf, &i, "no longer observing game *") ||
4020                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4021                 if (gameMode == IcsObserving &&
4022                     atoi(star_match[0]) == ics_gamenum)
4023                   {
4024                       /* icsEngineAnalyze */
4025                       if (appData.icsEngineAnalyze) {
4026                             ExitAnalyzeMode();
4027                             ModeHighlight();
4028                       }
4029                       StopClocks();
4030                       gameMode = IcsIdle;
4031                       ics_gamenum = -1;
4032                       ics_user_moved = FALSE;
4033                   }
4034                 continue;
4035             }
4036
4037             if (looking_at(buf, &i, "no longer examining game *")) {
4038                 if (gameMode == IcsExamining &&
4039                     atoi(star_match[0]) == ics_gamenum)
4040                   {
4041                       gameMode = IcsIdle;
4042                       ics_gamenum = -1;
4043                       ics_user_moved = FALSE;
4044                   }
4045                 continue;
4046             }
4047
4048             /* Advance leftover_start past any newlines we find,
4049                so only partial lines can get reparsed */
4050             if (looking_at(buf, &i, "\n")) {
4051                 prevColor = curColor;
4052                 if (curColor != ColorNormal) {
4053                     if (oldi > next_out) {
4054                         SendToPlayer(&buf[next_out], oldi - next_out);
4055                         next_out = oldi;
4056                     }
4057                     Colorize(ColorNormal, FALSE);
4058                     curColor = ColorNormal;
4059                 }
4060                 if (started == STARTED_BOARD) {
4061                     started = STARTED_NONE;
4062                     parse[parse_pos] = NULLCHAR;
4063                     ParseBoard12(parse);
4064                     ics_user_moved = 0;
4065
4066                     /* Send premove here */
4067                     if (appData.premove) {
4068                       char str[MSG_SIZ];
4069                       if (currentMove == 0 &&
4070                           gameMode == IcsPlayingWhite &&
4071                           appData.premoveWhite) {
4072                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4073                         if (appData.debugMode)
4074                           fprintf(debugFP, "Sending premove:\n");
4075                         SendToICS(str);
4076                       } else if (currentMove == 1 &&
4077                                  gameMode == IcsPlayingBlack &&
4078                                  appData.premoveBlack) {
4079                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4080                         if (appData.debugMode)
4081                           fprintf(debugFP, "Sending premove:\n");
4082                         SendToICS(str);
4083                       } else if (gotPremove) {
4084                         gotPremove = 0;
4085                         ClearPremoveHighlights();
4086                         if (appData.debugMode)
4087                           fprintf(debugFP, "Sending premove:\n");
4088                           UserMoveEvent(premoveFromX, premoveFromY,
4089                                         premoveToX, premoveToY,
4090                                         premovePromoChar);
4091                       }
4092                     }
4093
4094                     /* Usually suppress following prompt */
4095                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4096                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4097                         if (looking_at(buf, &i, "*% ")) {
4098                             savingComment = FALSE;
4099                             suppressKibitz = 0;
4100                         }
4101                     }
4102                     next_out = i;
4103                 } else if (started == STARTED_HOLDINGS) {
4104                     int gamenum;
4105                     char new_piece[MSG_SIZ];
4106                     started = STARTED_NONE;
4107                     parse[parse_pos] = NULLCHAR;
4108                     if (appData.debugMode)
4109                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4110                                                         parse, currentMove);
4111                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4112                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4113                         if (gameInfo.variant == VariantNormal) {
4114                           /* [HGM] We seem to switch variant during a game!
4115                            * Presumably no holdings were displayed, so we have
4116                            * to move the position two files to the right to
4117                            * create room for them!
4118                            */
4119                           VariantClass newVariant;
4120                           switch(gameInfo.boardWidth) { // base guess on board width
4121                                 case 9:  newVariant = VariantShogi; break;
4122                                 case 10: newVariant = VariantGreat; break;
4123                                 default: newVariant = VariantCrazyhouse; break;
4124                           }
4125                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4126                           /* Get a move list just to see the header, which
4127                              will tell us whether this is really bug or zh */
4128                           if (ics_getting_history == H_FALSE) {
4129                             ics_getting_history = H_REQUESTED;
4130                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4131                             SendToICS(str);
4132                           }
4133                         }
4134                         new_piece[0] = NULLCHAR;
4135                         sscanf(parse, "game %d white [%s black [%s <- %s",
4136                                &gamenum, white_holding, black_holding,
4137                                new_piece);
4138                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4139                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4140                         /* [HGM] copy holdings to board holdings area */
4141                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4142                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4143                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4144 #if ZIPPY
4145                         if (appData.zippyPlay && first.initDone) {
4146                             ZippyHoldings(white_holding, black_holding,
4147                                           new_piece);
4148                         }
4149 #endif /*ZIPPY*/
4150                         if (tinyLayout || smallLayout) {
4151                             char wh[16], bh[16];
4152                             PackHolding(wh, white_holding);
4153                             PackHolding(bh, black_holding);
4154                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4155                                     gameInfo.white, gameInfo.black);
4156                         } else {
4157                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4158                                     gameInfo.white, white_holding, _("vs."),
4159                                     gameInfo.black, black_holding);
4160                         }
4161                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4162                         DrawPosition(FALSE, boards[currentMove]);
4163                         DisplayTitle(str);
4164                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4165                         sscanf(parse, "game %d white [%s black [%s <- %s",
4166                                &gamenum, white_holding, black_holding,
4167                                new_piece);
4168                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4169                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4170                         /* [HGM] copy holdings to partner-board holdings area */
4171                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4172                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4173                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4174                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4175                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4176                       }
4177                     }
4178                     /* Suppress following prompt */
4179                     if (looking_at(buf, &i, "*% ")) {
4180                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4181                         savingComment = FALSE;
4182                         suppressKibitz = 0;
4183                     }
4184                     next_out = i;
4185                 }
4186                 continue;
4187             }
4188
4189             i++;                /* skip unparsed character and loop back */
4190         }
4191
4192         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4193 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4194 //          SendToPlayer(&buf[next_out], i - next_out);
4195             started != STARTED_HOLDINGS && leftover_start > next_out) {
4196             SendToPlayer(&buf[next_out], leftover_start - next_out);
4197             next_out = i;
4198         }
4199
4200         leftover_len = buf_len - leftover_start;
4201         /* if buffer ends with something we couldn't parse,
4202            reparse it after appending the next read */
4203
4204     } else if (count == 0) {
4205         RemoveInputSource(isr);
4206         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4207     } else {
4208         DisplayFatalError(_("Error reading from ICS"), error, 1);
4209     }
4210 }
4211
4212
4213 /* Board style 12 looks like this:
4214
4215    <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
4216
4217  * The "<12> " is stripped before it gets to this routine.  The two
4218  * trailing 0's (flip state and clock ticking) are later addition, and
4219  * some chess servers may not have them, or may have only the first.
4220  * Additional trailing fields may be added in the future.
4221  */
4222
4223 #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"
4224
4225 #define RELATION_OBSERVING_PLAYED    0
4226 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4227 #define RELATION_PLAYING_MYMOVE      1
4228 #define RELATION_PLAYING_NOTMYMOVE  -1
4229 #define RELATION_EXAMINING           2
4230 #define RELATION_ISOLATED_BOARD     -3
4231 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4232
4233 void
4234 ParseBoard12 (char *string)
4235 {
4236 #if ZIPPY
4237     int i, takeback;
4238     char *bookHit = NULL; // [HGM] book
4239 #endif
4240     GameMode newGameMode;
4241     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4242     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4243     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4244     char to_play, board_chars[200];
4245     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4246     char black[32], white[32];
4247     Board board;
4248     int prevMove = currentMove;
4249     int ticking = 2;
4250     ChessMove moveType;
4251     int fromX, fromY, toX, toY;
4252     char promoChar;
4253     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4254     Boolean weird = FALSE, reqFlag = FALSE;
4255
4256     fromX = fromY = toX = toY = -1;
4257
4258     newGame = FALSE;
4259
4260     if (appData.debugMode)
4261       fprintf(debugFP, "Parsing board: %s\n", string);
4262
4263     move_str[0] = NULLCHAR;
4264     elapsed_time[0] = NULLCHAR;
4265     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4266         int  i = 0, j;
4267         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4268             if(string[i] == ' ') { ranks++; files = 0; }
4269             else files++;
4270             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4271             i++;
4272         }
4273         for(j = 0; j <i; j++) board_chars[j] = string[j];
4274         board_chars[i] = '\0';
4275         string += i + 1;
4276     }
4277     n = sscanf(string, PATTERN, &to_play, &double_push,
4278                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4279                &gamenum, white, black, &relation, &basetime, &increment,
4280                &white_stren, &black_stren, &white_time, &black_time,
4281                &moveNum, str, elapsed_time, move_str, &ics_flip,
4282                &ticking);
4283
4284     if (n < 21) {
4285         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4286         DisplayError(str, 0);
4287         return;
4288     }
4289
4290     /* Convert the move number to internal form */
4291     moveNum = (moveNum - 1) * 2;
4292     if (to_play == 'B') moveNum++;
4293     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4294       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4295                         0, 1);
4296       return;
4297     }
4298
4299     switch (relation) {
4300       case RELATION_OBSERVING_PLAYED:
4301       case RELATION_OBSERVING_STATIC:
4302         if (gamenum == -1) {
4303             /* Old ICC buglet */
4304             relation = RELATION_OBSERVING_STATIC;
4305         }
4306         newGameMode = IcsObserving;
4307         break;
4308       case RELATION_PLAYING_MYMOVE:
4309       case RELATION_PLAYING_NOTMYMOVE:
4310         newGameMode =
4311           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4312             IcsPlayingWhite : IcsPlayingBlack;
4313         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4314         break;
4315       case RELATION_EXAMINING:
4316         newGameMode = IcsExamining;
4317         break;
4318       case RELATION_ISOLATED_BOARD:
4319       default:
4320         /* Just display this board.  If user was doing something else,
4321            we will forget about it until the next board comes. */
4322         newGameMode = IcsIdle;
4323         break;
4324       case RELATION_STARTING_POSITION:
4325         newGameMode = gameMode;
4326         break;
4327     }
4328
4329     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4330         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4331          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4332       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4333       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4334       static int lastBgGame = -1;
4335       char *toSqr;
4336       for (k = 0; k < ranks; k++) {
4337         for (j = 0; j < files; j++)
4338           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4339         if(gameInfo.holdingsWidth > 1) {
4340              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4341              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4342         }
4343       }
4344       CopyBoard(partnerBoard, board);
4345       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4346         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4347         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4348       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4349       if(toSqr = strchr(str, '-')) {
4350         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4351         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4352       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4353       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4354       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4355       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4356       if(twoBoards) {
4357           DisplayWhiteClock(white_time*fac, to_play == 'W');
4358           DisplayBlackClock(black_time*fac, to_play != 'W');
4359           activePartner = to_play;
4360           if(gamenum != lastBgGame) {
4361               char buf[MSG_SIZ];
4362               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4363               DisplayTitle(buf);
4364           }
4365           lastBgGame = gamenum;
4366           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4367                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4368       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4369                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4370       if(!twoBoards) DisplayMessage(partnerStatus, "");
4371         partnerBoardValid = TRUE;
4372       return;
4373     }
4374
4375     if(appData.dualBoard && appData.bgObserve) {
4376         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4377             SendToICS(ics_prefix), SendToICS("pobserve\n");
4378         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4379             char buf[MSG_SIZ];
4380             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4381             SendToICS(buf);
4382         }
4383     }
4384
4385     /* Modify behavior for initial board display on move listing
4386        of wild games.
4387        */
4388     switch (ics_getting_history) {
4389       case H_FALSE:
4390       case H_REQUESTED:
4391         break;
4392       case H_GOT_REQ_HEADER:
4393       case H_GOT_UNREQ_HEADER:
4394         /* This is the initial position of the current game */
4395         gamenum = ics_gamenum;
4396         moveNum = 0;            /* old ICS bug workaround */
4397         if (to_play == 'B') {
4398           startedFromSetupPosition = TRUE;
4399           blackPlaysFirst = TRUE;
4400           moveNum = 1;
4401           if (forwardMostMove == 0) forwardMostMove = 1;
4402           if (backwardMostMove == 0) backwardMostMove = 1;
4403           if (currentMove == 0) currentMove = 1;
4404         }
4405         newGameMode = gameMode;
4406         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4407         break;
4408       case H_GOT_UNWANTED_HEADER:
4409         /* This is an initial board that we don't want */
4410         return;
4411       case H_GETTING_MOVES:
4412         /* Should not happen */
4413         DisplayError(_("Error gathering move list: extra board"), 0);
4414         ics_getting_history = H_FALSE;
4415         return;
4416     }
4417
4418    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4419                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4420                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4421      /* [HGM] We seem to have switched variant unexpectedly
4422       * Try to guess new variant from board size
4423       */
4424           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4425           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4426           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4427           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4428           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4429           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4430           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4431           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4432           /* Get a move list just to see the header, which
4433              will tell us whether this is really bug or zh */
4434           if (ics_getting_history == H_FALSE) {
4435             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4436             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4437             SendToICS(str);
4438           }
4439     }
4440
4441     /* Take action if this is the first board of a new game, or of a
4442        different game than is currently being displayed.  */
4443     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4444         relation == RELATION_ISOLATED_BOARD) {
4445
4446         /* Forget the old game and get the history (if any) of the new one */
4447         if (gameMode != BeginningOfGame) {
4448           Reset(TRUE, TRUE);
4449         }
4450         newGame = TRUE;
4451         if (appData.autoRaiseBoard) BoardToTop();
4452         prevMove = -3;
4453         if (gamenum == -1) {
4454             newGameMode = IcsIdle;
4455         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4456                    appData.getMoveList && !reqFlag) {
4457             /* Need to get game history */
4458             ics_getting_history = H_REQUESTED;
4459             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4460             SendToICS(str);
4461         }
4462
4463         /* Initially flip the board to have black on the bottom if playing
4464            black or if the ICS flip flag is set, but let the user change
4465            it with the Flip View button. */
4466         flipView = appData.autoFlipView ?
4467           (newGameMode == IcsPlayingBlack) || ics_flip :
4468           appData.flipView;
4469
4470         /* Done with values from previous mode; copy in new ones */
4471         gameMode = newGameMode;
4472         ModeHighlight();
4473         ics_gamenum = gamenum;
4474         if (gamenum == gs_gamenum) {
4475             int klen = strlen(gs_kind);
4476             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4477             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4478             gameInfo.event = StrSave(str);
4479         } else {
4480             gameInfo.event = StrSave("ICS game");
4481         }
4482         gameInfo.site = StrSave(appData.icsHost);
4483         gameInfo.date = PGNDate();
4484         gameInfo.round = StrSave("-");
4485         gameInfo.white = StrSave(white);
4486         gameInfo.black = StrSave(black);
4487         timeControl = basetime * 60 * 1000;
4488         timeControl_2 = 0;
4489         timeIncrement = increment * 1000;
4490         movesPerSession = 0;
4491         gameInfo.timeControl = TimeControlTagValue();
4492         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4493   if (appData.debugMode) {
4494     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4495     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4496     setbuf(debugFP, NULL);
4497   }
4498
4499         gameInfo.outOfBook = NULL;
4500
4501         /* Do we have the ratings? */
4502         if (strcmp(player1Name, white) == 0 &&
4503             strcmp(player2Name, black) == 0) {
4504             if (appData.debugMode)
4505               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4506                       player1Rating, player2Rating);
4507             gameInfo.whiteRating = player1Rating;
4508             gameInfo.blackRating = player2Rating;
4509         } else if (strcmp(player2Name, white) == 0 &&
4510                    strcmp(player1Name, black) == 0) {
4511             if (appData.debugMode)
4512               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4513                       player2Rating, player1Rating);
4514             gameInfo.whiteRating = player2Rating;
4515             gameInfo.blackRating = player1Rating;
4516         }
4517         player1Name[0] = player2Name[0] = NULLCHAR;
4518
4519         /* Silence shouts if requested */
4520         if (appData.quietPlay &&
4521             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4522             SendToICS(ics_prefix);
4523             SendToICS("set shout 0\n");
4524         }
4525     }
4526
4527     /* Deal with midgame name changes */
4528     if (!newGame) {
4529         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4530             if (gameInfo.white) free(gameInfo.white);
4531             gameInfo.white = StrSave(white);
4532         }
4533         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4534             if (gameInfo.black) free(gameInfo.black);
4535             gameInfo.black = StrSave(black);
4536         }
4537     }
4538
4539     /* Throw away game result if anything actually changes in examine mode */
4540     if (gameMode == IcsExamining && !newGame) {
4541         gameInfo.result = GameUnfinished;
4542         if (gameInfo.resultDetails != NULL) {
4543             free(gameInfo.resultDetails);
4544             gameInfo.resultDetails = NULL;
4545         }
4546     }
4547
4548     /* In pausing && IcsExamining mode, we ignore boards coming
4549        in if they are in a different variation than we are. */
4550     if (pauseExamInvalid) return;
4551     if (pausing && gameMode == IcsExamining) {
4552         if (moveNum <= pauseExamForwardMostMove) {
4553             pauseExamInvalid = TRUE;
4554             forwardMostMove = pauseExamForwardMostMove;
4555             return;
4556         }
4557     }
4558
4559   if (appData.debugMode) {
4560     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4561   }
4562     /* Parse the board */
4563     for (k = 0; k < ranks; k++) {
4564       for (j = 0; j < files; j++)
4565         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4566       if(gameInfo.holdingsWidth > 1) {
4567            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4568            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4569       }
4570     }
4571     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4572       board[5][BOARD_RGHT+1] = WhiteAngel;
4573       board[6][BOARD_RGHT+1] = WhiteMarshall;
4574       board[1][0] = BlackMarshall;
4575       board[2][0] = BlackAngel;
4576       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4577     }
4578     CopyBoard(boards[moveNum], board);
4579     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4580     if (moveNum == 0) {
4581         startedFromSetupPosition =
4582           !CompareBoards(board, initialPosition);
4583         if(startedFromSetupPosition)
4584             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4585     }
4586
4587     /* [HGM] Set castling rights. Take the outermost Rooks,
4588        to make it also work for FRC opening positions. Note that board12
4589        is really defective for later FRC positions, as it has no way to
4590        indicate which Rook can castle if they are on the same side of King.
4591        For the initial position we grant rights to the outermost Rooks,
4592        and remember thos rights, and we then copy them on positions
4593        later in an FRC game. This means WB might not recognize castlings with
4594        Rooks that have moved back to their original position as illegal,
4595        but in ICS mode that is not its job anyway.
4596     */
4597     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4598     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4599
4600         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4601             if(board[0][i] == WhiteRook) j = i;
4602         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4603         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4604             if(board[0][i] == WhiteRook) j = i;
4605         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4606         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4607             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4608         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4609         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4610             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4611         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4612
4613         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4614         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4615         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4616             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4617         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4618             if(board[BOARD_HEIGHT-1][k] == bKing)
4619                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4620         if(gameInfo.variant == VariantTwoKings) {
4621             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4622             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4623             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4624         }
4625     } else { int r;
4626         r = boards[moveNum][CASTLING][0] = initialRights[0];
4627         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4628         r = boards[moveNum][CASTLING][1] = initialRights[1];
4629         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4630         r = boards[moveNum][CASTLING][3] = initialRights[3];
4631         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4632         r = boards[moveNum][CASTLING][4] = initialRights[4];
4633         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4634         /* wildcastle kludge: always assume King has rights */
4635         r = boards[moveNum][CASTLING][2] = initialRights[2];
4636         r = boards[moveNum][CASTLING][5] = initialRights[5];
4637     }
4638     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4639     boards[moveNum][EP_STATUS] = EP_NONE;
4640     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4641     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4642     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4643
4644
4645     if (ics_getting_history == H_GOT_REQ_HEADER ||
4646         ics_getting_history == H_GOT_UNREQ_HEADER) {
4647         /* This was an initial position from a move list, not
4648            the current position */
4649         return;
4650     }
4651
4652     /* Update currentMove and known move number limits */
4653     newMove = newGame || moveNum > forwardMostMove;
4654
4655     if (newGame) {
4656         forwardMostMove = backwardMostMove = currentMove = moveNum;
4657         if (gameMode == IcsExamining && moveNum == 0) {
4658           /* Workaround for ICS limitation: we are not told the wild
4659              type when starting to examine a game.  But if we ask for
4660              the move list, the move list header will tell us */
4661             ics_getting_history = H_REQUESTED;
4662             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4663             SendToICS(str);
4664         }
4665     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4666                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4667 #if ZIPPY
4668         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4669         /* [HGM] applied this also to an engine that is silently watching        */
4670         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4671             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4672             gameInfo.variant == currentlyInitializedVariant) {
4673           takeback = forwardMostMove - moveNum;
4674           for (i = 0; i < takeback; i++) {
4675             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4676             SendToProgram("undo\n", &first);
4677           }
4678         }
4679 #endif
4680
4681         forwardMostMove = moveNum;
4682         if (!pausing || currentMove > forwardMostMove)
4683           currentMove = forwardMostMove;
4684     } else {
4685         /* New part of history that is not contiguous with old part */
4686         if (pausing && gameMode == IcsExamining) {
4687             pauseExamInvalid = TRUE;
4688             forwardMostMove = pauseExamForwardMostMove;
4689             return;
4690         }
4691         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4692 #if ZIPPY
4693             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4694                 // [HGM] when we will receive the move list we now request, it will be
4695                 // fed to the engine from the first move on. So if the engine is not
4696                 // in the initial position now, bring it there.
4697                 InitChessProgram(&first, 0);
4698             }
4699 #endif
4700             ics_getting_history = H_REQUESTED;
4701             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4702             SendToICS(str);
4703         }
4704         forwardMostMove = backwardMostMove = currentMove = moveNum;
4705     }
4706
4707     /* Update the clocks */
4708     if (strchr(elapsed_time, '.')) {
4709       /* Time is in ms */
4710       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4711       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4712     } else {
4713       /* Time is in seconds */
4714       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4715       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4716     }
4717
4718
4719 #if ZIPPY
4720     if (appData.zippyPlay && newGame &&
4721         gameMode != IcsObserving && gameMode != IcsIdle &&
4722         gameMode != IcsExamining)
4723       ZippyFirstBoard(moveNum, basetime, increment);
4724 #endif
4725
4726     /* Put the move on the move list, first converting
4727        to canonical algebraic form. */
4728     if (moveNum > 0) {
4729   if (appData.debugMode) {
4730     int f = forwardMostMove;
4731     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4732             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4733             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4734     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4735     fprintf(debugFP, "moveNum = %d\n", moveNum);
4736     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4737     setbuf(debugFP, NULL);
4738   }
4739         if (moveNum <= backwardMostMove) {
4740             /* We don't know what the board looked like before
4741                this move.  Punt. */
4742           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4743             strcat(parseList[moveNum - 1], " ");
4744             strcat(parseList[moveNum - 1], elapsed_time);
4745             moveList[moveNum - 1][0] = NULLCHAR;
4746         } else if (strcmp(move_str, "none") == 0) {
4747             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4748             /* Again, we don't know what the board looked like;
4749                this is really the start of the game. */
4750             parseList[moveNum - 1][0] = NULLCHAR;
4751             moveList[moveNum - 1][0] = NULLCHAR;
4752             backwardMostMove = moveNum;
4753             startedFromSetupPosition = TRUE;
4754             fromX = fromY = toX = toY = -1;
4755         } else {
4756           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4757           //                 So we parse the long-algebraic move string in stead of the SAN move
4758           int valid; char buf[MSG_SIZ], *prom;
4759
4760           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4761                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4762           // str looks something like "Q/a1-a2"; kill the slash
4763           if(str[1] == '/')
4764             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4765           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4766           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4767                 strcat(buf, prom); // long move lacks promo specification!
4768           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4769                 if(appData.debugMode)
4770                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4771                 safeStrCpy(move_str, buf, MSG_SIZ);
4772           }
4773           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4774                                 &fromX, &fromY, &toX, &toY, &promoChar)
4775                || ParseOneMove(buf, moveNum - 1, &moveType,
4776                                 &fromX, &fromY, &toX, &toY, &promoChar);
4777           // end of long SAN patch
4778           if (valid) {
4779             (void) CoordsToAlgebraic(boards[moveNum - 1],
4780                                      PosFlags(moveNum - 1),
4781                                      fromY, fromX, toY, toX, promoChar,
4782                                      parseList[moveNum-1]);
4783             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4784               case MT_NONE:
4785               case MT_STALEMATE:
4786               default:
4787                 break;
4788               case MT_CHECK:
4789                 if(gameInfo.variant != VariantShogi)
4790                     strcat(parseList[moveNum - 1], "+");
4791                 break;
4792               case MT_CHECKMATE:
4793               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4794                 strcat(parseList[moveNum - 1], "#");
4795                 break;
4796             }
4797             strcat(parseList[moveNum - 1], " ");
4798             strcat(parseList[moveNum - 1], elapsed_time);
4799             /* currentMoveString is set as a side-effect of ParseOneMove */
4800             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4801             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4802             strcat(moveList[moveNum - 1], "\n");
4803
4804             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4805                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4806               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4807                 ChessSquare old, new = boards[moveNum][k][j];
4808                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4809                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4810                   if(old == new) continue;
4811                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4812                   else if(new == WhiteWazir || new == BlackWazir) {
4813                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4814                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4815                       else boards[moveNum][k][j] = old; // preserve type of Gold
4816                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4817                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4818               }
4819           } else {
4820             /* Move from ICS was illegal!?  Punt. */
4821             if (appData.debugMode) {
4822               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4823               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4824             }
4825             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4826             strcat(parseList[moveNum - 1], " ");
4827             strcat(parseList[moveNum - 1], elapsed_time);
4828             moveList[moveNum - 1][0] = NULLCHAR;
4829             fromX = fromY = toX = toY = -1;
4830           }
4831         }
4832   if (appData.debugMode) {
4833     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4834     setbuf(debugFP, NULL);
4835   }
4836
4837 #if ZIPPY
4838         /* Send move to chess program (BEFORE animating it). */
4839         if (appData.zippyPlay && !newGame && newMove &&
4840            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4841
4842             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4843                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4844                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4845                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4846                             move_str);
4847                     DisplayError(str, 0);
4848                 } else {
4849                     if (first.sendTime) {
4850                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4851                     }
4852                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4853                     if (firstMove && !bookHit) {
4854                         firstMove = FALSE;
4855                         if (first.useColors) {
4856                           SendToProgram(gameMode == IcsPlayingWhite ?
4857                                         "white\ngo\n" :
4858                                         "black\ngo\n", &first);
4859                         } else {
4860                           SendToProgram("go\n", &first);
4861                         }
4862                         first.maybeThinking = TRUE;
4863                     }
4864                 }
4865             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4866               if (moveList[moveNum - 1][0] == NULLCHAR) {
4867                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4868                 DisplayError(str, 0);
4869               } else {
4870                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4871                 SendMoveToProgram(moveNum - 1, &first);
4872               }
4873             }
4874         }
4875 #endif
4876     }
4877
4878     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4879         /* If move comes from a remote source, animate it.  If it
4880            isn't remote, it will have already been animated. */
4881         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4882             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4883         }
4884         if (!pausing && appData.highlightLastMove) {
4885             SetHighlights(fromX, fromY, toX, toY);
4886         }
4887     }
4888
4889     /* Start the clocks */
4890     whiteFlag = blackFlag = FALSE;
4891     appData.clockMode = !(basetime == 0 && increment == 0);
4892     if (ticking == 0) {
4893       ics_clock_paused = TRUE;
4894       StopClocks();
4895     } else if (ticking == 1) {
4896       ics_clock_paused = FALSE;
4897     }
4898     if (gameMode == IcsIdle ||
4899         relation == RELATION_OBSERVING_STATIC ||
4900         relation == RELATION_EXAMINING ||
4901         ics_clock_paused)
4902       DisplayBothClocks();
4903     else
4904       StartClocks();
4905
4906     /* Display opponents and material strengths */
4907     if (gameInfo.variant != VariantBughouse &&
4908         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4909         if (tinyLayout || smallLayout) {
4910             if(gameInfo.variant == VariantNormal)
4911               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4912                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4913                     basetime, increment);
4914             else
4915               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4916                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4917                     basetime, increment, (int) gameInfo.variant);
4918         } else {
4919             if(gameInfo.variant == VariantNormal)
4920               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4921                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4922                     basetime, increment);
4923             else
4924               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4925                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4926                     basetime, increment, VariantName(gameInfo.variant));
4927         }
4928         DisplayTitle(str);
4929   if (appData.debugMode) {
4930     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4931   }
4932     }
4933
4934
4935     /* Display the board */
4936     if (!pausing && !appData.noGUI) {
4937
4938       if (appData.premove)
4939           if (!gotPremove ||
4940              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4941              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4942               ClearPremoveHighlights();
4943
4944       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4945         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4946       DrawPosition(j, boards[currentMove]);
4947
4948       DisplayMove(moveNum - 1);
4949       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4950             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4951               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4952         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4953       }
4954     }
4955
4956     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4957 #if ZIPPY
4958     if(bookHit) { // [HGM] book: simulate book reply
4959         static char bookMove[MSG_SIZ]; // a bit generous?
4960
4961         programStats.nodes = programStats.depth = programStats.time =
4962         programStats.score = programStats.got_only_move = 0;
4963         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4964
4965         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4966         strcat(bookMove, bookHit);
4967         HandleMachineMove(bookMove, &first);
4968     }
4969 #endif
4970 }
4971
4972 void
4973 GetMoveListEvent ()
4974 {
4975     char buf[MSG_SIZ];
4976     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4977         ics_getting_history = H_REQUESTED;
4978         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4979         SendToICS(buf);
4980     }
4981 }
4982
4983 void
4984 SendToBoth (char *msg)
4985 {   // to make it easy to keep two engines in step in dual analysis
4986     SendToProgram(msg, &first);
4987     if(second.analyzing) SendToProgram(msg, &second);
4988 }
4989
4990 void
4991 AnalysisPeriodicEvent (int force)
4992 {
4993     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4994          && !force) || !appData.periodicUpdates)
4995       return;
4996
4997     /* Send . command to Crafty to collect stats */
4998     SendToBoth(".\n");
4999
5000     /* Don't send another until we get a response (this makes
5001        us stop sending to old Crafty's which don't understand
5002        the "." command (sending illegal cmds resets node count & time,
5003        which looks bad)) */
5004     programStats.ok_to_send = 0;
5005 }
5006
5007 void
5008 ics_update_width (int new_width)
5009 {
5010         ics_printf("set width %d\n", new_width);
5011 }
5012
5013 void
5014 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5015 {
5016     char buf[MSG_SIZ];
5017
5018     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5019         // null move in variant where engine does not understand it (for analysis purposes)
5020         SendBoard(cps, moveNum + 1); // send position after move in stead.
5021         return;
5022     }
5023     if (cps->useUsermove) {
5024       SendToProgram("usermove ", cps);
5025     }
5026     if (cps->useSAN) {
5027       char *space;
5028       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5029         int len = space - parseList[moveNum];
5030         memcpy(buf, parseList[moveNum], len);
5031         buf[len++] = '\n';
5032         buf[len] = NULLCHAR;
5033       } else {
5034         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5035       }
5036       SendToProgram(buf, cps);
5037     } else {
5038       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5039         AlphaRank(moveList[moveNum], 4);
5040         SendToProgram(moveList[moveNum], cps);
5041         AlphaRank(moveList[moveNum], 4); // and back
5042       } else
5043       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5044        * the engine. It would be nice to have a better way to identify castle
5045        * moves here. */
5046       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5047                                                                          && cps->useOOCastle) {
5048         int fromX = moveList[moveNum][0] - AAA;
5049         int fromY = moveList[moveNum][1] - ONE;
5050         int toX = moveList[moveNum][2] - AAA;
5051         int toY = moveList[moveNum][3] - ONE;
5052         if((boards[moveNum][fromY][fromX] == WhiteKing
5053             && boards[moveNum][toY][toX] == WhiteRook)
5054            || (boards[moveNum][fromY][fromX] == BlackKing
5055                && boards[moveNum][toY][toX] == BlackRook)) {
5056           if(toX > fromX) SendToProgram("O-O\n", cps);
5057           else SendToProgram("O-O-O\n", cps);
5058         }
5059         else SendToProgram(moveList[moveNum], cps);
5060       } else
5061       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5062         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5063           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5064           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5065                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5066         } else if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5067           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5068                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5069                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5070                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5071         } else
5072           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5073                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5074         SendToProgram(buf, cps);
5075       }
5076       else SendToProgram(moveList[moveNum], cps);
5077       /* End of additions by Tord */
5078     }
5079
5080     /* [HGM] setting up the opening has brought engine in force mode! */
5081     /*       Send 'go' if we are in a mode where machine should play. */
5082     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5083         (gameMode == TwoMachinesPlay   ||
5084 #if ZIPPY
5085          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5086 #endif
5087          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5088         SendToProgram("go\n", cps);
5089   if (appData.debugMode) {
5090     fprintf(debugFP, "(extra)\n");
5091   }
5092     }
5093     setboardSpoiledMachineBlack = 0;
5094 }
5095
5096 void
5097 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5098 {
5099     char user_move[MSG_SIZ];
5100     char suffix[4];
5101
5102     if(gameInfo.variant == VariantSChess && promoChar) {
5103         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5104         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5105     } else suffix[0] = NULLCHAR;
5106
5107     switch (moveType) {
5108       default:
5109         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5110                 (int)moveType, fromX, fromY, toX, toY);
5111         DisplayError(user_move + strlen("say "), 0);
5112         break;
5113       case WhiteKingSideCastle:
5114       case BlackKingSideCastle:
5115       case WhiteQueenSideCastleWild:
5116       case BlackQueenSideCastleWild:
5117       /* PUSH Fabien */
5118       case WhiteHSideCastleFR:
5119       case BlackHSideCastleFR:
5120       /* POP Fabien */
5121         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5122         break;
5123       case WhiteQueenSideCastle:
5124       case BlackQueenSideCastle:
5125       case WhiteKingSideCastleWild:
5126       case BlackKingSideCastleWild:
5127       /* PUSH Fabien */
5128       case WhiteASideCastleFR:
5129       case BlackASideCastleFR:
5130       /* POP Fabien */
5131         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5132         break;
5133       case WhiteNonPromotion:
5134       case BlackNonPromotion:
5135         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5136         break;
5137       case WhitePromotion:
5138       case BlackPromotion:
5139         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5140            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5141           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5142                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5143                 PieceToChar(WhiteFerz));
5144         else if(gameInfo.variant == VariantGreat)
5145           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5146                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5147                 PieceToChar(WhiteMan));
5148         else
5149           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5150                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5151                 promoChar);
5152         break;
5153       case WhiteDrop:
5154       case BlackDrop:
5155       drop:
5156         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5157                  ToUpper(PieceToChar((ChessSquare) fromX)),
5158                  AAA + toX, ONE + toY);
5159         break;
5160       case IllegalMove:  /* could be a variant we don't quite understand */
5161         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5162       case NormalMove:
5163       case WhiteCapturesEnPassant:
5164       case BlackCapturesEnPassant:
5165         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5166                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5167         break;
5168     }
5169     SendToICS(user_move);
5170     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5171         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5172 }
5173
5174 void
5175 UploadGameEvent ()
5176 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5177     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5178     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5179     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5180       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5181       return;
5182     }
5183     if(gameMode != IcsExamining) { // is this ever not the case?
5184         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5185
5186         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5187           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5188         } else { // on FICS we must first go to general examine mode
5189           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5190         }
5191         if(gameInfo.variant != VariantNormal) {
5192             // try figure out wild number, as xboard names are not always valid on ICS
5193             for(i=1; i<=36; i++) {
5194               snprintf(buf, MSG_SIZ, "wild/%d", i);
5195                 if(StringToVariant(buf) == gameInfo.variant) break;
5196             }
5197             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5198             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5199             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5200         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5201         SendToICS(ics_prefix);
5202         SendToICS(buf);
5203         if(startedFromSetupPosition || backwardMostMove != 0) {
5204           fen = PositionToFEN(backwardMostMove, NULL, 1);
5205           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5206             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5207             SendToICS(buf);
5208           } else { // FICS: everything has to set by separate bsetup commands
5209             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5210             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5211             SendToICS(buf);
5212             if(!WhiteOnMove(backwardMostMove)) {
5213                 SendToICS("bsetup tomove black\n");
5214             }
5215             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5216             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5217             SendToICS(buf);
5218             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5219             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5220             SendToICS(buf);
5221             i = boards[backwardMostMove][EP_STATUS];
5222             if(i >= 0) { // set e.p.
5223               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5224                 SendToICS(buf);
5225             }
5226             bsetup++;
5227           }
5228         }
5229       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5230             SendToICS("bsetup done\n"); // switch to normal examining.
5231     }
5232     for(i = backwardMostMove; i<last; i++) {
5233         char buf[20];
5234         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5235         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5236             int len = strlen(moveList[i]);
5237             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5238             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5239         }
5240         SendToICS(buf);
5241     }
5242     SendToICS(ics_prefix);
5243     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5244 }
5245
5246 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5247
5248 void
5249 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5250 {
5251     if (rf == DROP_RANK) {
5252       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5253       sprintf(move, "%c@%c%c\n",
5254                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5255     } else {
5256         if (promoChar == 'x' || promoChar == NULLCHAR) {
5257           sprintf(move, "%c%c%c%c\n",
5258                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5259           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5260         } else {
5261             sprintf(move, "%c%c%c%c%c\n",
5262                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5263         }
5264     }
5265 }
5266
5267 void
5268 ProcessICSInitScript (FILE *f)
5269 {
5270     char buf[MSG_SIZ];
5271
5272     while (fgets(buf, MSG_SIZ, f)) {
5273         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5274     }
5275
5276     fclose(f);
5277 }
5278
5279
5280 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
5281 static ClickType lastClickType;
5282
5283 void
5284 Sweep (int step)
5285 {
5286     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5287     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5288     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5289     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5290     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5291     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5292     do {
5293         promoSweep -= step;
5294         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5295         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5296         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5297         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5298         if(!step) step = -1;
5299     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5300             appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
5301             IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
5302     if(toX >= 0) {
5303         int victim = boards[currentMove][toY][toX];
5304         boards[currentMove][toY][toX] = promoSweep;
5305         DrawPosition(FALSE, boards[currentMove]);
5306         boards[currentMove][toY][toX] = victim;
5307     } else
5308     ChangeDragPiece(promoSweep);
5309 }
5310
5311 int
5312 PromoScroll (int x, int y)
5313 {
5314   int step = 0;
5315
5316   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5317   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5318   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5319   if(!step) return FALSE;
5320   lastX = x; lastY = y;
5321   if((promoSweep < BlackPawn) == flipView) step = -step;
5322   if(step > 0) selectFlag = 1;
5323   if(!selectFlag) Sweep(step);
5324   return FALSE;
5325 }
5326
5327 void
5328 NextPiece (int step)
5329 {
5330     ChessSquare piece = boards[currentMove][toY][toX];
5331     do {
5332         pieceSweep -= step;
5333         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5334         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5335         if(!step) step = -1;
5336     } while(PieceToChar(pieceSweep) == '.');
5337     boards[currentMove][toY][toX] = pieceSweep;
5338     DrawPosition(FALSE, boards[currentMove]);
5339     boards[currentMove][toY][toX] = piece;
5340 }
5341 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5342 void
5343 AlphaRank (char *move, int n)
5344 {
5345 //    char *p = move, c; int x, y;
5346
5347     if (appData.debugMode) {
5348         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5349     }
5350
5351     if(move[1]=='*' &&
5352        move[2]>='0' && move[2]<='9' &&
5353        move[3]>='a' && move[3]<='x'    ) {
5354         move[1] = '@';
5355         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5356         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5357     } else
5358     if(move[0]>='0' && move[0]<='9' &&
5359        move[1]>='a' && move[1]<='x' &&
5360        move[2]>='0' && move[2]<='9' &&
5361        move[3]>='a' && move[3]<='x'    ) {
5362         /* input move, Shogi -> normal */
5363         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5364         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5365         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5366         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5367     } else
5368     if(move[1]=='@' &&
5369        move[3]>='0' && move[3]<='9' &&
5370        move[2]>='a' && move[2]<='x'    ) {
5371         move[1] = '*';
5372         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5373         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5374     } else
5375     if(
5376        move[0]>='a' && move[0]<='x' &&
5377        move[3]>='0' && move[3]<='9' &&
5378        move[2]>='a' && move[2]<='x'    ) {
5379          /* output move, normal -> Shogi */
5380         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5381         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5382         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5383         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5384         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5385     }
5386     if (appData.debugMode) {
5387         fprintf(debugFP, "   out = '%s'\n", move);
5388     }
5389 }
5390
5391 char yy_textstr[8000];
5392
5393 /* Parser for moves from gnuchess, ICS, or user typein box */
5394 Boolean
5395 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5396 {
5397     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5398
5399     switch (*moveType) {
5400       case WhitePromotion:
5401       case BlackPromotion:
5402       case WhiteNonPromotion:
5403       case BlackNonPromotion:
5404       case NormalMove:
5405       case FirstLeg:
5406       case WhiteCapturesEnPassant:
5407       case BlackCapturesEnPassant:
5408       case WhiteKingSideCastle:
5409       case WhiteQueenSideCastle:
5410       case BlackKingSideCastle:
5411       case BlackQueenSideCastle:
5412       case WhiteKingSideCastleWild:
5413       case WhiteQueenSideCastleWild:
5414       case BlackKingSideCastleWild:
5415       case BlackQueenSideCastleWild:
5416       /* Code added by Tord: */
5417       case WhiteHSideCastleFR:
5418       case WhiteASideCastleFR:
5419       case BlackHSideCastleFR:
5420       case BlackASideCastleFR:
5421       /* End of code added by Tord */
5422       case IllegalMove:         /* bug or odd chess variant */
5423         *fromX = currentMoveString[0] - AAA;
5424         *fromY = currentMoveString[1] - ONE;
5425         *toX = currentMoveString[2] - AAA;
5426         *toY = currentMoveString[3] - ONE;
5427         *promoChar = currentMoveString[4];
5428         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5429             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5430     if (appData.debugMode) {
5431         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5432     }
5433             *fromX = *fromY = *toX = *toY = 0;
5434             return FALSE;
5435         }
5436         if (appData.testLegality) {
5437           return (*moveType != IllegalMove);
5438         } else {
5439           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5440                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5441         }
5442
5443       case WhiteDrop:
5444       case BlackDrop:
5445         *fromX = *moveType == WhiteDrop ?
5446           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5447           (int) CharToPiece(ToLower(currentMoveString[0]));
5448         *fromY = DROP_RANK;
5449         *toX = currentMoveString[2] - AAA;
5450         *toY = currentMoveString[3] - ONE;
5451         *promoChar = NULLCHAR;
5452         return TRUE;
5453
5454       case AmbiguousMove:
5455       case ImpossibleMove:
5456       case EndOfFile:
5457       case ElapsedTime:
5458       case Comment:
5459       case PGNTag:
5460       case NAG:
5461       case WhiteWins:
5462       case BlackWins:
5463       case GameIsDrawn:
5464       default:
5465     if (appData.debugMode) {
5466         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5467     }
5468         /* bug? */
5469         *fromX = *fromY = *toX = *toY = 0;
5470         *promoChar = NULLCHAR;
5471         return FALSE;
5472     }
5473 }
5474
5475 Boolean pushed = FALSE;
5476 char *lastParseAttempt;
5477
5478 void
5479 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5480 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5481   int fromX, fromY, toX, toY; char promoChar;
5482   ChessMove moveType;
5483   Boolean valid;
5484   int nr = 0;
5485
5486   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5487   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5488     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5489     pushed = TRUE;
5490   }
5491   endPV = forwardMostMove;
5492   do {
5493     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5494     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5495     lastParseAttempt = pv;
5496     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5497     if(!valid && nr == 0 &&
5498        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5499         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5500         // Hande case where played move is different from leading PV move
5501         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5502         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5503         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5504         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5505           endPV += 2; // if position different, keep this
5506           moveList[endPV-1][0] = fromX + AAA;
5507           moveList[endPV-1][1] = fromY + ONE;
5508           moveList[endPV-1][2] = toX + AAA;
5509           moveList[endPV-1][3] = toY + ONE;
5510           parseList[endPV-1][0] = NULLCHAR;
5511           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5512         }
5513       }
5514     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5515     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5516     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5517     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5518         valid++; // allow comments in PV
5519         continue;
5520     }
5521     nr++;
5522     if(endPV+1 > framePtr) break; // no space, truncate
5523     if(!valid) break;
5524     endPV++;
5525     CopyBoard(boards[endPV], boards[endPV-1]);
5526     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5527     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5528     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5529     CoordsToAlgebraic(boards[endPV - 1],
5530                              PosFlags(endPV - 1),
5531                              fromY, fromX, toY, toX, promoChar,
5532                              parseList[endPV - 1]);
5533   } while(valid);
5534   if(atEnd == 2) return; // used hidden, for PV conversion
5535   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5536   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5537   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5538                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5539   DrawPosition(TRUE, boards[currentMove]);
5540 }
5541
5542 int
5543 MultiPV (ChessProgramState *cps)
5544 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5545         int i;
5546         for(i=0; i<cps->nrOptions; i++)
5547             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5548                 return i;
5549         return -1;
5550 }
5551
5552 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5553
5554 Boolean
5555 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5556 {
5557         int startPV, multi, lineStart, origIndex = index;
5558         char *p, buf2[MSG_SIZ];
5559         ChessProgramState *cps = (pane ? &second : &first);
5560
5561         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5562         lastX = x; lastY = y;
5563         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5564         lineStart = startPV = index;
5565         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5566         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5567         index = startPV;
5568         do{ while(buf[index] && buf[index] != '\n') index++;
5569         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5570         buf[index] = 0;
5571         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5572                 int n = cps->option[multi].value;
5573                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5574                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5575                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5576                 cps->option[multi].value = n;
5577                 *start = *end = 0;
5578                 return FALSE;
5579         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5580                 ExcludeClick(origIndex - lineStart);
5581                 return FALSE;
5582         }
5583         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5584         *start = startPV; *end = index-1;
5585         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5586         return TRUE;
5587 }
5588
5589 char *
5590 PvToSAN (char *pv)
5591 {
5592         static char buf[10*MSG_SIZ];
5593         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5594         *buf = NULLCHAR;
5595         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5596         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5597         for(i = forwardMostMove; i<endPV; i++){
5598             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5599             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5600             k += strlen(buf+k);
5601         }
5602         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5603         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5604         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5605         endPV = savedEnd;
5606         return buf;
5607 }
5608
5609 Boolean
5610 LoadPV (int x, int y)
5611 { // called on right mouse click to load PV
5612   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5613   lastX = x; lastY = y;
5614   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5615   extendGame = FALSE;
5616   return TRUE;
5617 }
5618
5619 void
5620 UnLoadPV ()
5621 {
5622   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5623   if(endPV < 0) return;
5624   if(appData.autoCopyPV) CopyFENToClipboard();
5625   endPV = -1;
5626   if(extendGame && currentMove > forwardMostMove) {
5627         Boolean saveAnimate = appData.animate;
5628         if(pushed) {
5629             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5630                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5631             } else storedGames--; // abandon shelved tail of original game
5632         }
5633         pushed = FALSE;
5634         forwardMostMove = currentMove;
5635         currentMove = oldFMM;
5636         appData.animate = FALSE;
5637         ToNrEvent(forwardMostMove);
5638         appData.animate = saveAnimate;
5639   }
5640   currentMove = forwardMostMove;
5641   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5642   ClearPremoveHighlights();
5643   DrawPosition(TRUE, boards[currentMove]);
5644 }
5645
5646 void
5647 MovePV (int x, int y, int h)
5648 { // step through PV based on mouse coordinates (called on mouse move)
5649   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5650
5651   // we must somehow check if right button is still down (might be released off board!)
5652   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5653   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5654   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5655   if(!step) return;
5656   lastX = x; lastY = y;
5657
5658   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5659   if(endPV < 0) return;
5660   if(y < margin) step = 1; else
5661   if(y > h - margin) step = -1;
5662   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5663   currentMove += step;
5664   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5665   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5666                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5667   DrawPosition(FALSE, boards[currentMove]);
5668 }
5669
5670
5671 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5672 // All positions will have equal probability, but the current method will not provide a unique
5673 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5674 #define DARK 1
5675 #define LITE 2
5676 #define ANY 3
5677
5678 int squaresLeft[4];
5679 int piecesLeft[(int)BlackPawn];
5680 int seed, nrOfShuffles;
5681
5682 void
5683 GetPositionNumber ()
5684 {       // sets global variable seed
5685         int i;
5686
5687         seed = appData.defaultFrcPosition;
5688         if(seed < 0) { // randomize based on time for negative FRC position numbers
5689                 for(i=0; i<50; i++) seed += random();
5690                 seed = random() ^ random() >> 8 ^ random() << 8;
5691                 if(seed<0) seed = -seed;
5692         }
5693 }
5694
5695 int
5696 put (Board board, int pieceType, int rank, int n, int shade)
5697 // put the piece on the (n-1)-th empty squares of the given shade
5698 {
5699         int i;
5700
5701         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5702                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5703                         board[rank][i] = (ChessSquare) pieceType;
5704                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5705                         squaresLeft[ANY]--;
5706                         piecesLeft[pieceType]--;
5707                         return i;
5708                 }
5709         }
5710         return -1;
5711 }
5712
5713
5714 void
5715 AddOnePiece (Board board, int pieceType, int rank, int shade)
5716 // calculate where the next piece goes, (any empty square), and put it there
5717 {
5718         int i;
5719
5720         i = seed % squaresLeft[shade];
5721         nrOfShuffles *= squaresLeft[shade];
5722         seed /= squaresLeft[shade];
5723         put(board, pieceType, rank, i, shade);
5724 }
5725
5726 void
5727 AddTwoPieces (Board board, int pieceType, int rank)
5728 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5729 {
5730         int i, n=squaresLeft[ANY], j=n-1, k;
5731
5732         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5733         i = seed % k;  // pick one
5734         nrOfShuffles *= k;
5735         seed /= k;
5736         while(i >= j) i -= j--;
5737         j = n - 1 - j; i += j;
5738         put(board, pieceType, rank, j, ANY);
5739         put(board, pieceType, rank, i, ANY);
5740 }
5741
5742 void
5743 SetUpShuffle (Board board, int number)
5744 {
5745         int i, p, first=1;
5746
5747         GetPositionNumber(); nrOfShuffles = 1;
5748
5749         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5750         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5751         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5752
5753         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5754
5755         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5756             p = (int) board[0][i];
5757             if(p < (int) BlackPawn) piecesLeft[p] ++;
5758             board[0][i] = EmptySquare;
5759         }
5760
5761         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5762             // shuffles restricted to allow normal castling put KRR first
5763             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5764                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5765             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5766                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5767             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5768                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5769             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5770                 put(board, WhiteRook, 0, 0, ANY);
5771             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5772         }
5773
5774         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5775             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5776             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5777                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5778                 while(piecesLeft[p] >= 2) {
5779                     AddOnePiece(board, p, 0, LITE);
5780                     AddOnePiece(board, p, 0, DARK);
5781                 }
5782                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5783             }
5784
5785         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5786             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5787             // but we leave King and Rooks for last, to possibly obey FRC restriction
5788             if(p == (int)WhiteRook) continue;
5789             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5790             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5791         }
5792
5793         // now everything is placed, except perhaps King (Unicorn) and Rooks
5794
5795         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5796             // Last King gets castling rights
5797             while(piecesLeft[(int)WhiteUnicorn]) {
5798                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5799                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5800             }
5801
5802             while(piecesLeft[(int)WhiteKing]) {
5803                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5804                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5805             }
5806
5807
5808         } else {
5809             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5810             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5811         }
5812
5813         // Only Rooks can be left; simply place them all
5814         while(piecesLeft[(int)WhiteRook]) {
5815                 i = put(board, WhiteRook, 0, 0, ANY);
5816                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5817                         if(first) {
5818                                 first=0;
5819                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5820                         }
5821                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5822                 }
5823         }
5824         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5825             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5826         }
5827
5828         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5829 }
5830
5831 int
5832 SetCharTable (char *table, const char * map)
5833 /* [HGM] moved here from winboard.c because of its general usefulness */
5834 /*       Basically a safe strcpy that uses the last character as King */
5835 {
5836     int result = FALSE; int NrPieces;
5837
5838     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5839                     && NrPieces >= 12 && !(NrPieces&1)) {
5840         int i; /* [HGM] Accept even length from 12 to 34 */
5841
5842         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5843         for( i=0; i<NrPieces/2-1; i++ ) {
5844             table[i] = map[i];
5845             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5846         }
5847         table[(int) WhiteKing]  = map[NrPieces/2-1];
5848         table[(int) BlackKing]  = map[NrPieces-1];
5849
5850         result = TRUE;
5851     }
5852
5853     return result;
5854 }
5855
5856 void
5857 Prelude (Board board)
5858 {       // [HGM] superchess: random selection of exo-pieces
5859         int i, j, k; ChessSquare p;
5860         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5861
5862         GetPositionNumber(); // use FRC position number
5863
5864         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5865             SetCharTable(pieceToChar, appData.pieceToCharTable);
5866             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5867                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5868         }
5869
5870         j = seed%4;                 seed /= 4;
5871         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5872         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5873         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5874         j = seed%3 + (seed%3 >= j); seed /= 3;
5875         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5876         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5877         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5878         j = seed%3;                 seed /= 3;
5879         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5880         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5881         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5882         j = seed%2 + (seed%2 >= j); seed /= 2;
5883         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5884         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5885         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5886         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5887         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5888         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5889         put(board, exoPieces[0],    0, 0, ANY);
5890         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5891 }
5892
5893 void
5894 InitPosition (int redraw)
5895 {
5896     ChessSquare (* pieces)[BOARD_FILES];
5897     int i, j, pawnRow=1, pieceRows=1, overrule,
5898     oldx = gameInfo.boardWidth,
5899     oldy = gameInfo.boardHeight,
5900     oldh = gameInfo.holdingsWidth;
5901     static int oldv;
5902
5903     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5904
5905     /* [AS] Initialize pv info list [HGM] and game status */
5906     {
5907         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5908             pvInfoList[i].depth = 0;
5909             boards[i][EP_STATUS] = EP_NONE;
5910             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5911         }
5912
5913         initialRulePlies = 0; /* 50-move counter start */
5914
5915         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5916         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5917     }
5918
5919
5920     /* [HGM] logic here is completely changed. In stead of full positions */
5921     /* the initialized data only consist of the two backranks. The switch */
5922     /* selects which one we will use, which is than copied to the Board   */
5923     /* initialPosition, which for the rest is initialized by Pawns and    */
5924     /* empty squares. This initial position is then copied to boards[0],  */
5925     /* possibly after shuffling, so that it remains available.            */
5926
5927     gameInfo.holdingsWidth = 0; /* default board sizes */
5928     gameInfo.boardWidth    = 8;
5929     gameInfo.boardHeight   = 8;
5930     gameInfo.holdingsSize  = 0;
5931     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5932     for(i=0; i<BOARD_FILES-2; i++)
5933       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5934     initialPosition[EP_STATUS] = EP_NONE;
5935     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5936     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5937          SetCharTable(pieceNickName, appData.pieceNickNames);
5938     else SetCharTable(pieceNickName, "............");
5939     pieces = FIDEArray;
5940
5941     switch (gameInfo.variant) {
5942     case VariantFischeRandom:
5943       shuffleOpenings = TRUE;
5944     default:
5945       break;
5946     case VariantShatranj:
5947       pieces = ShatranjArray;
5948       nrCastlingRights = 0;
5949       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5950       break;
5951     case VariantMakruk:
5952       pieces = makrukArray;
5953       nrCastlingRights = 0;
5954       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5955       break;
5956     case VariantASEAN:
5957       pieces = aseanArray;
5958       nrCastlingRights = 0;
5959       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
5960       break;
5961     case VariantTwoKings:
5962       pieces = twoKingsArray;
5963       break;
5964     case VariantGrand:
5965       pieces = GrandArray;
5966       nrCastlingRights = 0;
5967       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5968       gameInfo.boardWidth = 10;
5969       gameInfo.boardHeight = 10;
5970       gameInfo.holdingsSize = 7;
5971       break;
5972     case VariantCapaRandom:
5973       shuffleOpenings = TRUE;
5974     case VariantCapablanca:
5975       pieces = CapablancaArray;
5976       gameInfo.boardWidth = 10;
5977       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5978       break;
5979     case VariantGothic:
5980       pieces = GothicArray;
5981       gameInfo.boardWidth = 10;
5982       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5983       break;
5984     case VariantSChess:
5985       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5986       gameInfo.holdingsSize = 7;
5987       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
5988       break;
5989     case VariantJanus:
5990       pieces = JanusArray;
5991       gameInfo.boardWidth = 10;
5992       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5993       nrCastlingRights = 6;
5994         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5995         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5996         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5997         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5998         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5999         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6000       break;
6001     case VariantFalcon:
6002       pieces = FalconArray;
6003       gameInfo.boardWidth = 10;
6004       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6005       break;
6006     case VariantXiangqi:
6007       pieces = XiangqiArray;
6008       gameInfo.boardWidth  = 9;
6009       gameInfo.boardHeight = 10;
6010       nrCastlingRights = 0;
6011       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6012       break;
6013     case VariantShogi:
6014       pieces = ShogiArray;
6015       gameInfo.boardWidth  = 9;
6016       gameInfo.boardHeight = 9;
6017       gameInfo.holdingsSize = 7;
6018       nrCastlingRights = 0;
6019       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6020       break;
6021     case VariantChu:
6022       pieces = ChuArray; pieceRows = 3;
6023       gameInfo.boardWidth  = 12;
6024       gameInfo.boardHeight = 12;
6025       nrCastlingRights = 0;
6026       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6027                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6028       break;
6029     case VariantCourier:
6030       pieces = CourierArray;
6031       gameInfo.boardWidth  = 12;
6032       nrCastlingRights = 0;
6033       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6034       break;
6035     case VariantKnightmate:
6036       pieces = KnightmateArray;
6037       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6038       break;
6039     case VariantSpartan:
6040       pieces = SpartanArray;
6041       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6042       break;
6043     case VariantLion:
6044       pieces = lionArray;
6045       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6046       break;
6047     case VariantFairy:
6048       pieces = fairyArray;
6049       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6050       break;
6051     case VariantGreat:
6052       pieces = GreatArray;
6053       gameInfo.boardWidth = 10;
6054       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6055       gameInfo.holdingsSize = 8;
6056       break;
6057     case VariantSuper:
6058       pieces = FIDEArray;
6059       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6060       gameInfo.holdingsSize = 8;
6061       startedFromSetupPosition = TRUE;
6062       break;
6063     case VariantCrazyhouse:
6064     case VariantBughouse:
6065       pieces = FIDEArray;
6066       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6067       gameInfo.holdingsSize = 5;
6068       break;
6069     case VariantWildCastle:
6070       pieces = FIDEArray;
6071       /* !!?shuffle with kings guaranteed to be on d or e file */
6072       shuffleOpenings = 1;
6073       break;
6074     case VariantNoCastle:
6075       pieces = FIDEArray;
6076       nrCastlingRights = 0;
6077       /* !!?unconstrained back-rank shuffle */
6078       shuffleOpenings = 1;
6079       break;
6080     }
6081
6082     overrule = 0;
6083     if(appData.NrFiles >= 0) {
6084         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6085         gameInfo.boardWidth = appData.NrFiles;
6086     }
6087     if(appData.NrRanks >= 0) {
6088         gameInfo.boardHeight = appData.NrRanks;
6089     }
6090     if(appData.holdingsSize >= 0) {
6091         i = appData.holdingsSize;
6092         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6093         gameInfo.holdingsSize = i;
6094     }
6095     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6096     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6097         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6098
6099     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6100     if(pawnRow < 1) pawnRow = 1;
6101     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
6102     if(gameInfo.variant == VariantChu) pawnRow = 3;
6103
6104     /* User pieceToChar list overrules defaults */
6105     if(appData.pieceToCharTable != NULL)
6106         SetCharTable(pieceToChar, appData.pieceToCharTable);
6107
6108     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6109
6110         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6111             s = (ChessSquare) 0; /* account holding counts in guard band */
6112         for( i=0; i<BOARD_HEIGHT; i++ )
6113             initialPosition[i][j] = s;
6114
6115         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6116         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
6117         initialPosition[pawnRow][j] = WhitePawn;
6118         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6119         if(gameInfo.variant == VariantXiangqi) {
6120             if(j&1) {
6121                 initialPosition[pawnRow][j] =
6122                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6123                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6124                    initialPosition[2][j] = WhiteCannon;
6125                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6126                 }
6127             }
6128         }
6129         if(gameInfo.variant == VariantChu) {
6130              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6131                initialPosition[pawnRow+1][j] = WhiteCobra,
6132                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6133              for(i=1; i<pieceRows; i++) {
6134                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6135                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6136              }
6137         }
6138         if(gameInfo.variant == VariantGrand) {
6139             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6140                initialPosition[0][j] = WhiteRook;
6141                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6142             }
6143         }
6144         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6145     }
6146     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6147
6148             j=BOARD_LEFT+1;
6149             initialPosition[1][j] = WhiteBishop;
6150             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6151             j=BOARD_RGHT-2;
6152             initialPosition[1][j] = WhiteRook;
6153             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6154     }
6155
6156     if( nrCastlingRights == -1) {
6157         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6158         /*       This sets default castling rights from none to normal corners   */
6159         /* Variants with other castling rights must set them themselves above    */
6160         nrCastlingRights = 6;
6161
6162         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6163         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6164         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6165         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6166         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6167         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6168      }
6169
6170      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6171      if(gameInfo.variant == VariantGreat) { // promotion commoners
6172         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6173         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6174         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6175         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6176      }
6177      if( gameInfo.variant == VariantSChess ) {
6178       initialPosition[1][0] = BlackMarshall;
6179       initialPosition[2][0] = BlackAngel;
6180       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6181       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6182       initialPosition[1][1] = initialPosition[2][1] =
6183       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6184      }
6185   if (appData.debugMode) {
6186     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6187   }
6188     if(shuffleOpenings) {
6189         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6190         startedFromSetupPosition = TRUE;
6191     }
6192     if(startedFromPositionFile) {
6193       /* [HGM] loadPos: use PositionFile for every new game */
6194       CopyBoard(initialPosition, filePosition);
6195       for(i=0; i<nrCastlingRights; i++)
6196           initialRights[i] = filePosition[CASTLING][i];
6197       startedFromSetupPosition = TRUE;
6198     }
6199
6200     CopyBoard(boards[0], initialPosition);
6201
6202     if(oldx != gameInfo.boardWidth ||
6203        oldy != gameInfo.boardHeight ||
6204        oldv != gameInfo.variant ||
6205        oldh != gameInfo.holdingsWidth
6206                                          )
6207             InitDrawingSizes(-2 ,0);
6208
6209     oldv = gameInfo.variant;
6210     if (redraw)
6211       DrawPosition(TRUE, boards[currentMove]);
6212 }
6213
6214 void
6215 SendBoard (ChessProgramState *cps, int moveNum)
6216 {
6217     char message[MSG_SIZ];
6218
6219     if (cps->useSetboard) {
6220       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6221       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6222       SendToProgram(message, cps);
6223       free(fen);
6224
6225     } else {
6226       ChessSquare *bp;
6227       int i, j, left=0, right=BOARD_WIDTH;
6228       /* Kludge to set black to move, avoiding the troublesome and now
6229        * deprecated "black" command.
6230        */
6231       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6232         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6233
6234       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6235
6236       SendToProgram("edit\n", cps);
6237       SendToProgram("#\n", cps);
6238       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6239         bp = &boards[moveNum][i][left];
6240         for (j = left; j < right; j++, bp++) {
6241           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6242           if ((int) *bp < (int) BlackPawn) {
6243             if(j == BOARD_RGHT+1)
6244                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6245             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6246             if(message[0] == '+' || message[0] == '~') {
6247               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6248                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6249                         AAA + j, ONE + i);
6250             }
6251             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6252                 message[1] = BOARD_RGHT   - 1 - j + '1';
6253                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6254             }
6255             SendToProgram(message, cps);
6256           }
6257         }
6258       }
6259
6260       SendToProgram("c\n", cps);
6261       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6262         bp = &boards[moveNum][i][left];
6263         for (j = left; j < right; j++, bp++) {
6264           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6265           if (((int) *bp != (int) EmptySquare)
6266               && ((int) *bp >= (int) BlackPawn)) {
6267             if(j == BOARD_LEFT-2)
6268                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6269             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6270                     AAA + j, ONE + i);
6271             if(message[0] == '+' || message[0] == '~') {
6272               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6273                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6274                         AAA + j, ONE + i);
6275             }
6276             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6277                 message[1] = BOARD_RGHT   - 1 - j + '1';
6278                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6279             }
6280             SendToProgram(message, cps);
6281           }
6282         }
6283       }
6284
6285       SendToProgram(".\n", cps);
6286     }
6287     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6288 }
6289
6290 char exclusionHeader[MSG_SIZ];
6291 int exCnt, excludePtr;
6292 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6293 static Exclusion excluTab[200];
6294 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6295
6296 static void
6297 WriteMap (int s)
6298 {
6299     int j;
6300     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6301     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6302 }
6303
6304 static void
6305 ClearMap ()
6306 {
6307     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6308     excludePtr = 24; exCnt = 0;
6309     WriteMap(0);
6310 }
6311
6312 static void
6313 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6314 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6315     char buf[2*MOVE_LEN], *p;
6316     Exclusion *e = excluTab;
6317     int i;
6318     for(i=0; i<exCnt; i++)
6319         if(e[i].ff == fromX && e[i].fr == fromY &&
6320            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6321     if(i == exCnt) { // was not in exclude list; add it
6322         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6323         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6324             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6325             return; // abort
6326         }
6327         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6328         excludePtr++; e[i].mark = excludePtr++;
6329         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6330         exCnt++;
6331     }
6332     exclusionHeader[e[i].mark] = state;
6333 }
6334
6335 static int
6336 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6337 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6338     char buf[MSG_SIZ];
6339     int j, k;
6340     ChessMove moveType;
6341     if((signed char)promoChar == -1) { // kludge to indicate best move
6342         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6343             return 1; // if unparsable, abort
6344     }
6345     // update exclusion map (resolving toggle by consulting existing state)
6346     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6347     j = k%8; k >>= 3;
6348     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6349     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6350          excludeMap[k] |=   1<<j;
6351     else excludeMap[k] &= ~(1<<j);
6352     // update header
6353     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6354     // inform engine
6355     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6356     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6357     SendToBoth(buf);
6358     return (state == '+');
6359 }
6360
6361 static void
6362 ExcludeClick (int index)
6363 {
6364     int i, j;
6365     Exclusion *e = excluTab;
6366     if(index < 25) { // none, best or tail clicked
6367         if(index < 13) { // none: include all
6368             WriteMap(0); // clear map
6369             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6370             SendToBoth("include all\n"); // and inform engine
6371         } else if(index > 18) { // tail
6372             if(exclusionHeader[19] == '-') { // tail was excluded
6373                 SendToBoth("include all\n");
6374                 WriteMap(0); // clear map completely
6375                 // now re-exclude selected moves
6376                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6377                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6378             } else { // tail was included or in mixed state
6379                 SendToBoth("exclude all\n");
6380                 WriteMap(0xFF); // fill map completely
6381                 // now re-include selected moves
6382                 j = 0; // count them
6383                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6384                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6385                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6386             }
6387         } else { // best
6388             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6389         }
6390     } else {
6391         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6392             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6393             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6394             break;
6395         }
6396     }
6397 }
6398
6399 ChessSquare
6400 DefaultPromoChoice (int white)
6401 {
6402     ChessSquare result;
6403     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6404        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6405         result = WhiteFerz; // no choice
6406     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6407         result= WhiteKing; // in Suicide Q is the last thing we want
6408     else if(gameInfo.variant == VariantSpartan)
6409         result = white ? WhiteQueen : WhiteAngel;
6410     else result = WhiteQueen;
6411     if(!white) result = WHITE_TO_BLACK result;
6412     return result;
6413 }
6414
6415 static int autoQueen; // [HGM] oneclick
6416
6417 int
6418 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6419 {
6420     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6421     /* [HGM] add Shogi promotions */
6422     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6423     ChessSquare piece;
6424     ChessMove moveType;
6425     Boolean premove;
6426
6427     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6428     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6429
6430     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6431       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6432         return FALSE;
6433
6434     piece = boards[currentMove][fromY][fromX];
6435     if(gameInfo.variant == VariantChu) {
6436         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6437         promotionZoneSize = BOARD_HEIGHT/3;
6438         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6439     } else if(gameInfo.variant == VariantShogi) {
6440         promotionZoneSize = BOARD_HEIGHT/3;
6441         highestPromotingPiece = (int)WhiteAlfil;
6442     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6443         promotionZoneSize = 3;
6444     }
6445
6446     // Treat Lance as Pawn when it is not representing Amazon
6447     if(gameInfo.variant != VariantSuper) {
6448         if(piece == WhiteLance) piece = WhitePawn; else
6449         if(piece == BlackLance) piece = BlackPawn;
6450     }
6451
6452     // next weed out all moves that do not touch the promotion zone at all
6453     if((int)piece >= BlackPawn) {
6454         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6455              return FALSE;
6456         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6457     } else {
6458         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6459            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6460     }
6461
6462     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6463
6464     // weed out mandatory Shogi promotions
6465     if(gameInfo.variant == VariantShogi) {
6466         if(piece >= BlackPawn) {
6467             if(toY == 0 && piece == BlackPawn ||
6468                toY == 0 && piece == BlackQueen ||
6469                toY <= 1 && piece == BlackKnight) {
6470                 *promoChoice = '+';
6471                 return FALSE;
6472             }
6473         } else {
6474             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6475                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6476                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6477                 *promoChoice = '+';
6478                 return FALSE;
6479             }
6480         }
6481     }
6482
6483     // weed out obviously illegal Pawn moves
6484     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6485         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6486         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6487         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6488         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6489         // note we are not allowed to test for valid (non-)capture, due to premove
6490     }
6491
6492     // we either have a choice what to promote to, or (in Shogi) whether to promote
6493     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6494        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6495         *promoChoice = PieceToChar(BlackFerz);  // no choice
6496         return FALSE;
6497     }
6498     // no sense asking what we must promote to if it is going to explode...
6499     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6500         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6501         return FALSE;
6502     }
6503     // give caller the default choice even if we will not make it
6504     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6505     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6506     if(        sweepSelect && gameInfo.variant != VariantGreat
6507                            && gameInfo.variant != VariantGrand
6508                            && gameInfo.variant != VariantSuper) return FALSE;
6509     if(autoQueen) return FALSE; // predetermined
6510
6511     // suppress promotion popup on illegal moves that are not premoves
6512     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6513               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6514     if(appData.testLegality && !premove) {
6515         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6516                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
6517         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6518             return FALSE;
6519     }
6520
6521     return TRUE;
6522 }
6523
6524 int
6525 InPalace (int row, int column)
6526 {   /* [HGM] for Xiangqi */
6527     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6528          column < (BOARD_WIDTH + 4)/2 &&
6529          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6530     return FALSE;
6531 }
6532
6533 int
6534 PieceForSquare (int x, int y)
6535 {
6536   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6537      return -1;
6538   else
6539      return boards[currentMove][y][x];
6540 }
6541
6542 int
6543 OKToStartUserMove (int x, int y)
6544 {
6545     ChessSquare from_piece;
6546     int white_piece;
6547
6548     if (matchMode) return FALSE;
6549     if (gameMode == EditPosition) return TRUE;
6550
6551     if (x >= 0 && y >= 0)
6552       from_piece = boards[currentMove][y][x];
6553     else
6554       from_piece = EmptySquare;
6555
6556     if (from_piece == EmptySquare) return FALSE;
6557
6558     white_piece = (int)from_piece >= (int)WhitePawn &&
6559       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6560
6561     switch (gameMode) {
6562       case AnalyzeFile:
6563       case TwoMachinesPlay:
6564       case EndOfGame:
6565         return FALSE;
6566
6567       case IcsObserving:
6568       case IcsIdle:
6569         return FALSE;
6570
6571       case MachinePlaysWhite:
6572       case IcsPlayingBlack:
6573         if (appData.zippyPlay) return FALSE;
6574         if (white_piece) {
6575             DisplayMoveError(_("You are playing Black"));
6576             return FALSE;
6577         }
6578         break;
6579
6580       case MachinePlaysBlack:
6581       case IcsPlayingWhite:
6582         if (appData.zippyPlay) return FALSE;
6583         if (!white_piece) {
6584             DisplayMoveError(_("You are playing White"));
6585             return FALSE;
6586         }
6587         break;
6588
6589       case PlayFromGameFile:
6590             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6591       case EditGame:
6592         if (!white_piece && WhiteOnMove(currentMove)) {
6593             DisplayMoveError(_("It is White's turn"));
6594             return FALSE;
6595         }
6596         if (white_piece && !WhiteOnMove(currentMove)) {
6597             DisplayMoveError(_("It is Black's turn"));
6598             return FALSE;
6599         }
6600         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6601             /* Editing correspondence game history */
6602             /* Could disallow this or prompt for confirmation */
6603             cmailOldMove = -1;
6604         }
6605         break;
6606
6607       case BeginningOfGame:
6608         if (appData.icsActive) return FALSE;
6609         if (!appData.noChessProgram) {
6610             if (!white_piece) {
6611                 DisplayMoveError(_("You are playing White"));
6612                 return FALSE;
6613             }
6614         }
6615         break;
6616
6617       case Training:
6618         if (!white_piece && WhiteOnMove(currentMove)) {
6619             DisplayMoveError(_("It is White's turn"));
6620             return FALSE;
6621         }
6622         if (white_piece && !WhiteOnMove(currentMove)) {
6623             DisplayMoveError(_("It is Black's turn"));
6624             return FALSE;
6625         }
6626         break;
6627
6628       default:
6629       case IcsExamining:
6630         break;
6631     }
6632     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6633         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6634         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6635         && gameMode != AnalyzeFile && gameMode != Training) {
6636         DisplayMoveError(_("Displayed position is not current"));
6637         return FALSE;
6638     }
6639     return TRUE;
6640 }
6641
6642 Boolean
6643 OnlyMove (int *x, int *y, Boolean captures)
6644 {
6645     DisambiguateClosure cl;
6646     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6647     switch(gameMode) {
6648       case MachinePlaysBlack:
6649       case IcsPlayingWhite:
6650       case BeginningOfGame:
6651         if(!WhiteOnMove(currentMove)) return FALSE;
6652         break;
6653       case MachinePlaysWhite:
6654       case IcsPlayingBlack:
6655         if(WhiteOnMove(currentMove)) return FALSE;
6656         break;
6657       case EditGame:
6658         break;
6659       default:
6660         return FALSE;
6661     }
6662     cl.pieceIn = EmptySquare;
6663     cl.rfIn = *y;
6664     cl.ffIn = *x;
6665     cl.rtIn = -1;
6666     cl.ftIn = -1;
6667     cl.promoCharIn = NULLCHAR;
6668     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6669     if( cl.kind == NormalMove ||
6670         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6671         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6672         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6673       fromX = cl.ff;
6674       fromY = cl.rf;
6675       *x = cl.ft;
6676       *y = cl.rt;
6677       return TRUE;
6678     }
6679     if(cl.kind != ImpossibleMove) return FALSE;
6680     cl.pieceIn = EmptySquare;
6681     cl.rfIn = -1;
6682     cl.ffIn = -1;
6683     cl.rtIn = *y;
6684     cl.ftIn = *x;
6685     cl.promoCharIn = NULLCHAR;
6686     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6687     if( cl.kind == NormalMove ||
6688         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6689         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6690         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6691       fromX = cl.ff;
6692       fromY = cl.rf;
6693       *x = cl.ft;
6694       *y = cl.rt;
6695       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6696       return TRUE;
6697     }
6698     return FALSE;
6699 }
6700
6701 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6702 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6703 int lastLoadGameUseList = FALSE;
6704 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6705 ChessMove lastLoadGameStart = EndOfFile;
6706 int doubleClick;
6707
6708 void
6709 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6710 {
6711     ChessMove moveType;
6712     ChessSquare pup;
6713     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6714
6715     /* Check if the user is playing in turn.  This is complicated because we
6716        let the user "pick up" a piece before it is his turn.  So the piece he
6717        tried to pick up may have been captured by the time he puts it down!
6718        Therefore we use the color the user is supposed to be playing in this
6719        test, not the color of the piece that is currently on the starting
6720        square---except in EditGame mode, where the user is playing both
6721        sides; fortunately there the capture race can't happen.  (It can
6722        now happen in IcsExamining mode, but that's just too bad.  The user
6723        will get a somewhat confusing message in that case.)
6724        */
6725
6726     switch (gameMode) {
6727       case AnalyzeFile:
6728       case TwoMachinesPlay:
6729       case EndOfGame:
6730       case IcsObserving:
6731       case IcsIdle:
6732         /* We switched into a game mode where moves are not accepted,
6733            perhaps while the mouse button was down. */
6734         return;
6735
6736       case MachinePlaysWhite:
6737         /* User is moving for Black */
6738         if (WhiteOnMove(currentMove)) {
6739             DisplayMoveError(_("It is White's turn"));
6740             return;
6741         }
6742         break;
6743
6744       case MachinePlaysBlack:
6745         /* User is moving for White */
6746         if (!WhiteOnMove(currentMove)) {
6747             DisplayMoveError(_("It is Black's turn"));
6748             return;
6749         }
6750         break;
6751
6752       case PlayFromGameFile:
6753             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6754       case EditGame:
6755       case IcsExamining:
6756       case BeginningOfGame:
6757       case AnalyzeMode:
6758       case Training:
6759         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6760         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6761             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6762             /* User is moving for Black */
6763             if (WhiteOnMove(currentMove)) {
6764                 DisplayMoveError(_("It is White's turn"));
6765                 return;
6766             }
6767         } else {
6768             /* User is moving for White */
6769             if (!WhiteOnMove(currentMove)) {
6770                 DisplayMoveError(_("It is Black's turn"));
6771                 return;
6772             }
6773         }
6774         break;
6775
6776       case IcsPlayingBlack:
6777         /* User is moving for Black */
6778         if (WhiteOnMove(currentMove)) {
6779             if (!appData.premove) {
6780                 DisplayMoveError(_("It is White's turn"));
6781             } else if (toX >= 0 && toY >= 0) {
6782                 premoveToX = toX;
6783                 premoveToY = toY;
6784                 premoveFromX = fromX;
6785                 premoveFromY = fromY;
6786                 premovePromoChar = promoChar;
6787                 gotPremove = 1;
6788                 if (appData.debugMode)
6789                     fprintf(debugFP, "Got premove: fromX %d,"
6790                             "fromY %d, toX %d, toY %d\n",
6791                             fromX, fromY, toX, toY);
6792             }
6793             return;
6794         }
6795         break;
6796
6797       case IcsPlayingWhite:
6798         /* User is moving for White */
6799         if (!WhiteOnMove(currentMove)) {
6800             if (!appData.premove) {
6801                 DisplayMoveError(_("It is Black's turn"));
6802             } else if (toX >= 0 && toY >= 0) {
6803                 premoveToX = toX;
6804                 premoveToY = toY;
6805                 premoveFromX = fromX;
6806                 premoveFromY = fromY;
6807                 premovePromoChar = promoChar;
6808                 gotPremove = 1;
6809                 if (appData.debugMode)
6810                     fprintf(debugFP, "Got premove: fromX %d,"
6811                             "fromY %d, toX %d, toY %d\n",
6812                             fromX, fromY, toX, toY);
6813             }
6814             return;
6815         }
6816         break;
6817
6818       default:
6819         break;
6820
6821       case EditPosition:
6822         /* EditPosition, empty square, or different color piece;
6823            click-click move is possible */
6824         if (toX == -2 || toY == -2) {
6825             boards[0][fromY][fromX] = EmptySquare;
6826             DrawPosition(FALSE, boards[currentMove]);
6827             return;
6828         } else if (toX >= 0 && toY >= 0) {
6829             boards[0][toY][toX] = boards[0][fromY][fromX];
6830             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6831                 if(boards[0][fromY][0] != EmptySquare) {
6832                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6833                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6834                 }
6835             } else
6836             if(fromX == BOARD_RGHT+1) {
6837                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6838                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6839                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6840                 }
6841             } else
6842             boards[0][fromY][fromX] = gatingPiece;
6843             DrawPosition(FALSE, boards[currentMove]);
6844             return;
6845         }
6846         return;
6847     }
6848
6849     if(toX < 0 || toY < 0) return;
6850     pup = boards[currentMove][toY][toX];
6851
6852     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6853     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6854          if( pup != EmptySquare ) return;
6855          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6856            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6857                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6858            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6859            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6860            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6861            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6862          fromY = DROP_RANK;
6863     }
6864
6865     /* [HGM] always test for legality, to get promotion info */
6866     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6867                                          fromY, fromX, toY, toX, promoChar);
6868
6869     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6870
6871     /* [HGM] but possibly ignore an IllegalMove result */
6872     if (appData.testLegality) {
6873         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6874             DisplayMoveError(_("Illegal move"));
6875             return;
6876         }
6877     }
6878
6879     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6880         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6881              ClearPremoveHighlights(); // was included
6882         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6883         return;
6884     }
6885
6886     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6887 }
6888
6889 /* Common tail of UserMoveEvent and DropMenuEvent */
6890 int
6891 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6892 {
6893     char *bookHit = 0;
6894
6895     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6896         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6897         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6898         if(WhiteOnMove(currentMove)) {
6899             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6900         } else {
6901             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6902         }
6903     }
6904
6905     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6906        move type in caller when we know the move is a legal promotion */
6907     if(moveType == NormalMove && promoChar)
6908         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6909
6910     /* [HGM] <popupFix> The following if has been moved here from
6911        UserMoveEvent(). Because it seemed to belong here (why not allow
6912        piece drops in training games?), and because it can only be
6913        performed after it is known to what we promote. */
6914     if (gameMode == Training) {
6915       /* compare the move played on the board to the next move in the
6916        * game. If they match, display the move and the opponent's response.
6917        * If they don't match, display an error message.
6918        */
6919       int saveAnimate;
6920       Board testBoard;
6921       CopyBoard(testBoard, boards[currentMove]);
6922       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6923
6924       if (CompareBoards(testBoard, boards[currentMove+1])) {
6925         ForwardInner(currentMove+1);
6926
6927         /* Autoplay the opponent's response.
6928          * if appData.animate was TRUE when Training mode was entered,
6929          * the response will be animated.
6930          */
6931         saveAnimate = appData.animate;
6932         appData.animate = animateTraining;
6933         ForwardInner(currentMove+1);
6934         appData.animate = saveAnimate;
6935
6936         /* check for the end of the game */
6937         if (currentMove >= forwardMostMove) {
6938           gameMode = PlayFromGameFile;
6939           ModeHighlight();
6940           SetTrainingModeOff();
6941           DisplayInformation(_("End of game"));
6942         }
6943       } else {
6944         DisplayError(_("Incorrect move"), 0);
6945       }
6946       return 1;
6947     }
6948
6949   /* Ok, now we know that the move is good, so we can kill
6950      the previous line in Analysis Mode */
6951   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6952                                 && currentMove < forwardMostMove) {
6953     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6954     else forwardMostMove = currentMove;
6955   }
6956
6957   ClearMap();
6958
6959   /* If we need the chess program but it's dead, restart it */
6960   ResurrectChessProgram();
6961
6962   /* A user move restarts a paused game*/
6963   if (pausing)
6964     PauseEvent();
6965
6966   thinkOutput[0] = NULLCHAR;
6967
6968   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6969
6970   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6971     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6972     return 1;
6973   }
6974
6975   if (gameMode == BeginningOfGame) {
6976     if (appData.noChessProgram) {
6977       gameMode = EditGame;
6978       SetGameInfo();
6979     } else {
6980       char buf[MSG_SIZ];
6981       gameMode = MachinePlaysBlack;
6982       StartClocks();
6983       SetGameInfo();
6984       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6985       DisplayTitle(buf);
6986       if (first.sendName) {
6987         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6988         SendToProgram(buf, &first);
6989       }
6990       StartClocks();
6991     }
6992     ModeHighlight();
6993   }
6994
6995   /* Relay move to ICS or chess engine */
6996   if (appData.icsActive) {
6997     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6998         gameMode == IcsExamining) {
6999       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7000         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7001         SendToICS("draw ");
7002         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7003       }
7004       // also send plain move, in case ICS does not understand atomic claims
7005       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7006       ics_user_moved = 1;
7007     }
7008   } else {
7009     if (first.sendTime && (gameMode == BeginningOfGame ||
7010                            gameMode == MachinePlaysWhite ||
7011                            gameMode == MachinePlaysBlack)) {
7012       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7013     }
7014     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7015          // [HGM] book: if program might be playing, let it use book
7016         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7017         first.maybeThinking = TRUE;
7018     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7019         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7020         SendBoard(&first, currentMove+1);
7021         if(second.analyzing) {
7022             if(!second.useSetboard) SendToProgram("undo\n", &second);
7023             SendBoard(&second, currentMove+1);
7024         }
7025     } else {
7026         SendMoveToProgram(forwardMostMove-1, &first);
7027         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7028     }
7029     if (currentMove == cmailOldMove + 1) {
7030       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7031     }
7032   }
7033
7034   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7035
7036   switch (gameMode) {
7037   case EditGame:
7038     if(appData.testLegality)
7039     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7040     case MT_NONE:
7041     case MT_CHECK:
7042       break;
7043     case MT_CHECKMATE:
7044     case MT_STAINMATE:
7045       if (WhiteOnMove(currentMove)) {
7046         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7047       } else {
7048         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7049       }
7050       break;
7051     case MT_STALEMATE:
7052       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7053       break;
7054     }
7055     break;
7056
7057   case MachinePlaysBlack:
7058   case MachinePlaysWhite:
7059     /* disable certain menu options while machine is thinking */
7060     SetMachineThinkingEnables();
7061     break;
7062
7063   default:
7064     break;
7065   }
7066
7067   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7068   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7069
7070   if(bookHit) { // [HGM] book: simulate book reply
7071         static char bookMove[MSG_SIZ]; // a bit generous?
7072
7073         programStats.nodes = programStats.depth = programStats.time =
7074         programStats.score = programStats.got_only_move = 0;
7075         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7076
7077         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7078         strcat(bookMove, bookHit);
7079         HandleMachineMove(bookMove, &first);
7080   }
7081   return 1;
7082 }
7083
7084 void
7085 MarkByFEN(char *fen)
7086 {
7087         int r, f;
7088         if(!appData.markers || !appData.highlightDragging) return;
7089         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7090         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7091         while(*fen) {
7092             int s = 0;
7093             marker[r][f] = 0;
7094             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7095             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7096             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7097             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7098             if(*fen == 'T') marker[r][f++] = 0; else
7099             if(*fen == 'Y') marker[r][f++] = 1; else
7100             if(*fen == 'G') marker[r][f++] = 3; else
7101             if(*fen == 'B') marker[r][f++] = 4; else
7102             if(*fen == 'C') marker[r][f++] = 5; else
7103             if(*fen == 'M') marker[r][f++] = 6; else
7104             if(*fen == 'W') marker[r][f++] = 7; else
7105             if(*fen == 'D') marker[r][f++] = 8; else
7106             if(*fen == 'R') marker[r][f++] = 2; else {
7107                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7108               f += s; fen -= s>0;
7109             }
7110             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7111             if(r < 0) break;
7112             fen++;
7113         }
7114         DrawPosition(TRUE, NULL);
7115 }
7116
7117 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7118
7119 void
7120 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7121 {
7122     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7123     Markers *m = (Markers *) closure;
7124     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7125         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7126                          || kind == WhiteCapturesEnPassant
7127                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7128     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7129 }
7130
7131 void
7132 MarkTargetSquares (int clear)
7133 {
7134   int x, y, sum=0;
7135   if(clear) { // no reason to ever suppress clearing
7136     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = baseMarker[y][x] = 0;
7137     if(!sum) return; // nothing was cleared,no redraw needed
7138   } else {
7139     int capt = 0;
7140     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7141        !appData.testLegality || gameMode == EditPosition) return;
7142     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7143     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7144       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7145       if(capt)
7146       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7147     }
7148   }
7149   DrawPosition(FALSE, NULL);
7150 }
7151
7152 int
7153 Explode (Board board, int fromX, int fromY, int toX, int toY)
7154 {
7155     if(gameInfo.variant == VariantAtomic &&
7156        (board[toY][toX] != EmptySquare ||                     // capture?
7157         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7158                          board[fromY][fromX] == BlackPawn   )
7159       )) {
7160         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7161         return TRUE;
7162     }
7163     return FALSE;
7164 }
7165
7166 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7167
7168 int
7169 CanPromote (ChessSquare piece, int y)
7170 {
7171         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7172         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7173         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7174            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7175            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7176          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7177         return (piece == BlackPawn && y == 1 ||
7178                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
7179                 piece == BlackLance && y == 1 ||
7180                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7181 }
7182
7183 void
7184 HoverEvent (int xPix, int yPix, int x, int y)
7185 {
7186         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7187         int r, f;
7188         if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
7189         if(!first.highlight) return;
7190         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7191         if(x == oldX && y == oldY) return; // only do something if we enter new square
7192         oldFromX = fromX; oldFromY = fromY;
7193         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
7194           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7195             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7196         else if(oldX != x || oldY != y) {
7197           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7198           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7199             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7200           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7201             char buf[MSG_SIZ];
7202             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7203             SendToProgram(buf, &first);
7204           }
7205           oldX = x; oldY = y;
7206 //        SetHighlights(fromX, fromY, x, y);
7207         }
7208 }
7209
7210 void ReportClick(char *action, int x, int y)
7211 {
7212         char buf[MSG_SIZ]; // Inform engine of what user does
7213         int r, f;
7214         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7215           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7216         if(!first.highlight || gameMode == EditPosition) return;
7217         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7218         SendToProgram(buf, &first);
7219 }
7220
7221 void
7222 LeftClick (ClickType clickType, int xPix, int yPix)
7223 {
7224     int x, y;
7225     Boolean saveAnimate;
7226     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7227     char promoChoice = NULLCHAR;
7228     ChessSquare piece;
7229     static TimeMark lastClickTime, prevClickTime;
7230
7231     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7232
7233     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7234
7235     if (clickType == Press) ErrorPopDown();
7236     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7237
7238     x = EventToSquare(xPix, BOARD_WIDTH);
7239     y = EventToSquare(yPix, BOARD_HEIGHT);
7240     if (!flipView && y >= 0) {
7241         y = BOARD_HEIGHT - 1 - y;
7242     }
7243     if (flipView && x >= 0) {
7244         x = BOARD_WIDTH - 1 - x;
7245     }
7246
7247     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7248         defaultPromoChoice = promoSweep;
7249         promoSweep = EmptySquare;   // terminate sweep
7250         promoDefaultAltered = TRUE;
7251         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7252     }
7253
7254     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7255         if(clickType == Release) return; // ignore upclick of click-click destination
7256         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7257         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7258         if(gameInfo.holdingsWidth &&
7259                 (WhiteOnMove(currentMove)
7260                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7261                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7262             // click in right holdings, for determining promotion piece
7263             ChessSquare p = boards[currentMove][y][x];
7264             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7265             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7266             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7267                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7268                 fromX = fromY = -1;
7269                 return;
7270             }
7271         }
7272         DrawPosition(FALSE, boards[currentMove]);
7273         return;
7274     }
7275
7276     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7277     if(clickType == Press
7278             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7279               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7280               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7281         return;
7282
7283     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7284         // could be static click on premove from-square: abort premove
7285         gotPremove = 0;
7286         ClearPremoveHighlights();
7287     }
7288
7289     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7290         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7291
7292     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7293         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7294                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7295         defaultPromoChoice = DefaultPromoChoice(side);
7296     }
7297
7298     autoQueen = appData.alwaysPromoteToQueen;
7299
7300     if (fromX == -1) {
7301       int originalY = y;
7302       gatingPiece = EmptySquare;
7303       if (clickType != Press) {
7304         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7305             DragPieceEnd(xPix, yPix); dragging = 0;
7306             DrawPosition(FALSE, NULL);
7307         }
7308         return;
7309       }
7310       doubleClick = FALSE;
7311       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7312         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7313       }
7314       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7315       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7316          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7317          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7318             /* First square */
7319             if (OKToStartUserMove(fromX, fromY)) {
7320                 second = 0;
7321                 ReportClick("lift", x, y);
7322                 MarkTargetSquares(0);
7323                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7324                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7325                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7326                     promoSweep = defaultPromoChoice;
7327                     selectFlag = 0; lastX = xPix; lastY = yPix;
7328                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7329                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7330                 }
7331                 if (appData.highlightDragging) {
7332                     SetHighlights(fromX, fromY, -1, -1);
7333                 } else {
7334                     ClearHighlights();
7335                 }
7336             } else fromX = fromY = -1;
7337             return;
7338         }
7339     }
7340
7341     /* fromX != -1 */
7342     if (clickType == Press && gameMode != EditPosition) {
7343         ChessSquare fromP;
7344         ChessSquare toP;
7345         int frc;
7346
7347         // ignore off-board to clicks
7348         if(y < 0 || x < 0) return;
7349
7350         /* Check if clicking again on the same color piece */
7351         fromP = boards[currentMove][fromY][fromX];
7352         toP = boards[currentMove][y][x];
7353         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7354         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7355            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7356              WhitePawn <= toP && toP <= WhiteKing &&
7357              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7358              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7359             (BlackPawn <= fromP && fromP <= BlackKing &&
7360              BlackPawn <= toP && toP <= BlackKing &&
7361              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7362              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7363             /* Clicked again on same color piece -- changed his mind */
7364             second = (x == fromX && y == fromY);
7365             killX = killY = -1;
7366             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7367                 second = FALSE; // first double-click rather than scond click
7368                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7369             }
7370             promoDefaultAltered = FALSE;
7371             MarkTargetSquares(1);
7372            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7373             if (appData.highlightDragging) {
7374                 SetHighlights(x, y, -1, -1);
7375             } else {
7376                 ClearHighlights();
7377             }
7378             if (OKToStartUserMove(x, y)) {
7379                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7380                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7381                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7382                  gatingPiece = boards[currentMove][fromY][fromX];
7383                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7384                 fromX = x;
7385                 fromY = y; dragging = 1;
7386                 ReportClick("lift", x, y);
7387                 MarkTargetSquares(0);
7388                 DragPieceBegin(xPix, yPix, FALSE);
7389                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7390                     promoSweep = defaultPromoChoice;
7391                     selectFlag = 0; lastX = xPix; lastY = yPix;
7392                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7393                 }
7394             }
7395            }
7396            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7397            second = FALSE;
7398         }
7399         // ignore clicks on holdings
7400         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7401     }
7402
7403     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7404         DragPieceEnd(xPix, yPix); dragging = 0;
7405         if(clearFlag) {
7406             // a deferred attempt to click-click move an empty square on top of a piece
7407             boards[currentMove][y][x] = EmptySquare;
7408             ClearHighlights();
7409             DrawPosition(FALSE, boards[currentMove]);
7410             fromX = fromY = -1; clearFlag = 0;
7411             return;
7412         }
7413         if (appData.animateDragging) {
7414             /* Undo animation damage if any */
7415             DrawPosition(FALSE, NULL);
7416         }
7417         if (second || sweepSelecting) {
7418             /* Second up/down in same square; just abort move */
7419             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7420             second = sweepSelecting = 0;
7421             fromX = fromY = -1;
7422             gatingPiece = EmptySquare;
7423             MarkTargetSquares(1);
7424             ClearHighlights();
7425             gotPremove = 0;
7426             ClearPremoveHighlights();
7427         } else {
7428             /* First upclick in same square; start click-click mode */
7429             SetHighlights(x, y, -1, -1);
7430         }
7431         return;
7432     }
7433
7434     clearFlag = 0;
7435
7436     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7437         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7438         DisplayMessage(_("only marked squares are legal"),"");
7439         DrawPosition(TRUE, NULL);
7440         return; // ignore to-click
7441     }
7442
7443     /* we now have a different from- and (possibly off-board) to-square */
7444     /* Completed move */
7445     if(!sweepSelecting) {
7446         toX = x;
7447         toY = y;
7448     }
7449
7450     saveAnimate = appData.animate;
7451     if (clickType == Press) {
7452         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7453             // must be Edit Position mode with empty-square selected
7454             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7455             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7456             return;
7457         }
7458         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7459             dragging = 1;
7460             return;
7461         }
7462         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7463             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7464         } else
7465         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7466         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7467           if(appData.sweepSelect) {
7468             ChessSquare piece = boards[currentMove][fromY][fromX];
7469             promoSweep = defaultPromoChoice;
7470             if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7471             selectFlag = 0; lastX = xPix; lastY = yPix;
7472             Sweep(0); // Pawn that is going to promote: preview promotion piece
7473             sweepSelecting = 1;
7474             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7475             MarkTargetSquares(1);
7476           }
7477           return; // promo popup appears on up-click
7478         }
7479         /* Finish clickclick move */
7480         if (appData.animate || appData.highlightLastMove) {
7481             SetHighlights(fromX, fromY, toX, toY);
7482         } else {
7483             ClearHighlights();
7484         }
7485     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7486         sweepSelecting = 0;
7487         if (appData.animate || appData.highlightLastMove) {
7488             SetHighlights(fromX, fromY, toX, toY);
7489         } else {
7490             ClearHighlights();
7491         }
7492     } else {
7493 #if 0
7494 // [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
7495         /* Finish drag move */
7496         if (appData.highlightLastMove) {
7497             SetHighlights(fromX, fromY, toX, toY);
7498         } else {
7499             ClearHighlights();
7500         }
7501 #endif
7502         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7503           dragging *= 2;            // flag button-less dragging if we are dragging
7504           MarkTargetSquares(1);
7505           if(x == killX && y == killY) killX = killY = -1; else {
7506             killX = x; killY = y;     //remeber this square as intermediate
7507             MarkTargetSquares(0);
7508             ReportClick("put", x, y); // and inform engine
7509             ReportClick("lift", x, y);
7510             return;
7511           }
7512         }
7513         DragPieceEnd(xPix, yPix); dragging = 0;
7514         /* Don't animate move and drag both */
7515         appData.animate = FALSE;
7516     }
7517
7518     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7519     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7520         ChessSquare piece = boards[currentMove][fromY][fromX];
7521         if(gameMode == EditPosition && piece != EmptySquare &&
7522            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7523             int n;
7524
7525             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7526                 n = PieceToNumber(piece - (int)BlackPawn);
7527                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7528                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7529                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7530             } else
7531             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7532                 n = PieceToNumber(piece);
7533                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7534                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7535                 boards[currentMove][n][BOARD_WIDTH-2]++;
7536             }
7537             boards[currentMove][fromY][fromX] = EmptySquare;
7538         }
7539         ClearHighlights();
7540         fromX = fromY = -1;
7541         MarkTargetSquares(1);
7542         DrawPosition(TRUE, boards[currentMove]);
7543         return;
7544     }
7545
7546     // off-board moves should not be highlighted
7547     if(x < 0 || y < 0) ClearHighlights();
7548     else ReportClick("put", x, y);
7549
7550     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7551
7552     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7553         SetHighlights(fromX, fromY, toX, toY);
7554         MarkTargetSquares(1);
7555         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7556             // [HGM] super: promotion to captured piece selected from holdings
7557             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7558             promotionChoice = TRUE;
7559             // kludge follows to temporarily execute move on display, without promoting yet
7560             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7561             boards[currentMove][toY][toX] = p;
7562             DrawPosition(FALSE, boards[currentMove]);
7563             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7564             boards[currentMove][toY][toX] = q;
7565             DisplayMessage("Click in holdings to choose piece", "");
7566             return;
7567         }
7568         PromotionPopUp();
7569     } else {
7570         int oldMove = currentMove;
7571         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7572         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7573         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7574         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7575            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7576             DrawPosition(TRUE, boards[currentMove]);
7577         MarkTargetSquares(1);
7578         fromX = fromY = -1;
7579     }
7580     appData.animate = saveAnimate;
7581     if (appData.animate || appData.animateDragging) {
7582         /* Undo animation damage if needed */
7583         DrawPosition(FALSE, NULL);
7584     }
7585 }
7586
7587 int
7588 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7589 {   // front-end-free part taken out of PieceMenuPopup
7590     int whichMenu; int xSqr, ySqr;
7591
7592     if(seekGraphUp) { // [HGM] seekgraph
7593         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7594         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7595         return -2;
7596     }
7597
7598     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7599          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7600         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7601         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7602         if(action == Press)   {
7603             originalFlip = flipView;
7604             flipView = !flipView; // temporarily flip board to see game from partners perspective
7605             DrawPosition(TRUE, partnerBoard);
7606             DisplayMessage(partnerStatus, "");
7607             partnerUp = TRUE;
7608         } else if(action == Release) {
7609             flipView = originalFlip;
7610             DrawPosition(TRUE, boards[currentMove]);
7611             partnerUp = FALSE;
7612         }
7613         return -2;
7614     }
7615
7616     xSqr = EventToSquare(x, BOARD_WIDTH);
7617     ySqr = EventToSquare(y, BOARD_HEIGHT);
7618     if (action == Release) {
7619         if(pieceSweep != EmptySquare) {
7620             EditPositionMenuEvent(pieceSweep, toX, toY);
7621             pieceSweep = EmptySquare;
7622         } else UnLoadPV(); // [HGM] pv
7623     }
7624     if (action != Press) return -2; // return code to be ignored
7625     switch (gameMode) {
7626       case IcsExamining:
7627         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7628       case EditPosition:
7629         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7630         if (xSqr < 0 || ySqr < 0) return -1;
7631         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7632         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7633         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7634         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7635         NextPiece(0);
7636         return 2; // grab
7637       case IcsObserving:
7638         if(!appData.icsEngineAnalyze) return -1;
7639       case IcsPlayingWhite:
7640       case IcsPlayingBlack:
7641         if(!appData.zippyPlay) goto noZip;
7642       case AnalyzeMode:
7643       case AnalyzeFile:
7644       case MachinePlaysWhite:
7645       case MachinePlaysBlack:
7646       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7647         if (!appData.dropMenu) {
7648           LoadPV(x, y);
7649           return 2; // flag front-end to grab mouse events
7650         }
7651         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7652            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7653       case EditGame:
7654       noZip:
7655         if (xSqr < 0 || ySqr < 0) return -1;
7656         if (!appData.dropMenu || appData.testLegality &&
7657             gameInfo.variant != VariantBughouse &&
7658             gameInfo.variant != VariantCrazyhouse) return -1;
7659         whichMenu = 1; // drop menu
7660         break;
7661       default:
7662         return -1;
7663     }
7664
7665     if (((*fromX = xSqr) < 0) ||
7666         ((*fromY = ySqr) < 0)) {
7667         *fromX = *fromY = -1;
7668         return -1;
7669     }
7670     if (flipView)
7671       *fromX = BOARD_WIDTH - 1 - *fromX;
7672     else
7673       *fromY = BOARD_HEIGHT - 1 - *fromY;
7674
7675     return whichMenu;
7676 }
7677
7678 void
7679 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7680 {
7681 //    char * hint = lastHint;
7682     FrontEndProgramStats stats;
7683
7684     stats.which = cps == &first ? 0 : 1;
7685     stats.depth = cpstats->depth;
7686     stats.nodes = cpstats->nodes;
7687     stats.score = cpstats->score;
7688     stats.time = cpstats->time;
7689     stats.pv = cpstats->movelist;
7690     stats.hint = lastHint;
7691     stats.an_move_index = 0;
7692     stats.an_move_count = 0;
7693
7694     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7695         stats.hint = cpstats->move_name;
7696         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7697         stats.an_move_count = cpstats->nr_moves;
7698     }
7699
7700     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
7701
7702     SetProgramStats( &stats );
7703 }
7704
7705 void
7706 ClearEngineOutputPane (int which)
7707 {
7708     static FrontEndProgramStats dummyStats;
7709     dummyStats.which = which;
7710     dummyStats.pv = "#";
7711     SetProgramStats( &dummyStats );
7712 }
7713
7714 #define MAXPLAYERS 500
7715
7716 char *
7717 TourneyStandings (int display)
7718 {
7719     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7720     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7721     char result, *p, *names[MAXPLAYERS];
7722
7723     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7724         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7725     names[0] = p = strdup(appData.participants);
7726     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7727
7728     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7729
7730     while(result = appData.results[nr]) {
7731         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7732         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7733         wScore = bScore = 0;
7734         switch(result) {
7735           case '+': wScore = 2; break;
7736           case '-': bScore = 2; break;
7737           case '=': wScore = bScore = 1; break;
7738           case ' ':
7739           case '*': return strdup("busy"); // tourney not finished
7740         }
7741         score[w] += wScore;
7742         score[b] += bScore;
7743         games[w]++;
7744         games[b]++;
7745         nr++;
7746     }
7747     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7748     for(w=0; w<nPlayers; w++) {
7749         bScore = -1;
7750         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7751         ranking[w] = b; points[w] = bScore; score[b] = -2;
7752     }
7753     p = malloc(nPlayers*34+1);
7754     for(w=0; w<nPlayers && w<display; w++)
7755         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7756     free(names[0]);
7757     return p;
7758 }
7759
7760 void
7761 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7762 {       // count all piece types
7763         int p, f, r;
7764         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7765         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7766         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7767                 p = board[r][f];
7768                 pCnt[p]++;
7769                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7770                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7771                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7772                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7773                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7774                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7775         }
7776 }
7777
7778 int
7779 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7780 {
7781         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7782         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7783
7784         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7785         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7786         if(myPawns == 2 && nMine == 3) // KPP
7787             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7788         if(myPawns == 1 && nMine == 2) // KP
7789             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7790         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7791             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7792         if(myPawns) return FALSE;
7793         if(pCnt[WhiteRook+side])
7794             return pCnt[BlackRook-side] ||
7795                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7796                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7797                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7798         if(pCnt[WhiteCannon+side]) {
7799             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7800             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7801         }
7802         if(pCnt[WhiteKnight+side])
7803             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7804         return FALSE;
7805 }
7806
7807 int
7808 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7809 {
7810         VariantClass v = gameInfo.variant;
7811
7812         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7813         if(v == VariantShatranj) return TRUE; // always winnable through baring
7814         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7815         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7816
7817         if(v == VariantXiangqi) {
7818                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7819
7820                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7821                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7822                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7823                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7824                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7825                 if(stale) // we have at least one last-rank P plus perhaps C
7826                     return majors // KPKX
7827                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7828                 else // KCA*E*
7829                     return pCnt[WhiteFerz+side] // KCAK
7830                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7831                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7832                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7833
7834         } else if(v == VariantKnightmate) {
7835                 if(nMine == 1) return FALSE;
7836                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7837         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7838                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7839
7840                 if(nMine == 1) return FALSE; // bare King
7841                 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
7842                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7843                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7844                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7845                 if(pCnt[WhiteKnight+side])
7846                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7847                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7848                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7849                 if(nBishops)
7850                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7851                 if(pCnt[WhiteAlfil+side])
7852                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7853                 if(pCnt[WhiteWazir+side])
7854                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7855         }
7856
7857         return TRUE;
7858 }
7859
7860 int
7861 CompareWithRights (Board b1, Board b2)
7862 {
7863     int rights = 0;
7864     if(!CompareBoards(b1, b2)) return FALSE;
7865     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7866     /* compare castling rights */
7867     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7868            rights++; /* King lost rights, while rook still had them */
7869     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7870         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7871            rights++; /* but at least one rook lost them */
7872     }
7873     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7874            rights++;
7875     if( b1[CASTLING][5] != NoRights ) {
7876         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7877            rights++;
7878     }
7879     return rights == 0;
7880 }
7881
7882 int
7883 Adjudicate (ChessProgramState *cps)
7884 {       // [HGM] some adjudications useful with buggy engines
7885         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7886         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7887         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7888         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7889         int k, drop, count = 0; static int bare = 1;
7890         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7891         Boolean canAdjudicate = !appData.icsActive;
7892
7893         // most tests only when we understand the game, i.e. legality-checking on
7894             if( appData.testLegality )
7895             {   /* [HGM] Some more adjudications for obstinate engines */
7896                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7897                 static int moveCount = 6;
7898                 ChessMove result;
7899                 char *reason = NULL;
7900
7901                 /* Count what is on board. */
7902                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7903
7904                 /* Some material-based adjudications that have to be made before stalemate test */
7905                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7906                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7907                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7908                      if(canAdjudicate && appData.checkMates) {
7909                          if(engineOpponent)
7910                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7911                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7912                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7913                          return 1;
7914                      }
7915                 }
7916
7917                 /* Bare King in Shatranj (loses) or Losers (wins) */
7918                 if( nrW == 1 || nrB == 1) {
7919                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7920                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7921                      if(canAdjudicate && appData.checkMates) {
7922                          if(engineOpponent)
7923                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7924                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7925                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7926                          return 1;
7927                      }
7928                   } else
7929                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7930                   {    /* bare King */
7931                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7932                         if(canAdjudicate && appData.checkMates) {
7933                             /* but only adjudicate if adjudication enabled */
7934                             if(engineOpponent)
7935                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7936                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7937                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7938                             return 1;
7939                         }
7940                   }
7941                 } else bare = 1;
7942
7943
7944             // don't wait for engine to announce game end if we can judge ourselves
7945             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7946               case MT_CHECK:
7947                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7948                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7949                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7950                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7951                             checkCnt++;
7952                         if(checkCnt >= 2) {
7953                             reason = "Xboard adjudication: 3rd check";
7954                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7955                             break;
7956                         }
7957                     }
7958                 }
7959               case MT_NONE:
7960               default:
7961                 break;
7962               case MT_STALEMATE:
7963               case MT_STAINMATE:
7964                 reason = "Xboard adjudication: Stalemate";
7965                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7966                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7967                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7968                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7969                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7970                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7971                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7972                                                                         EP_CHECKMATE : EP_WINS);
7973                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
7974                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7975                 }
7976                 break;
7977               case MT_CHECKMATE:
7978                 reason = "Xboard adjudication: Checkmate";
7979                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7980                 if(gameInfo.variant == VariantShogi) {
7981                     if(forwardMostMove > backwardMostMove
7982                        && moveList[forwardMostMove-1][1] == '@'
7983                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
7984                         reason = "XBoard adjudication: pawn-drop mate";
7985                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
7986                     }
7987                 }
7988                 break;
7989             }
7990
7991                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7992                     case EP_STALEMATE:
7993                         result = GameIsDrawn; break;
7994                     case EP_CHECKMATE:
7995                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7996                     case EP_WINS:
7997                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7998                     default:
7999                         result = EndOfFile;
8000                 }
8001                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8002                     if(engineOpponent)
8003                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8004                     GameEnds( result, reason, GE_XBOARD );
8005                     return 1;
8006                 }
8007
8008                 /* Next absolutely insufficient mating material. */
8009                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8010                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8011                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8012
8013                      /* always flag draws, for judging claims */
8014                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8015
8016                      if(canAdjudicate && appData.materialDraws) {
8017                          /* but only adjudicate them if adjudication enabled */
8018                          if(engineOpponent) {
8019                            SendToProgram("force\n", engineOpponent); // suppress reply
8020                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8021                          }
8022                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8023                          return 1;
8024                      }
8025                 }
8026
8027                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8028                 if(gameInfo.variant == VariantXiangqi ?
8029                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8030                  : nrW + nrB == 4 &&
8031                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8032                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8033                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8034                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8035                    ) ) {
8036                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8037                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8038                           if(engineOpponent) {
8039                             SendToProgram("force\n", engineOpponent); // suppress reply
8040                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8041                           }
8042                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8043                           return 1;
8044                      }
8045                 } else moveCount = 6;
8046             }
8047
8048         // Repetition draws and 50-move rule can be applied independently of legality testing
8049
8050                 /* Check for rep-draws */
8051                 count = 0;
8052                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8053                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8054                 for(k = forwardMostMove-2;
8055                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8056                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8057                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8058                     k-=2)
8059                 {   int rights=0;
8060                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8061                         /* compare castling rights */
8062                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8063                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8064                                 rights++; /* King lost rights, while rook still had them */
8065                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8066                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8067                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8068                                    rights++; /* but at least one rook lost them */
8069                         }
8070                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8071                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8072                                 rights++;
8073                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8074                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8075                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8076                                    rights++;
8077                         }
8078                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8079                             && appData.drawRepeats > 1) {
8080                              /* adjudicate after user-specified nr of repeats */
8081                              int result = GameIsDrawn;
8082                              char *details = "XBoard adjudication: repetition draw";
8083                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8084                                 // [HGM] xiangqi: check for forbidden perpetuals
8085                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8086                                 for(m=forwardMostMove; m>k; m-=2) {
8087                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8088                                         ourPerpetual = 0; // the current mover did not always check
8089                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8090                                         hisPerpetual = 0; // the opponent did not always check
8091                                 }
8092                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8093                                                                         ourPerpetual, hisPerpetual);
8094                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8095                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8096                                     details = "Xboard adjudication: perpetual checking";
8097                                 } else
8098                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8099                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8100                                 } else
8101                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8102                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8103                                         result = BlackWins;
8104                                         details = "Xboard adjudication: repetition";
8105                                     }
8106                                 } else // it must be XQ
8107                                 // Now check for perpetual chases
8108                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8109                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8110                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8111                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8112                                         static char resdet[MSG_SIZ];
8113                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8114                                         details = resdet;
8115                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8116                                     } else
8117                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8118                                         break; // Abort repetition-checking loop.
8119                                 }
8120                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8121                              }
8122                              if(engineOpponent) {
8123                                SendToProgram("force\n", engineOpponent); // suppress reply
8124                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8125                              }
8126                              GameEnds( result, details, GE_XBOARD );
8127                              return 1;
8128                         }
8129                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8130                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8131                     }
8132                 }
8133
8134                 /* Now we test for 50-move draws. Determine ply count */
8135                 count = forwardMostMove;
8136                 /* look for last irreversble move */
8137                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8138                     count--;
8139                 /* if we hit starting position, add initial plies */
8140                 if( count == backwardMostMove )
8141                     count -= initialRulePlies;
8142                 count = forwardMostMove - count;
8143                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8144                         // adjust reversible move counter for checks in Xiangqi
8145                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8146                         if(i < backwardMostMove) i = backwardMostMove;
8147                         while(i <= forwardMostMove) {
8148                                 lastCheck = inCheck; // check evasion does not count
8149                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8150                                 if(inCheck || lastCheck) count--; // check does not count
8151                                 i++;
8152                         }
8153                 }
8154                 if( count >= 100)
8155                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8156                          /* this is used to judge if draw claims are legal */
8157                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8158                          if(engineOpponent) {
8159                            SendToProgram("force\n", engineOpponent); // suppress reply
8160                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8161                          }
8162                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8163                          return 1;
8164                 }
8165
8166                 /* if draw offer is pending, treat it as a draw claim
8167                  * when draw condition present, to allow engines a way to
8168                  * claim draws before making their move to avoid a race
8169                  * condition occurring after their move
8170                  */
8171                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8172                          char *p = NULL;
8173                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8174                              p = "Draw claim: 50-move rule";
8175                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8176                              p = "Draw claim: 3-fold repetition";
8177                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8178                              p = "Draw claim: insufficient mating material";
8179                          if( p != NULL && canAdjudicate) {
8180                              if(engineOpponent) {
8181                                SendToProgram("force\n", engineOpponent); // suppress reply
8182                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8183                              }
8184                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8185                              return 1;
8186                          }
8187                 }
8188
8189                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8190                     if(engineOpponent) {
8191                       SendToProgram("force\n", engineOpponent); // suppress reply
8192                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8193                     }
8194                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8195                     return 1;
8196                 }
8197         return 0;
8198 }
8199
8200 char *
8201 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8202 {   // [HGM] book: this routine intercepts moves to simulate book replies
8203     char *bookHit = NULL;
8204
8205     //first determine if the incoming move brings opponent into his book
8206     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8207         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8208     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8209     if(bookHit != NULL && !cps->bookSuspend) {
8210         // make sure opponent is not going to reply after receiving move to book position
8211         SendToProgram("force\n", cps);
8212         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8213     }
8214     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8215     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8216     // now arrange restart after book miss
8217     if(bookHit) {
8218         // after a book hit we never send 'go', and the code after the call to this routine
8219         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8220         char buf[MSG_SIZ], *move = bookHit;
8221         if(cps->useSAN) {
8222             int fromX, fromY, toX, toY;
8223             char promoChar;
8224             ChessMove moveType;
8225             move = buf + 30;
8226             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8227                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8228                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8229                                     PosFlags(forwardMostMove),
8230                                     fromY, fromX, toY, toX, promoChar, move);
8231             } else {
8232                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8233                 bookHit = NULL;
8234             }
8235         }
8236         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8237         SendToProgram(buf, cps);
8238         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8239     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8240         SendToProgram("go\n", cps);
8241         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8242     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8243         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8244             SendToProgram("go\n", cps);
8245         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8246     }
8247     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8248 }
8249
8250 int
8251 LoadError (char *errmess, ChessProgramState *cps)
8252 {   // unloads engine and switches back to -ncp mode if it was first
8253     if(cps->initDone) return FALSE;
8254     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8255     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8256     cps->pr = NoProc;
8257     if(cps == &first) {
8258         appData.noChessProgram = TRUE;
8259         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8260         gameMode = BeginningOfGame; ModeHighlight();
8261         SetNCPMode();
8262     }
8263     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8264     DisplayMessage("", ""); // erase waiting message
8265     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8266     return TRUE;
8267 }
8268
8269 char *savedMessage;
8270 ChessProgramState *savedState;
8271 void
8272 DeferredBookMove (void)
8273 {
8274         if(savedState->lastPing != savedState->lastPong)
8275                     ScheduleDelayedEvent(DeferredBookMove, 10);
8276         else
8277         HandleMachineMove(savedMessage, savedState);
8278 }
8279
8280 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8281 static ChessProgramState *stalledEngine;
8282 static char stashedInputMove[MSG_SIZ];
8283
8284 void
8285 HandleMachineMove (char *message, ChessProgramState *cps)
8286 {
8287     static char firstLeg[20];
8288     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8289     char realname[MSG_SIZ];
8290     int fromX, fromY, toX, toY;
8291     ChessMove moveType;
8292     char promoChar, roar;
8293     char *p, *pv=buf1;
8294     int machineWhite, oldError;
8295     char *bookHit;
8296
8297     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8298         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8299         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8300             DisplayError(_("Invalid pairing from pairing engine"), 0);
8301             return;
8302         }
8303         pairingReceived = 1;
8304         NextMatchGame();
8305         return; // Skim the pairing messages here.
8306     }
8307
8308     oldError = cps->userError; cps->userError = 0;
8309
8310 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8311     /*
8312      * Kludge to ignore BEL characters
8313      */
8314     while (*message == '\007') message++;
8315
8316     /*
8317      * [HGM] engine debug message: ignore lines starting with '#' character
8318      */
8319     if(cps->debug && *message == '#') return;
8320
8321     /*
8322      * Look for book output
8323      */
8324     if (cps == &first && bookRequested) {
8325         if (message[0] == '\t' || message[0] == ' ') {
8326             /* Part of the book output is here; append it */
8327             strcat(bookOutput, message);
8328             strcat(bookOutput, "  \n");
8329             return;
8330         } else if (bookOutput[0] != NULLCHAR) {
8331             /* All of book output has arrived; display it */
8332             char *p = bookOutput;
8333             while (*p != NULLCHAR) {
8334                 if (*p == '\t') *p = ' ';
8335                 p++;
8336             }
8337             DisplayInformation(bookOutput);
8338             bookRequested = FALSE;
8339             /* Fall through to parse the current output */
8340         }
8341     }
8342
8343     /*
8344      * Look for machine move.
8345      */
8346     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8347         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8348     {
8349         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8350             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8351             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8352             stalledEngine = cps;
8353             if(appData.ponderNextMove) { // bring opponent out of ponder
8354                 if(gameMode == TwoMachinesPlay) {
8355                     if(cps->other->pause)
8356                         PauseEngine(cps->other);
8357                     else
8358                         SendToProgram("easy\n", cps->other);
8359                 }
8360             }
8361             StopClocks();
8362             return;
8363         }
8364
8365         /* This method is only useful on engines that support ping */
8366         if (cps->lastPing != cps->lastPong) {
8367           if (gameMode == BeginningOfGame) {
8368             /* Extra move from before last new; ignore */
8369             if (appData.debugMode) {
8370                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8371             }
8372           } else {
8373             if (appData.debugMode) {
8374                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8375                         cps->which, gameMode);
8376             }
8377
8378             SendToProgram("undo\n", cps);
8379           }
8380           return;
8381         }
8382
8383         switch (gameMode) {
8384           case BeginningOfGame:
8385             /* Extra move from before last reset; ignore */
8386             if (appData.debugMode) {
8387                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8388             }
8389             return;
8390
8391           case EndOfGame:
8392           case IcsIdle:
8393           default:
8394             /* Extra move after we tried to stop.  The mode test is
8395                not a reliable way of detecting this problem, but it's
8396                the best we can do on engines that don't support ping.
8397             */
8398             if (appData.debugMode) {
8399                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8400                         cps->which, gameMode);
8401             }
8402             SendToProgram("undo\n", cps);
8403             return;
8404
8405           case MachinePlaysWhite:
8406           case IcsPlayingWhite:
8407             machineWhite = TRUE;
8408             break;
8409
8410           case MachinePlaysBlack:
8411           case IcsPlayingBlack:
8412             machineWhite = FALSE;
8413             break;
8414
8415           case TwoMachinesPlay:
8416             machineWhite = (cps->twoMachinesColor[0] == 'w');
8417             break;
8418         }
8419         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8420             if (appData.debugMode) {
8421                 fprintf(debugFP,
8422                         "Ignoring move out of turn by %s, gameMode %d"
8423                         ", forwardMost %d\n",
8424                         cps->which, gameMode, forwardMostMove);
8425             }
8426             return;
8427         }
8428
8429         if(cps->alphaRank) AlphaRank(machineMove, 4);
8430
8431         // [HGM] lion: (some very limited) support for Alien protocol
8432         killX = killY = -1;
8433         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8434             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8435             return;
8436         } else if(firstLeg[0]) { // there was a previous leg;
8437             // only support case where same piece makes two step (and don't even test that!)
8438             char buf[20], *p = machineMove+1, *q = buf+1, f;
8439             safeStrCpy(buf, machineMove, 20);
8440             while(isdigit(*q)) q++; // find start of to-square
8441             safeStrCpy(machineMove, firstLeg, 20);
8442             while(isdigit(*p)) p++;
8443             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8444             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8445             firstLeg[0] = NULLCHAR;
8446         }
8447
8448         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8449                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8450             /* Machine move could not be parsed; ignore it. */
8451           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8452                     machineMove, _(cps->which));
8453             DisplayMoveError(buf1);
8454             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8455                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8456             if (gameMode == TwoMachinesPlay) {
8457               GameEnds(machineWhite ? BlackWins : WhiteWins,
8458                        buf1, GE_XBOARD);
8459             }
8460             return;
8461         }
8462
8463         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8464         /* So we have to redo legality test with true e.p. status here,  */
8465         /* to make sure an illegal e.p. capture does not slip through,   */
8466         /* to cause a forfeit on a justified illegal-move complaint      */
8467         /* of the opponent.                                              */
8468         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8469            ChessMove moveType;
8470            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8471                              fromY, fromX, toY, toX, promoChar);
8472             if(moveType == IllegalMove) {
8473               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8474                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8475                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8476                            buf1, GE_XBOARD);
8477                 return;
8478            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8479            /* [HGM] Kludge to handle engines that send FRC-style castling
8480               when they shouldn't (like TSCP-Gothic) */
8481            switch(moveType) {
8482              case WhiteASideCastleFR:
8483              case BlackASideCastleFR:
8484                toX+=2;
8485                currentMoveString[2]++;
8486                break;
8487              case WhiteHSideCastleFR:
8488              case BlackHSideCastleFR:
8489                toX--;
8490                currentMoveString[2]--;
8491                break;
8492              default: ; // nothing to do, but suppresses warning of pedantic compilers
8493            }
8494         }
8495         hintRequested = FALSE;
8496         lastHint[0] = NULLCHAR;
8497         bookRequested = FALSE;
8498         /* Program may be pondering now */
8499         cps->maybeThinking = TRUE;
8500         if (cps->sendTime == 2) cps->sendTime = 1;
8501         if (cps->offeredDraw) cps->offeredDraw--;
8502
8503         /* [AS] Save move info*/
8504         pvInfoList[ forwardMostMove ].score = programStats.score;
8505         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8506         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8507
8508         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8509
8510         /* Test suites abort the 'game' after one move */
8511         if(*appData.finger) {
8512            static FILE *f;
8513            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8514            if(!f) f = fopen(appData.finger, "w");
8515            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8516            else { DisplayFatalError("Bad output file", errno, 0); return; }
8517            free(fen);
8518            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8519         }
8520
8521         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8522         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8523             int count = 0;
8524
8525             while( count < adjudicateLossPlies ) {
8526                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8527
8528                 if( count & 1 ) {
8529                     score = -score; /* Flip score for winning side */
8530                 }
8531
8532                 if( score > adjudicateLossThreshold ) {
8533                     break;
8534                 }
8535
8536                 count++;
8537             }
8538
8539             if( count >= adjudicateLossPlies ) {
8540                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8541
8542                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8543                     "Xboard adjudication",
8544                     GE_XBOARD );
8545
8546                 return;
8547             }
8548         }
8549
8550         if(Adjudicate(cps)) {
8551             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8552             return; // [HGM] adjudicate: for all automatic game ends
8553         }
8554
8555 #if ZIPPY
8556         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8557             first.initDone) {
8558           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8559                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8560                 SendToICS("draw ");
8561                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8562           }
8563           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8564           ics_user_moved = 1;
8565           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8566                 char buf[3*MSG_SIZ];
8567
8568                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8569                         programStats.score / 100.,
8570                         programStats.depth,
8571                         programStats.time / 100.,
8572                         (unsigned int)programStats.nodes,
8573                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8574                         programStats.movelist);
8575                 SendToICS(buf);
8576 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8577           }
8578         }
8579 #endif
8580
8581         /* [AS] Clear stats for next move */
8582         ClearProgramStats();
8583         thinkOutput[0] = NULLCHAR;
8584         hiddenThinkOutputState = 0;
8585
8586         bookHit = NULL;
8587         if (gameMode == TwoMachinesPlay) {
8588             /* [HGM] relaying draw offers moved to after reception of move */
8589             /* and interpreting offer as claim if it brings draw condition */
8590             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8591                 SendToProgram("draw\n", cps->other);
8592             }
8593             if (cps->other->sendTime) {
8594                 SendTimeRemaining(cps->other,
8595                                   cps->other->twoMachinesColor[0] == 'w');
8596             }
8597             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8598             if (firstMove && !bookHit) {
8599                 firstMove = FALSE;
8600                 if (cps->other->useColors) {
8601                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8602                 }
8603                 SendToProgram("go\n", cps->other);
8604             }
8605             cps->other->maybeThinking = TRUE;
8606         }
8607
8608         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8609
8610         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8611
8612         if (!pausing && appData.ringBellAfterMoves) {
8613             if(!roar) RingBell();
8614         }
8615
8616         /*
8617          * Reenable menu items that were disabled while
8618          * machine was thinking
8619          */
8620         if (gameMode != TwoMachinesPlay)
8621             SetUserThinkingEnables();
8622
8623         // [HGM] book: after book hit opponent has received move and is now in force mode
8624         // force the book reply into it, and then fake that it outputted this move by jumping
8625         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8626         if(bookHit) {
8627                 static char bookMove[MSG_SIZ]; // a bit generous?
8628
8629                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8630                 strcat(bookMove, bookHit);
8631                 message = bookMove;
8632                 cps = cps->other;
8633                 programStats.nodes = programStats.depth = programStats.time =
8634                 programStats.score = programStats.got_only_move = 0;
8635                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8636
8637                 if(cps->lastPing != cps->lastPong) {
8638                     savedMessage = message; // args for deferred call
8639                     savedState = cps;
8640                     ScheduleDelayedEvent(DeferredBookMove, 10);
8641                     return;
8642                 }
8643                 goto FakeBookMove;
8644         }
8645
8646         return;
8647     }
8648
8649     /* Set special modes for chess engines.  Later something general
8650      *  could be added here; for now there is just one kludge feature,
8651      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8652      *  when "xboard" is given as an interactive command.
8653      */
8654     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8655         cps->useSigint = FALSE;
8656         cps->useSigterm = FALSE;
8657     }
8658     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8659       ParseFeatures(message+8, cps);
8660       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8661     }
8662
8663     if (!strncmp(message, "setup ", 6) && 
8664         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
8665                                         ) { // [HGM] allow first engine to define opening position
8666       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8667       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8668       *buf = NULLCHAR;
8669       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8670       if(startedFromSetupPosition) return;
8671       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8672       if(dummy >= 3) {
8673         while(message[s] && message[s++] != ' ');
8674         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8675            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8676             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8677             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8678           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8679           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8680         }
8681       }
8682       ParseFEN(boards[0], &dummy, message+s, FALSE);
8683       DrawPosition(TRUE, boards[0]);
8684       startedFromSetupPosition = TRUE;
8685       return;
8686     }
8687     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8688      * want this, I was asked to put it in, and obliged.
8689      */
8690     if (!strncmp(message, "setboard ", 9)) {
8691         Board initial_position;
8692
8693         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8694
8695         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8696             DisplayError(_("Bad FEN received from engine"), 0);
8697             return ;
8698         } else {
8699            Reset(TRUE, FALSE);
8700            CopyBoard(boards[0], initial_position);
8701            initialRulePlies = FENrulePlies;
8702            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8703            else gameMode = MachinePlaysBlack;
8704            DrawPosition(FALSE, boards[currentMove]);
8705         }
8706         return;
8707     }
8708
8709     /*
8710      * Look for communication commands
8711      */
8712     if (!strncmp(message, "telluser ", 9)) {
8713         if(message[9] == '\\' && message[10] == '\\')
8714             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8715         PlayTellSound();
8716         DisplayNote(message + 9);
8717         return;
8718     }
8719     if (!strncmp(message, "tellusererror ", 14)) {
8720         cps->userError = 1;
8721         if(message[14] == '\\' && message[15] == '\\')
8722             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8723         PlayTellSound();
8724         DisplayError(message + 14, 0);
8725         return;
8726     }
8727     if (!strncmp(message, "tellopponent ", 13)) {
8728       if (appData.icsActive) {
8729         if (loggedOn) {
8730           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8731           SendToICS(buf1);
8732         }
8733       } else {
8734         DisplayNote(message + 13);
8735       }
8736       return;
8737     }
8738     if (!strncmp(message, "tellothers ", 11)) {
8739       if (appData.icsActive) {
8740         if (loggedOn) {
8741           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8742           SendToICS(buf1);
8743         }
8744       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8745       return;
8746     }
8747     if (!strncmp(message, "tellall ", 8)) {
8748       if (appData.icsActive) {
8749         if (loggedOn) {
8750           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8751           SendToICS(buf1);
8752         }
8753       } else {
8754         DisplayNote(message + 8);
8755       }
8756       return;
8757     }
8758     if (strncmp(message, "warning", 7) == 0) {
8759         /* Undocumented feature, use tellusererror in new code */
8760         DisplayError(message, 0);
8761         return;
8762     }
8763     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8764         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8765         strcat(realname, " query");
8766         AskQuestion(realname, buf2, buf1, cps->pr);
8767         return;
8768     }
8769     /* Commands from the engine directly to ICS.  We don't allow these to be
8770      *  sent until we are logged on. Crafty kibitzes have been known to
8771      *  interfere with the login process.
8772      */
8773     if (loggedOn) {
8774         if (!strncmp(message, "tellics ", 8)) {
8775             SendToICS(message + 8);
8776             SendToICS("\n");
8777             return;
8778         }
8779         if (!strncmp(message, "tellicsnoalias ", 15)) {
8780             SendToICS(ics_prefix);
8781             SendToICS(message + 15);
8782             SendToICS("\n");
8783             return;
8784         }
8785         /* The following are for backward compatibility only */
8786         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8787             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8788             SendToICS(ics_prefix);
8789             SendToICS(message);
8790             SendToICS("\n");
8791             return;
8792         }
8793     }
8794     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8795         return;
8796     }
8797     if(!strncmp(message, "highlight ", 10)) {
8798         if(appData.testLegality && appData.markers) return;
8799         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8800         return;
8801     }
8802     if(!strncmp(message, "click ", 6)) {
8803         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8804         if(appData.testLegality || !appData.oneClick) return;
8805         sscanf(message+6, "%c%d%c", &f, &y, &c);
8806         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8807         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8808         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8809         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8810         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8811         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8812             LeftClick(Release, lastLeftX, lastLeftY);
8813         controlKey  = (c == ',');
8814         LeftClick(Press, x, y);
8815         LeftClick(Release, x, y);
8816         first.highlight = f;
8817         return;
8818     }
8819     /*
8820      * If the move is illegal, cancel it and redraw the board.
8821      * Also deal with other error cases.  Matching is rather loose
8822      * here to accommodate engines written before the spec.
8823      */
8824     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8825         strncmp(message, "Error", 5) == 0) {
8826         if (StrStr(message, "name") ||
8827             StrStr(message, "rating") || StrStr(message, "?") ||
8828             StrStr(message, "result") || StrStr(message, "board") ||
8829             StrStr(message, "bk") || StrStr(message, "computer") ||
8830             StrStr(message, "variant") || StrStr(message, "hint") ||
8831             StrStr(message, "random") || StrStr(message, "depth") ||
8832             StrStr(message, "accepted")) {
8833             return;
8834         }
8835         if (StrStr(message, "protover")) {
8836           /* Program is responding to input, so it's apparently done
8837              initializing, and this error message indicates it is
8838              protocol version 1.  So we don't need to wait any longer
8839              for it to initialize and send feature commands. */
8840           FeatureDone(cps, 1);
8841           cps->protocolVersion = 1;
8842           return;
8843         }
8844         cps->maybeThinking = FALSE;
8845
8846         if (StrStr(message, "draw")) {
8847             /* Program doesn't have "draw" command */
8848             cps->sendDrawOffers = 0;
8849             return;
8850         }
8851         if (cps->sendTime != 1 &&
8852             (StrStr(message, "time") || StrStr(message, "otim"))) {
8853           /* Program apparently doesn't have "time" or "otim" command */
8854           cps->sendTime = 0;
8855           return;
8856         }
8857         if (StrStr(message, "analyze")) {
8858             cps->analysisSupport = FALSE;
8859             cps->analyzing = FALSE;
8860 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8861             EditGameEvent(); // [HGM] try to preserve loaded game
8862             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8863             DisplayError(buf2, 0);
8864             return;
8865         }
8866         if (StrStr(message, "(no matching move)st")) {
8867           /* Special kludge for GNU Chess 4 only */
8868           cps->stKludge = TRUE;
8869           SendTimeControl(cps, movesPerSession, timeControl,
8870                           timeIncrement, appData.searchDepth,
8871                           searchTime);
8872           return;
8873         }
8874         if (StrStr(message, "(no matching move)sd")) {
8875           /* Special kludge for GNU Chess 4 only */
8876           cps->sdKludge = TRUE;
8877           SendTimeControl(cps, movesPerSession, timeControl,
8878                           timeIncrement, appData.searchDepth,
8879                           searchTime);
8880           return;
8881         }
8882         if (!StrStr(message, "llegal")) {
8883             return;
8884         }
8885         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8886             gameMode == IcsIdle) return;
8887         if (forwardMostMove <= backwardMostMove) return;
8888         if (pausing) PauseEvent();
8889       if(appData.forceIllegal) {
8890             // [HGM] illegal: machine refused move; force position after move into it
8891           SendToProgram("force\n", cps);
8892           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8893                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8894                 // when black is to move, while there might be nothing on a2 or black
8895                 // might already have the move. So send the board as if white has the move.
8896                 // But first we must change the stm of the engine, as it refused the last move
8897                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8898                 if(WhiteOnMove(forwardMostMove)) {
8899                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8900                     SendBoard(cps, forwardMostMove); // kludgeless board
8901                 } else {
8902                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8903                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8904                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8905                 }
8906           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8907             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8908                  gameMode == TwoMachinesPlay)
8909               SendToProgram("go\n", cps);
8910             return;
8911       } else
8912         if (gameMode == PlayFromGameFile) {
8913             /* Stop reading this game file */
8914             gameMode = EditGame;
8915             ModeHighlight();
8916         }
8917         /* [HGM] illegal-move claim should forfeit game when Xboard */
8918         /* only passes fully legal moves                            */
8919         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8920             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8921                                 "False illegal-move claim", GE_XBOARD );
8922             return; // do not take back move we tested as valid
8923         }
8924         currentMove = forwardMostMove-1;
8925         DisplayMove(currentMove-1); /* before DisplayMoveError */
8926         SwitchClocks(forwardMostMove-1); // [HGM] race
8927         DisplayBothClocks();
8928         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8929                 parseList[currentMove], _(cps->which));
8930         DisplayMoveError(buf1);
8931         DrawPosition(FALSE, boards[currentMove]);
8932
8933         SetUserThinkingEnables();
8934         return;
8935     }
8936     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8937         /* Program has a broken "time" command that
8938            outputs a string not ending in newline.
8939            Don't use it. */
8940         cps->sendTime = 0;
8941     }
8942
8943     /*
8944      * If chess program startup fails, exit with an error message.
8945      * Attempts to recover here are futile. [HGM] Well, we try anyway
8946      */
8947     if ((StrStr(message, "unknown host") != NULL)
8948         || (StrStr(message, "No remote directory") != NULL)
8949         || (StrStr(message, "not found") != NULL)
8950         || (StrStr(message, "No such file") != NULL)
8951         || (StrStr(message, "can't alloc") != NULL)
8952         || (StrStr(message, "Permission denied") != NULL)) {
8953
8954         cps->maybeThinking = FALSE;
8955         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8956                 _(cps->which), cps->program, cps->host, message);
8957         RemoveInputSource(cps->isr);
8958         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8959             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
8960             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
8961         }
8962         return;
8963     }
8964
8965     /*
8966      * Look for hint output
8967      */
8968     if (sscanf(message, "Hint: %s", buf1) == 1) {
8969         if (cps == &first && hintRequested) {
8970             hintRequested = FALSE;
8971             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8972                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8973                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8974                                     PosFlags(forwardMostMove),
8975                                     fromY, fromX, toY, toX, promoChar, buf1);
8976                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8977                 DisplayInformation(buf2);
8978             } else {
8979                 /* Hint move could not be parsed!? */
8980               snprintf(buf2, sizeof(buf2),
8981                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8982                         buf1, _(cps->which));
8983                 DisplayError(buf2, 0);
8984             }
8985         } else {
8986           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8987         }
8988         return;
8989     }
8990
8991     /*
8992      * Ignore other messages if game is not in progress
8993      */
8994     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8995         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8996
8997     /*
8998      * look for win, lose, draw, or draw offer
8999      */
9000     if (strncmp(message, "1-0", 3) == 0) {
9001         char *p, *q, *r = "";
9002         p = strchr(message, '{');
9003         if (p) {
9004             q = strchr(p, '}');
9005             if (q) {
9006                 *q = NULLCHAR;
9007                 r = p + 1;
9008             }
9009         }
9010         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9011         return;
9012     } else if (strncmp(message, "0-1", 3) == 0) {
9013         char *p, *q, *r = "";
9014         p = strchr(message, '{');
9015         if (p) {
9016             q = strchr(p, '}');
9017             if (q) {
9018                 *q = NULLCHAR;
9019                 r = p + 1;
9020             }
9021         }
9022         /* Kludge for Arasan 4.1 bug */
9023         if (strcmp(r, "Black resigns") == 0) {
9024             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9025             return;
9026         }
9027         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9028         return;
9029     } else if (strncmp(message, "1/2", 3) == 0) {
9030         char *p, *q, *r = "";
9031         p = strchr(message, '{');
9032         if (p) {
9033             q = strchr(p, '}');
9034             if (q) {
9035                 *q = NULLCHAR;
9036                 r = p + 1;
9037             }
9038         }
9039
9040         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9041         return;
9042
9043     } else if (strncmp(message, "White resign", 12) == 0) {
9044         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9045         return;
9046     } else if (strncmp(message, "Black resign", 12) == 0) {
9047         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9048         return;
9049     } else if (strncmp(message, "White matches", 13) == 0 ||
9050                strncmp(message, "Black matches", 13) == 0   ) {
9051         /* [HGM] ignore GNUShogi noises */
9052         return;
9053     } else if (strncmp(message, "White", 5) == 0 &&
9054                message[5] != '(' &&
9055                StrStr(message, "Black") == NULL) {
9056         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9057         return;
9058     } else if (strncmp(message, "Black", 5) == 0 &&
9059                message[5] != '(') {
9060         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9061         return;
9062     } else if (strcmp(message, "resign") == 0 ||
9063                strcmp(message, "computer resigns") == 0) {
9064         switch (gameMode) {
9065           case MachinePlaysBlack:
9066           case IcsPlayingBlack:
9067             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9068             break;
9069           case MachinePlaysWhite:
9070           case IcsPlayingWhite:
9071             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9072             break;
9073           case TwoMachinesPlay:
9074             if (cps->twoMachinesColor[0] == 'w')
9075               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9076             else
9077               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9078             break;
9079           default:
9080             /* can't happen */
9081             break;
9082         }
9083         return;
9084     } else if (strncmp(message, "opponent mates", 14) == 0) {
9085         switch (gameMode) {
9086           case MachinePlaysBlack:
9087           case IcsPlayingBlack:
9088             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9089             break;
9090           case MachinePlaysWhite:
9091           case IcsPlayingWhite:
9092             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9093             break;
9094           case TwoMachinesPlay:
9095             if (cps->twoMachinesColor[0] == 'w')
9096               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9097             else
9098               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9099             break;
9100           default:
9101             /* can't happen */
9102             break;
9103         }
9104         return;
9105     } else if (strncmp(message, "computer mates", 14) == 0) {
9106         switch (gameMode) {
9107           case MachinePlaysBlack:
9108           case IcsPlayingBlack:
9109             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9110             break;
9111           case MachinePlaysWhite:
9112           case IcsPlayingWhite:
9113             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9114             break;
9115           case TwoMachinesPlay:
9116             if (cps->twoMachinesColor[0] == 'w')
9117               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9118             else
9119               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9120             break;
9121           default:
9122             /* can't happen */
9123             break;
9124         }
9125         return;
9126     } else if (strncmp(message, "checkmate", 9) == 0) {
9127         if (WhiteOnMove(forwardMostMove)) {
9128             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9129         } else {
9130             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9131         }
9132         return;
9133     } else if (strstr(message, "Draw") != NULL ||
9134                strstr(message, "game is a draw") != NULL) {
9135         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9136         return;
9137     } else if (strstr(message, "offer") != NULL &&
9138                strstr(message, "draw") != NULL) {
9139 #if ZIPPY
9140         if (appData.zippyPlay && first.initDone) {
9141             /* Relay offer to ICS */
9142             SendToICS(ics_prefix);
9143             SendToICS("draw\n");
9144         }
9145 #endif
9146         cps->offeredDraw = 2; /* valid until this engine moves twice */
9147         if (gameMode == TwoMachinesPlay) {
9148             if (cps->other->offeredDraw) {
9149                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9150             /* [HGM] in two-machine mode we delay relaying draw offer      */
9151             /* until after we also have move, to see if it is really claim */
9152             }
9153         } else if (gameMode == MachinePlaysWhite ||
9154                    gameMode == MachinePlaysBlack) {
9155           if (userOfferedDraw) {
9156             DisplayInformation(_("Machine accepts your draw offer"));
9157             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9158           } else {
9159             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9160           }
9161         }
9162     }
9163
9164
9165     /*
9166      * Look for thinking output
9167      */
9168     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9169           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9170                                 ) {
9171         int plylev, mvleft, mvtot, curscore, time;
9172         char mvname[MOVE_LEN];
9173         u64 nodes; // [DM]
9174         char plyext;
9175         int ignore = FALSE;
9176         int prefixHint = FALSE;
9177         mvname[0] = NULLCHAR;
9178
9179         switch (gameMode) {
9180           case MachinePlaysBlack:
9181           case IcsPlayingBlack:
9182             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9183             break;
9184           case MachinePlaysWhite:
9185           case IcsPlayingWhite:
9186             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9187             break;
9188           case AnalyzeMode:
9189           case AnalyzeFile:
9190             break;
9191           case IcsObserving: /* [DM] icsEngineAnalyze */
9192             if (!appData.icsEngineAnalyze) ignore = TRUE;
9193             break;
9194           case TwoMachinesPlay:
9195             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9196                 ignore = TRUE;
9197             }
9198             break;
9199           default:
9200             ignore = TRUE;
9201             break;
9202         }
9203
9204         if (!ignore) {
9205             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9206             buf1[0] = NULLCHAR;
9207             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9208                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9209
9210                 if (plyext != ' ' && plyext != '\t') {
9211                     time *= 100;
9212                 }
9213
9214                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9215                 if( cps->scoreIsAbsolute &&
9216                     ( gameMode == MachinePlaysBlack ||
9217                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9218                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9219                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9220                      !WhiteOnMove(currentMove)
9221                     ) )
9222                 {
9223                     curscore = -curscore;
9224                 }
9225
9226                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9227
9228                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9229                         char buf[MSG_SIZ];
9230                         FILE *f;
9231                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9232                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9233                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9234                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9235                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9236                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9237                                 fclose(f);
9238                         }
9239                         else
9240                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9241                           DisplayError(_("failed writing PV"), 0);
9242                 }
9243
9244                 tempStats.depth = plylev;
9245                 tempStats.nodes = nodes;
9246                 tempStats.time = time;
9247                 tempStats.score = curscore;
9248                 tempStats.got_only_move = 0;
9249
9250                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9251                         int ticklen;
9252
9253                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9254                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9255                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9256                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9257                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9258                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9259                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9260                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9261                 }
9262
9263                 /* Buffer overflow protection */
9264                 if (pv[0] != NULLCHAR) {
9265                     if (strlen(pv) >= sizeof(tempStats.movelist)
9266                         && appData.debugMode) {
9267                         fprintf(debugFP,
9268                                 "PV is too long; using the first %u bytes.\n",
9269                                 (unsigned) sizeof(tempStats.movelist) - 1);
9270                     }
9271
9272                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9273                 } else {
9274                     sprintf(tempStats.movelist, " no PV\n");
9275                 }
9276
9277                 if (tempStats.seen_stat) {
9278                     tempStats.ok_to_send = 1;
9279                 }
9280
9281                 if (strchr(tempStats.movelist, '(') != NULL) {
9282                     tempStats.line_is_book = 1;
9283                     tempStats.nr_moves = 0;
9284                     tempStats.moves_left = 0;
9285                 } else {
9286                     tempStats.line_is_book = 0;
9287                 }
9288
9289                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9290                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9291
9292                 SendProgramStatsToFrontend( cps, &tempStats );
9293
9294                 /*
9295                     [AS] Protect the thinkOutput buffer from overflow... this
9296                     is only useful if buf1 hasn't overflowed first!
9297                 */
9298                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9299                          plylev,
9300                          (gameMode == TwoMachinesPlay ?
9301                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9302                          ((double) curscore) / 100.0,
9303                          prefixHint ? lastHint : "",
9304                          prefixHint ? " " : "" );
9305
9306                 if( buf1[0] != NULLCHAR ) {
9307                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9308
9309                     if( strlen(pv) > max_len ) {
9310                         if( appData.debugMode) {
9311                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9312                         }
9313                         pv[max_len+1] = '\0';
9314                     }
9315
9316                     strcat( thinkOutput, pv);
9317                 }
9318
9319                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9320                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9321                     DisplayMove(currentMove - 1);
9322                 }
9323                 return;
9324
9325             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9326                 /* crafty (9.25+) says "(only move) <move>"
9327                  * if there is only 1 legal move
9328                  */
9329                 sscanf(p, "(only move) %s", buf1);
9330                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9331                 sprintf(programStats.movelist, "%s (only move)", buf1);
9332                 programStats.depth = 1;
9333                 programStats.nr_moves = 1;
9334                 programStats.moves_left = 1;
9335                 programStats.nodes = 1;
9336                 programStats.time = 1;
9337                 programStats.got_only_move = 1;
9338
9339                 /* Not really, but we also use this member to
9340                    mean "line isn't going to change" (Crafty
9341                    isn't searching, so stats won't change) */
9342                 programStats.line_is_book = 1;
9343
9344                 SendProgramStatsToFrontend( cps, &programStats );
9345
9346                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9347                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9348                     DisplayMove(currentMove - 1);
9349                 }
9350                 return;
9351             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9352                               &time, &nodes, &plylev, &mvleft,
9353                               &mvtot, mvname) >= 5) {
9354                 /* The stat01: line is from Crafty (9.29+) in response
9355                    to the "." command */
9356                 programStats.seen_stat = 1;
9357                 cps->maybeThinking = TRUE;
9358
9359                 if (programStats.got_only_move || !appData.periodicUpdates)
9360                   return;
9361
9362                 programStats.depth = plylev;
9363                 programStats.time = time;
9364                 programStats.nodes = nodes;
9365                 programStats.moves_left = mvleft;
9366                 programStats.nr_moves = mvtot;
9367                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9368                 programStats.ok_to_send = 1;
9369                 programStats.movelist[0] = '\0';
9370
9371                 SendProgramStatsToFrontend( cps, &programStats );
9372
9373                 return;
9374
9375             } else if (strncmp(message,"++",2) == 0) {
9376                 /* Crafty 9.29+ outputs this */
9377                 programStats.got_fail = 2;
9378                 return;
9379
9380             } else if (strncmp(message,"--",2) == 0) {
9381                 /* Crafty 9.29+ outputs this */
9382                 programStats.got_fail = 1;
9383                 return;
9384
9385             } else if (thinkOutput[0] != NULLCHAR &&
9386                        strncmp(message, "    ", 4) == 0) {
9387                 unsigned message_len;
9388
9389                 p = message;
9390                 while (*p && *p == ' ') p++;
9391
9392                 message_len = strlen( p );
9393
9394                 /* [AS] Avoid buffer overflow */
9395                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9396                     strcat(thinkOutput, " ");
9397                     strcat(thinkOutput, p);
9398                 }
9399
9400                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9401                     strcat(programStats.movelist, " ");
9402                     strcat(programStats.movelist, p);
9403                 }
9404
9405                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9406                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9407                     DisplayMove(currentMove - 1);
9408                 }
9409                 return;
9410             }
9411         }
9412         else {
9413             buf1[0] = NULLCHAR;
9414
9415             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9416                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9417             {
9418                 ChessProgramStats cpstats;
9419
9420                 if (plyext != ' ' && plyext != '\t') {
9421                     time *= 100;
9422                 }
9423
9424                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9425                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9426                     curscore = -curscore;
9427                 }
9428
9429                 cpstats.depth = plylev;
9430                 cpstats.nodes = nodes;
9431                 cpstats.time = time;
9432                 cpstats.score = curscore;
9433                 cpstats.got_only_move = 0;
9434                 cpstats.movelist[0] = '\0';
9435
9436                 if (buf1[0] != NULLCHAR) {
9437                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9438                 }
9439
9440                 cpstats.ok_to_send = 0;
9441                 cpstats.line_is_book = 0;
9442                 cpstats.nr_moves = 0;
9443                 cpstats.moves_left = 0;
9444
9445                 SendProgramStatsToFrontend( cps, &cpstats );
9446             }
9447         }
9448     }
9449 }
9450
9451
9452 /* Parse a game score from the character string "game", and
9453    record it as the history of the current game.  The game
9454    score is NOT assumed to start from the standard position.
9455    The display is not updated in any way.
9456    */
9457 void
9458 ParseGameHistory (char *game)
9459 {
9460     ChessMove moveType;
9461     int fromX, fromY, toX, toY, boardIndex;
9462     char promoChar;
9463     char *p, *q;
9464     char buf[MSG_SIZ];
9465
9466     if (appData.debugMode)
9467       fprintf(debugFP, "Parsing game history: %s\n", game);
9468
9469     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9470     gameInfo.site = StrSave(appData.icsHost);
9471     gameInfo.date = PGNDate();
9472     gameInfo.round = StrSave("-");
9473
9474     /* Parse out names of players */
9475     while (*game == ' ') game++;
9476     p = buf;
9477     while (*game != ' ') *p++ = *game++;
9478     *p = NULLCHAR;
9479     gameInfo.white = StrSave(buf);
9480     while (*game == ' ') game++;
9481     p = buf;
9482     while (*game != ' ' && *game != '\n') *p++ = *game++;
9483     *p = NULLCHAR;
9484     gameInfo.black = StrSave(buf);
9485
9486     /* Parse moves */
9487     boardIndex = blackPlaysFirst ? 1 : 0;
9488     yynewstr(game);
9489     for (;;) {
9490         yyboardindex = boardIndex;
9491         moveType = (ChessMove) Myylex();
9492         switch (moveType) {
9493           case IllegalMove:             /* maybe suicide chess, etc. */
9494   if (appData.debugMode) {
9495     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9496     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9497     setbuf(debugFP, NULL);
9498   }
9499           case WhitePromotion:
9500           case BlackPromotion:
9501           case WhiteNonPromotion:
9502           case BlackNonPromotion:
9503           case NormalMove:
9504           case FirstLeg:
9505           case WhiteCapturesEnPassant:
9506           case BlackCapturesEnPassant:
9507           case WhiteKingSideCastle:
9508           case WhiteQueenSideCastle:
9509           case BlackKingSideCastle:
9510           case BlackQueenSideCastle:
9511           case WhiteKingSideCastleWild:
9512           case WhiteQueenSideCastleWild:
9513           case BlackKingSideCastleWild:
9514           case BlackQueenSideCastleWild:
9515           /* PUSH Fabien */
9516           case WhiteHSideCastleFR:
9517           case WhiteASideCastleFR:
9518           case BlackHSideCastleFR:
9519           case BlackASideCastleFR:
9520           /* POP Fabien */
9521             fromX = currentMoveString[0] - AAA;
9522             fromY = currentMoveString[1] - ONE;
9523             toX = currentMoveString[2] - AAA;
9524             toY = currentMoveString[3] - ONE;
9525             promoChar = currentMoveString[4];
9526             break;
9527           case WhiteDrop:
9528           case BlackDrop:
9529             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9530             fromX = moveType == WhiteDrop ?
9531               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9532             (int) CharToPiece(ToLower(currentMoveString[0]));
9533             fromY = DROP_RANK;
9534             toX = currentMoveString[2] - AAA;
9535             toY = currentMoveString[3] - ONE;
9536             promoChar = NULLCHAR;
9537             break;
9538           case AmbiguousMove:
9539             /* bug? */
9540             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9541   if (appData.debugMode) {
9542     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9543     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9544     setbuf(debugFP, NULL);
9545   }
9546             DisplayError(buf, 0);
9547             return;
9548           case ImpossibleMove:
9549             /* bug? */
9550             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9551   if (appData.debugMode) {
9552     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9553     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9554     setbuf(debugFP, NULL);
9555   }
9556             DisplayError(buf, 0);
9557             return;
9558           case EndOfFile:
9559             if (boardIndex < backwardMostMove) {
9560                 /* Oops, gap.  How did that happen? */
9561                 DisplayError(_("Gap in move list"), 0);
9562                 return;
9563             }
9564             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9565             if (boardIndex > forwardMostMove) {
9566                 forwardMostMove = boardIndex;
9567             }
9568             return;
9569           case ElapsedTime:
9570             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9571                 strcat(parseList[boardIndex-1], " ");
9572                 strcat(parseList[boardIndex-1], yy_text);
9573             }
9574             continue;
9575           case Comment:
9576           case PGNTag:
9577           case NAG:
9578           default:
9579             /* ignore */
9580             continue;
9581           case WhiteWins:
9582           case BlackWins:
9583           case GameIsDrawn:
9584           case GameUnfinished:
9585             if (gameMode == IcsExamining) {
9586                 if (boardIndex < backwardMostMove) {
9587                     /* Oops, gap.  How did that happen? */
9588                     return;
9589                 }
9590                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9591                 return;
9592             }
9593             gameInfo.result = moveType;
9594             p = strchr(yy_text, '{');
9595             if (p == NULL) p = strchr(yy_text, '(');
9596             if (p == NULL) {
9597                 p = yy_text;
9598                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9599             } else {
9600                 q = strchr(p, *p == '{' ? '}' : ')');
9601                 if (q != NULL) *q = NULLCHAR;
9602                 p++;
9603             }
9604             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9605             gameInfo.resultDetails = StrSave(p);
9606             continue;
9607         }
9608         if (boardIndex >= forwardMostMove &&
9609             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9610             backwardMostMove = blackPlaysFirst ? 1 : 0;
9611             return;
9612         }
9613         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9614                                  fromY, fromX, toY, toX, promoChar,
9615                                  parseList[boardIndex]);
9616         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9617         /* currentMoveString is set as a side-effect of yylex */
9618         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9619         strcat(moveList[boardIndex], "\n");
9620         boardIndex++;
9621         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9622         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9623           case MT_NONE:
9624           case MT_STALEMATE:
9625           default:
9626             break;
9627           case MT_CHECK:
9628             if(gameInfo.variant != VariantShogi)
9629                 strcat(parseList[boardIndex - 1], "+");
9630             break;
9631           case MT_CHECKMATE:
9632           case MT_STAINMATE:
9633             strcat(parseList[boardIndex - 1], "#");
9634             break;
9635         }
9636     }
9637 }
9638
9639
9640 /* Apply a move to the given board  */
9641 void
9642 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9643 {
9644   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9645   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9646
9647     /* [HGM] compute & store e.p. status and castling rights for new position */
9648     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9649
9650       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9651       oldEP = (signed char)board[EP_STATUS];
9652       board[EP_STATUS] = EP_NONE;
9653
9654   if (fromY == DROP_RANK) {
9655         /* must be first */
9656         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9657             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9658             return;
9659         }
9660         piece = board[toY][toX] = (ChessSquare) fromX;
9661   } else {
9662       ChessSquare victim;
9663       int i;
9664
9665       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9666            victim = board[killY][killX],
9667            board[killY][killX] = EmptySquare,
9668            board[EP_STATUS] = EP_CAPTURE;
9669
9670       if( board[toY][toX] != EmptySquare ) {
9671            board[EP_STATUS] = EP_CAPTURE;
9672            if( (fromX != toX || fromY != toY) && // not igui!
9673                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9674                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9675                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9676            }
9677       }
9678
9679       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9680            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9681                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9682       } else
9683       if( board[fromY][fromX] == WhitePawn ) {
9684            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9685                board[EP_STATUS] = EP_PAWN_MOVE;
9686            if( toY-fromY==2) {
9687                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9688                         gameInfo.variant != VariantBerolina || toX < fromX)
9689                       board[EP_STATUS] = toX | berolina;
9690                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9691                         gameInfo.variant != VariantBerolina || toX > fromX)
9692                       board[EP_STATUS] = toX;
9693            }
9694       } else
9695       if( board[fromY][fromX] == BlackPawn ) {
9696            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9697                board[EP_STATUS] = EP_PAWN_MOVE;
9698            if( toY-fromY== -2) {
9699                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9700                         gameInfo.variant != VariantBerolina || toX < fromX)
9701                       board[EP_STATUS] = toX | berolina;
9702                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9703                         gameInfo.variant != VariantBerolina || toX > fromX)
9704                       board[EP_STATUS] = toX;
9705            }
9706        }
9707
9708        for(i=0; i<nrCastlingRights; i++) {
9709            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9710               board[CASTLING][i] == toX   && castlingRank[i] == toY
9711              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9712        }
9713
9714        if(gameInfo.variant == VariantSChess) { // update virginity
9715            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9716            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9717            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9718            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9719        }
9720
9721      if (fromX == toX && fromY == toY) return;
9722
9723      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9724      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9725      if(gameInfo.variant == VariantKnightmate)
9726          king += (int) WhiteUnicorn - (int) WhiteKing;
9727
9728     /* Code added by Tord: */
9729     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9730     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9731         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9732       board[fromY][fromX] = EmptySquare;
9733       board[toY][toX] = EmptySquare;
9734       if((toX > fromX) != (piece == WhiteRook)) {
9735         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9736       } else {
9737         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9738       }
9739     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9740                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9741       board[fromY][fromX] = EmptySquare;
9742       board[toY][toX] = EmptySquare;
9743       if((toX > fromX) != (piece == BlackRook)) {
9744         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9745       } else {
9746         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9747       }
9748     /* End of code added by Tord */
9749
9750     } else if (board[fromY][fromX] == king
9751         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9752         && toY == fromY && toX > fromX+1) {
9753         board[fromY][fromX] = EmptySquare;
9754         board[toY][toX] = king;
9755         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9756         board[fromY][BOARD_RGHT-1] = EmptySquare;
9757     } else if (board[fromY][fromX] == king
9758         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9759                && toY == fromY && toX < fromX-1) {
9760         board[fromY][fromX] = EmptySquare;
9761         board[toY][toX] = king;
9762         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9763         board[fromY][BOARD_LEFT] = EmptySquare;
9764     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9765                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9766                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9767                ) {
9768         /* white pawn promotion */
9769         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9770         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9771             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9772         board[fromY][fromX] = EmptySquare;
9773     } else if ((fromY >= BOARD_HEIGHT>>1)
9774                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9775                && (toX != fromX)
9776                && gameInfo.variant != VariantXiangqi
9777                && gameInfo.variant != VariantBerolina
9778                && (board[fromY][fromX] == WhitePawn)
9779                && (board[toY][toX] == EmptySquare)) {
9780         board[fromY][fromX] = EmptySquare;
9781         board[toY][toX] = WhitePawn;
9782         captured = board[toY - 1][toX];
9783         board[toY - 1][toX] = EmptySquare;
9784     } else if ((fromY == BOARD_HEIGHT-4)
9785                && (toX == fromX)
9786                && gameInfo.variant == VariantBerolina
9787                && (board[fromY][fromX] == WhitePawn)
9788                && (board[toY][toX] == EmptySquare)) {
9789         board[fromY][fromX] = EmptySquare;
9790         board[toY][toX] = WhitePawn;
9791         if(oldEP & EP_BEROLIN_A) {
9792                 captured = board[fromY][fromX-1];
9793                 board[fromY][fromX-1] = EmptySquare;
9794         }else{  captured = board[fromY][fromX+1];
9795                 board[fromY][fromX+1] = EmptySquare;
9796         }
9797     } else if (board[fromY][fromX] == king
9798         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9799                && toY == fromY && toX > fromX+1) {
9800         board[fromY][fromX] = EmptySquare;
9801         board[toY][toX] = king;
9802         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9803         board[fromY][BOARD_RGHT-1] = EmptySquare;
9804     } else if (board[fromY][fromX] == king
9805         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9806                && toY == fromY && toX < fromX-1) {
9807         board[fromY][fromX] = EmptySquare;
9808         board[toY][toX] = king;
9809         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9810         board[fromY][BOARD_LEFT] = EmptySquare;
9811     } else if (fromY == 7 && fromX == 3
9812                && board[fromY][fromX] == BlackKing
9813                && toY == 7 && toX == 5) {
9814         board[fromY][fromX] = EmptySquare;
9815         board[toY][toX] = BlackKing;
9816         board[fromY][7] = EmptySquare;
9817         board[toY][4] = BlackRook;
9818     } else if (fromY == 7 && fromX == 3
9819                && board[fromY][fromX] == BlackKing
9820                && toY == 7 && toX == 1) {
9821         board[fromY][fromX] = EmptySquare;
9822         board[toY][toX] = BlackKing;
9823         board[fromY][0] = EmptySquare;
9824         board[toY][2] = BlackRook;
9825     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9826                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9827                && toY < promoRank && promoChar
9828                ) {
9829         /* black pawn promotion */
9830         board[toY][toX] = CharToPiece(ToLower(promoChar));
9831         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9832             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9833         board[fromY][fromX] = EmptySquare;
9834     } else if ((fromY < BOARD_HEIGHT>>1)
9835                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9836                && (toX != fromX)
9837                && gameInfo.variant != VariantXiangqi
9838                && gameInfo.variant != VariantBerolina
9839                && (board[fromY][fromX] == BlackPawn)
9840                && (board[toY][toX] == EmptySquare)) {
9841         board[fromY][fromX] = EmptySquare;
9842         board[toY][toX] = BlackPawn;
9843         captured = board[toY + 1][toX];
9844         board[toY + 1][toX] = EmptySquare;
9845     } else if ((fromY == 3)
9846                && (toX == fromX)
9847                && gameInfo.variant == VariantBerolina
9848                && (board[fromY][fromX] == BlackPawn)
9849                && (board[toY][toX] == EmptySquare)) {
9850         board[fromY][fromX] = EmptySquare;
9851         board[toY][toX] = BlackPawn;
9852         if(oldEP & EP_BEROLIN_A) {
9853                 captured = board[fromY][fromX-1];
9854                 board[fromY][fromX-1] = EmptySquare;
9855         }else{  captured = board[fromY][fromX+1];
9856                 board[fromY][fromX+1] = EmptySquare;
9857         }
9858     } else {
9859         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
9860         board[fromY][fromX] = EmptySquare;
9861         board[toY][toX] = piece;
9862     }
9863   }
9864
9865     if (gameInfo.holdingsWidth != 0) {
9866
9867       /* !!A lot more code needs to be written to support holdings  */
9868       /* [HGM] OK, so I have written it. Holdings are stored in the */
9869       /* penultimate board files, so they are automaticlly stored   */
9870       /* in the game history.                                       */
9871       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9872                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9873         /* Delete from holdings, by decreasing count */
9874         /* and erasing image if necessary            */
9875         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9876         if(p < (int) BlackPawn) { /* white drop */
9877              p -= (int)WhitePawn;
9878                  p = PieceToNumber((ChessSquare)p);
9879              if(p >= gameInfo.holdingsSize) p = 0;
9880              if(--board[p][BOARD_WIDTH-2] <= 0)
9881                   board[p][BOARD_WIDTH-1] = EmptySquare;
9882              if((int)board[p][BOARD_WIDTH-2] < 0)
9883                         board[p][BOARD_WIDTH-2] = 0;
9884         } else {                  /* black drop */
9885              p -= (int)BlackPawn;
9886                  p = PieceToNumber((ChessSquare)p);
9887              if(p >= gameInfo.holdingsSize) p = 0;
9888              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9889                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9890              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9891                         board[BOARD_HEIGHT-1-p][1] = 0;
9892         }
9893       }
9894       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9895           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9896         /* [HGM] holdings: Add to holdings, if holdings exist */
9897         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9898                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9899                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9900         }
9901         p = (int) captured;
9902         if (p >= (int) BlackPawn) {
9903           p -= (int)BlackPawn;
9904           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9905                   /* in Shogi restore piece to its original  first */
9906                   captured = (ChessSquare) (DEMOTED captured);
9907                   p = DEMOTED p;
9908           }
9909           p = PieceToNumber((ChessSquare)p);
9910           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9911           board[p][BOARD_WIDTH-2]++;
9912           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9913         } else {
9914           p -= (int)WhitePawn;
9915           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9916                   captured = (ChessSquare) (DEMOTED captured);
9917                   p = DEMOTED p;
9918           }
9919           p = PieceToNumber((ChessSquare)p);
9920           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9921           board[BOARD_HEIGHT-1-p][1]++;
9922           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9923         }
9924       }
9925     } else if (gameInfo.variant == VariantAtomic) {
9926       if (captured != EmptySquare) {
9927         int y, x;
9928         for (y = toY-1; y <= toY+1; y++) {
9929           for (x = toX-1; x <= toX+1; x++) {
9930             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9931                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9932               board[y][x] = EmptySquare;
9933             }
9934           }
9935         }
9936         board[toY][toX] = EmptySquare;
9937       }
9938     }
9939     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9940         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9941     } else
9942     if(promoChar == '+') {
9943         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9944         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
9945     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9946         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9947         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9948            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9949         board[toY][toX] = newPiece;
9950     }
9951     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9952                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9953         // [HGM] superchess: take promotion piece out of holdings
9954         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9955         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9956             if(!--board[k][BOARD_WIDTH-2])
9957                 board[k][BOARD_WIDTH-1] = EmptySquare;
9958         } else {
9959             if(!--board[BOARD_HEIGHT-1-k][1])
9960                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9961         }
9962     }
9963 }
9964
9965 /* Updates forwardMostMove */
9966 void
9967 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9968 {
9969     int x = toX, y = toY;
9970     char *s = parseList[forwardMostMove];
9971     ChessSquare p = boards[forwardMostMove][toY][toX];
9972 //    forwardMostMove++; // [HGM] bare: moved downstream
9973
9974     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
9975     (void) CoordsToAlgebraic(boards[forwardMostMove],
9976                              PosFlags(forwardMostMove),
9977                              fromY, fromX, y, x, promoChar,
9978                              s);
9979     if(killX >= 0 && killY >= 0)
9980         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
9981
9982     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9983         int timeLeft; static int lastLoadFlag=0; int king, piece;
9984         piece = boards[forwardMostMove][fromY][fromX];
9985         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9986         if(gameInfo.variant == VariantKnightmate)
9987             king += (int) WhiteUnicorn - (int) WhiteKing;
9988         if(forwardMostMove == 0) {
9989             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9990                 fprintf(serverMoves, "%s;", UserName());
9991             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9992                 fprintf(serverMoves, "%s;", second.tidy);
9993             fprintf(serverMoves, "%s;", first.tidy);
9994             if(gameMode == MachinePlaysWhite)
9995                 fprintf(serverMoves, "%s;", UserName());
9996             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9997                 fprintf(serverMoves, "%s;", second.tidy);
9998         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9999         lastLoadFlag = loadFlag;
10000         // print base move
10001         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10002         // print castling suffix
10003         if( toY == fromY && piece == king ) {
10004             if(toX-fromX > 1)
10005                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10006             if(fromX-toX >1)
10007                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10008         }
10009         // e.p. suffix
10010         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10011              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10012              boards[forwardMostMove][toY][toX] == EmptySquare
10013              && fromX != toX && fromY != toY)
10014                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10015         // promotion suffix
10016         if(promoChar != NULLCHAR) {
10017             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10018                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10019                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10020             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10021         }
10022         if(!loadFlag) {
10023                 char buf[MOVE_LEN*2], *p; int len;
10024             fprintf(serverMoves, "/%d/%d",
10025                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10026             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10027             else                      timeLeft = blackTimeRemaining/1000;
10028             fprintf(serverMoves, "/%d", timeLeft);
10029                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10030                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10031                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10032                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10033             fprintf(serverMoves, "/%s", buf);
10034         }
10035         fflush(serverMoves);
10036     }
10037
10038     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10039         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10040       return;
10041     }
10042     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10043     if (commentList[forwardMostMove+1] != NULL) {
10044         free(commentList[forwardMostMove+1]);
10045         commentList[forwardMostMove+1] = NULL;
10046     }
10047     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10048     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10049     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10050     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10051     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10052     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10053     adjustedClock = FALSE;
10054     gameInfo.result = GameUnfinished;
10055     if (gameInfo.resultDetails != NULL) {
10056         free(gameInfo.resultDetails);
10057         gameInfo.resultDetails = NULL;
10058     }
10059     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10060                               moveList[forwardMostMove - 1]);
10061     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10062       case MT_NONE:
10063       case MT_STALEMATE:
10064       default:
10065         break;
10066       case MT_CHECK:
10067         if(gameInfo.variant != VariantShogi)
10068             strcat(parseList[forwardMostMove - 1], "+");
10069         break;
10070       case MT_CHECKMATE:
10071       case MT_STAINMATE:
10072         strcat(parseList[forwardMostMove - 1], "#");
10073         break;
10074     }
10075 }
10076
10077 /* Updates currentMove if not pausing */
10078 void
10079 ShowMove (int fromX, int fromY, int toX, int toY)
10080 {
10081     int instant = (gameMode == PlayFromGameFile) ?
10082         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10083     if(appData.noGUI) return;
10084     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10085         if (!instant) {
10086             if (forwardMostMove == currentMove + 1) {
10087                 AnimateMove(boards[forwardMostMove - 1],
10088                             fromX, fromY, toX, toY);
10089             }
10090         }
10091         currentMove = forwardMostMove;
10092     }
10093
10094     killX = killY = -1; // [HGM] lion: used up
10095
10096     if (instant) return;
10097
10098     DisplayMove(currentMove - 1);
10099     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10100             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10101                 SetHighlights(fromX, fromY, toX, toY);
10102             }
10103     }
10104     DrawPosition(FALSE, boards[currentMove]);
10105     DisplayBothClocks();
10106     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10107 }
10108
10109 void
10110 SendEgtPath (ChessProgramState *cps)
10111 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10112         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10113
10114         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10115
10116         while(*p) {
10117             char c, *q = name+1, *r, *s;
10118
10119             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10120             while(*p && *p != ',') *q++ = *p++;
10121             *q++ = ':'; *q = 0;
10122             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10123                 strcmp(name, ",nalimov:") == 0 ) {
10124                 // take nalimov path from the menu-changeable option first, if it is defined
10125               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10126                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10127             } else
10128             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10129                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10130                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10131                 s = r = StrStr(s, ":") + 1; // beginning of path info
10132                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10133                 c = *r; *r = 0;             // temporarily null-terminate path info
10134                     *--q = 0;               // strip of trailig ':' from name
10135                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10136                 *r = c;
10137                 SendToProgram(buf,cps);     // send egtbpath command for this format
10138             }
10139             if(*p == ',') p++; // read away comma to position for next format name
10140         }
10141 }
10142
10143 static int
10144 NonStandardBoardSize ()
10145 {
10146       /* [HGM] Awkward testing. Should really be a table */
10147       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10148       if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10149       if( gameInfo.variant == VariantXiangqi )
10150            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
10151       if( gameInfo.variant == VariantShogi )
10152            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
10153       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
10154            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
10155       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
10156           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
10157            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10158       if( gameInfo.variant == VariantCourier )
10159            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
10160       if( gameInfo.variant == VariantSuper )
10161            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10162       if( gameInfo.variant == VariantGreat )
10163            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
10164       if( gameInfo.variant == VariantSChess )
10165            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
10166       if( gameInfo.variant == VariantGrand )
10167            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
10168       if( gameInfo.variant == VariantChu )
10169            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
10170       return overruled;
10171 }
10172
10173 void
10174 InitChessProgram (ChessProgramState *cps, int setup)
10175 /* setup needed to setup FRC opening position */
10176 {
10177     char buf[MSG_SIZ], b[MSG_SIZ];
10178     if (appData.noChessProgram) return;
10179     hintRequested = FALSE;
10180     bookRequested = FALSE;
10181
10182     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10183     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10184     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10185     if(cps->memSize) { /* [HGM] memory */
10186       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10187         SendToProgram(buf, cps);
10188     }
10189     SendEgtPath(cps); /* [HGM] EGT */
10190     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10191       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10192         SendToProgram(buf, cps);
10193     }
10194
10195     SendToProgram(cps->initString, cps);
10196     if (gameInfo.variant != VariantNormal &&
10197         gameInfo.variant != VariantLoadable
10198         /* [HGM] also send variant if board size non-standard */
10199         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
10200                                             ) {
10201       char *v = VariantName(gameInfo.variant);
10202       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
10203         /* [HGM] in protocol 1 we have to assume all variants valid */
10204         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
10205         DisplayFatalError(buf, 0, 1);
10206         return;
10207       }
10208
10209       if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
10210         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
10211                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
10212            /* [HGM] varsize: try first if this defiant size variant is specifically known */
10213            if(StrStr(cps->variants, b) == NULL) {
10214                // specific sized variant not known, check if general sizing allowed
10215                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
10216                    if(StrStr(cps->variants, "boardsize") == NULL) {
10217                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10218                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
10219                        DisplayFatalError(buf, 0, 1);
10220                        return;
10221                    }
10222                    /* [HGM] here we really should compare with the maximum supported board size */
10223                }
10224            }
10225       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
10226       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10227       SendToProgram(buf, cps);
10228     }
10229     currentlyInitializedVariant = gameInfo.variant;
10230
10231     /* [HGM] send opening position in FRC to first engine */
10232     if(setup) {
10233           SendToProgram("force\n", cps);
10234           SendBoard(cps, 0);
10235           /* engine is now in force mode! Set flag to wake it up after first move. */
10236           setboardSpoiledMachineBlack = 1;
10237     }
10238
10239     if (cps->sendICS) {
10240       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10241       SendToProgram(buf, cps);
10242     }
10243     cps->maybeThinking = FALSE;
10244     cps->offeredDraw = 0;
10245     if (!appData.icsActive) {
10246         SendTimeControl(cps, movesPerSession, timeControl,
10247                         timeIncrement, appData.searchDepth,
10248                         searchTime);
10249     }
10250     if (appData.showThinking
10251         // [HGM] thinking: four options require thinking output to be sent
10252         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10253                                 ) {
10254         SendToProgram("post\n", cps);
10255     }
10256     SendToProgram("hard\n", cps);
10257     if (!appData.ponderNextMove) {
10258         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10259            it without being sure what state we are in first.  "hard"
10260            is not a toggle, so that one is OK.
10261          */
10262         SendToProgram("easy\n", cps);
10263     }
10264     if (cps->usePing) {
10265       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
10266       SendToProgram(buf, cps);
10267     }
10268     cps->initDone = TRUE;
10269     ClearEngineOutputPane(cps == &second);
10270 }
10271
10272
10273 void
10274 ResendOptions (ChessProgramState *cps)
10275 { // send the stored value of the options
10276   int i;
10277   char buf[MSG_SIZ];
10278   Option *opt = cps->option;
10279   for(i=0; i<cps->nrOptions; i++, opt++) {
10280       switch(opt->type) {
10281         case Spin:
10282         case Slider:
10283         case CheckBox:
10284             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10285           break;
10286         case ComboBox:
10287           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10288           break;
10289         default:
10290             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10291           break;
10292         case Button:
10293         case SaveButton:
10294           continue;
10295       }
10296       SendToProgram(buf, cps);
10297   }
10298 }
10299
10300 void
10301 StartChessProgram (ChessProgramState *cps)
10302 {
10303     char buf[MSG_SIZ];
10304     int err;
10305
10306     if (appData.noChessProgram) return;
10307     cps->initDone = FALSE;
10308
10309     if (strcmp(cps->host, "localhost") == 0) {
10310         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10311     } else if (*appData.remoteShell == NULLCHAR) {
10312         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10313     } else {
10314         if (*appData.remoteUser == NULLCHAR) {
10315           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10316                     cps->program);
10317         } else {
10318           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10319                     cps->host, appData.remoteUser, cps->program);
10320         }
10321         err = StartChildProcess(buf, "", &cps->pr);
10322     }
10323
10324     if (err != 0) {
10325       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10326         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10327         if(cps != &first) return;
10328         appData.noChessProgram = TRUE;
10329         ThawUI();
10330         SetNCPMode();
10331 //      DisplayFatalError(buf, err, 1);
10332 //      cps->pr = NoProc;
10333 //      cps->isr = NULL;
10334         return;
10335     }
10336
10337     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10338     if (cps->protocolVersion > 1) {
10339       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10340       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10341         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10342         cps->comboCnt = 0;  //                and values of combo boxes
10343       }
10344       SendToProgram(buf, cps);
10345       if(cps->reload) ResendOptions(cps);
10346     } else {
10347       SendToProgram("xboard\n", cps);
10348     }
10349 }
10350
10351 void
10352 TwoMachinesEventIfReady P((void))
10353 {
10354   static int curMess = 0;
10355   if (first.lastPing != first.lastPong) {
10356     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10357     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10358     return;
10359   }
10360   if (second.lastPing != second.lastPong) {
10361     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10362     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10363     return;
10364   }
10365   DisplayMessage("", ""); curMess = 0;
10366   TwoMachinesEvent();
10367 }
10368
10369 char *
10370 MakeName (char *template)
10371 {
10372     time_t clock;
10373     struct tm *tm;
10374     static char buf[MSG_SIZ];
10375     char *p = buf;
10376     int i;
10377
10378     clock = time((time_t *)NULL);
10379     tm = localtime(&clock);
10380
10381     while(*p++ = *template++) if(p[-1] == '%') {
10382         switch(*template++) {
10383           case 0:   *p = 0; return buf;
10384           case 'Y': i = tm->tm_year+1900; break;
10385           case 'y': i = tm->tm_year-100; break;
10386           case 'M': i = tm->tm_mon+1; break;
10387           case 'd': i = tm->tm_mday; break;
10388           case 'h': i = tm->tm_hour; break;
10389           case 'm': i = tm->tm_min; break;
10390           case 's': i = tm->tm_sec; break;
10391           default:  i = 0;
10392         }
10393         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10394     }
10395     return buf;
10396 }
10397
10398 int
10399 CountPlayers (char *p)
10400 {
10401     int n = 0;
10402     while(p = strchr(p, '\n')) p++, n++; // count participants
10403     return n;
10404 }
10405
10406 FILE *
10407 WriteTourneyFile (char *results, FILE *f)
10408 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10409     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10410     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10411         // create a file with tournament description
10412         fprintf(f, "-participants {%s}\n", appData.participants);
10413         fprintf(f, "-seedBase %d\n", appData.seedBase);
10414         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10415         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10416         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10417         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10418         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10419         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10420         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10421         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10422         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10423         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10424         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10425         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10426         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10427         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10428         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10429         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10430         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10431         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10432         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10433         fprintf(f, "-smpCores %d\n", appData.smpCores);
10434         if(searchTime > 0)
10435                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10436         else {
10437                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10438                 fprintf(f, "-tc %s\n", appData.timeControl);
10439                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10440         }
10441         fprintf(f, "-results \"%s\"\n", results);
10442     }
10443     return f;
10444 }
10445
10446 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10447
10448 void
10449 Substitute (char *participants, int expunge)
10450 {
10451     int i, changed, changes=0, nPlayers=0;
10452     char *p, *q, *r, buf[MSG_SIZ];
10453     if(participants == NULL) return;
10454     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10455     r = p = participants; q = appData.participants;
10456     while(*p && *p == *q) {
10457         if(*p == '\n') r = p+1, nPlayers++;
10458         p++; q++;
10459     }
10460     if(*p) { // difference
10461         while(*p && *p++ != '\n');
10462         while(*q && *q++ != '\n');
10463       changed = nPlayers;
10464         changes = 1 + (strcmp(p, q) != 0);
10465     }
10466     if(changes == 1) { // a single engine mnemonic was changed
10467         q = r; while(*q) nPlayers += (*q++ == '\n');
10468         p = buf; while(*r && (*p = *r++) != '\n') p++;
10469         *p = NULLCHAR;
10470         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10471         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10472         if(mnemonic[i]) { // The substitute is valid
10473             FILE *f;
10474             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10475                 flock(fileno(f), LOCK_EX);
10476                 ParseArgsFromFile(f);
10477                 fseek(f, 0, SEEK_SET);
10478                 FREE(appData.participants); appData.participants = participants;
10479                 if(expunge) { // erase results of replaced engine
10480                     int len = strlen(appData.results), w, b, dummy;
10481                     for(i=0; i<len; i++) {
10482                         Pairing(i, nPlayers, &w, &b, &dummy);
10483                         if((w == changed || b == changed) && appData.results[i] == '*') {
10484                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10485                             fclose(f);
10486                             return;
10487                         }
10488                     }
10489                     for(i=0; i<len; i++) {
10490                         Pairing(i, nPlayers, &w, &b, &dummy);
10491                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10492                     }
10493                 }
10494                 WriteTourneyFile(appData.results, f);
10495                 fclose(f); // release lock
10496                 return;
10497             }
10498         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10499     }
10500     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10501     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10502     free(participants);
10503     return;
10504 }
10505
10506 int
10507 CheckPlayers (char *participants)
10508 {
10509         int i;
10510         char buf[MSG_SIZ], *p;
10511         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10512         while(p = strchr(participants, '\n')) {
10513             *p = NULLCHAR;
10514             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10515             if(!mnemonic[i]) {
10516                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10517                 *p = '\n';
10518                 DisplayError(buf, 0);
10519                 return 1;
10520             }
10521             *p = '\n';
10522             participants = p + 1;
10523         }
10524         return 0;
10525 }
10526
10527 int
10528 CreateTourney (char *name)
10529 {
10530         FILE *f;
10531         if(matchMode && strcmp(name, appData.tourneyFile)) {
10532              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10533         }
10534         if(name[0] == NULLCHAR) {
10535             if(appData.participants[0])
10536                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10537             return 0;
10538         }
10539         f = fopen(name, "r");
10540         if(f) { // file exists
10541             ASSIGN(appData.tourneyFile, name);
10542             ParseArgsFromFile(f); // parse it
10543         } else {
10544             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10545             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10546                 DisplayError(_("Not enough participants"), 0);
10547                 return 0;
10548             }
10549             if(CheckPlayers(appData.participants)) return 0;
10550             ASSIGN(appData.tourneyFile, name);
10551             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10552             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10553         }
10554         fclose(f);
10555         appData.noChessProgram = FALSE;
10556         appData.clockMode = TRUE;
10557         SetGNUMode();
10558         return 1;
10559 }
10560
10561 int
10562 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10563 {
10564     char buf[MSG_SIZ], *p, *q;
10565     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10566     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10567     skip = !all && group[0]; // if group requested, we start in skip mode
10568     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10569         p = names; q = buf; header = 0;
10570         while(*p && *p != '\n') *q++ = *p++;
10571         *q = 0;
10572         if(*p == '\n') p++;
10573         if(buf[0] == '#') {
10574             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10575             depth++; // we must be entering a new group
10576             if(all) continue; // suppress printing group headers when complete list requested
10577             header = 1;
10578             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10579         }
10580         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10581         if(engineList[i]) free(engineList[i]);
10582         engineList[i] = strdup(buf);
10583         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10584         if(engineMnemonic[i]) free(engineMnemonic[i]);
10585         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10586             strcat(buf, " (");
10587             sscanf(q + 8, "%s", buf + strlen(buf));
10588             strcat(buf, ")");
10589         }
10590         engineMnemonic[i] = strdup(buf);
10591         i++;
10592     }
10593     engineList[i] = engineMnemonic[i] = NULL;
10594     return i;
10595 }
10596
10597 // following implemented as macro to avoid type limitations
10598 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10599
10600 void
10601 SwapEngines (int n)
10602 {   // swap settings for first engine and other engine (so far only some selected options)
10603     int h;
10604     char *p;
10605     if(n == 0) return;
10606     SWAP(directory, p)
10607     SWAP(chessProgram, p)
10608     SWAP(isUCI, h)
10609     SWAP(hasOwnBookUCI, h)
10610     SWAP(protocolVersion, h)
10611     SWAP(reuse, h)
10612     SWAP(scoreIsAbsolute, h)
10613     SWAP(timeOdds, h)
10614     SWAP(logo, p)
10615     SWAP(pgnName, p)
10616     SWAP(pvSAN, h)
10617     SWAP(engOptions, p)
10618     SWAP(engInitString, p)
10619     SWAP(computerString, p)
10620     SWAP(features, p)
10621     SWAP(fenOverride, p)
10622     SWAP(NPS, h)
10623     SWAP(accumulateTC, h)
10624     SWAP(host, p)
10625 }
10626
10627 int
10628 GetEngineLine (char *s, int n)
10629 {
10630     int i;
10631     char buf[MSG_SIZ];
10632     extern char *icsNames;
10633     if(!s || !*s) return 0;
10634     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10635     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10636     if(!mnemonic[i]) return 0;
10637     if(n == 11) return 1; // just testing if there was a match
10638     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10639     if(n == 1) SwapEngines(n);
10640     ParseArgsFromString(buf);
10641     if(n == 1) SwapEngines(n);
10642     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10643         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10644         ParseArgsFromString(buf);
10645     }
10646     return 1;
10647 }
10648
10649 int
10650 SetPlayer (int player, char *p)
10651 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10652     int i;
10653     char buf[MSG_SIZ], *engineName;
10654     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10655     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10656     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10657     if(mnemonic[i]) {
10658         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10659         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10660         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10661         ParseArgsFromString(buf);
10662     } else { // no engine with this nickname is installed!
10663         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10664         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10665         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10666         ModeHighlight();
10667         DisplayError(buf, 0);
10668         return 0;
10669     }
10670     free(engineName);
10671     return i;
10672 }
10673
10674 char *recentEngines;
10675
10676 void
10677 RecentEngineEvent (int nr)
10678 {
10679     int n;
10680 //    SwapEngines(1); // bump first to second
10681 //    ReplaceEngine(&second, 1); // and load it there
10682     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10683     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10684     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10685         ReplaceEngine(&first, 0);
10686         FloatToFront(&appData.recentEngineList, command[n]);
10687     }
10688 }
10689
10690 int
10691 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10692 {   // determine players from game number
10693     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10694
10695     if(appData.tourneyType == 0) {
10696         roundsPerCycle = (nPlayers - 1) | 1;
10697         pairingsPerRound = nPlayers / 2;
10698     } else if(appData.tourneyType > 0) {
10699         roundsPerCycle = nPlayers - appData.tourneyType;
10700         pairingsPerRound = appData.tourneyType;
10701     }
10702     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10703     gamesPerCycle = gamesPerRound * roundsPerCycle;
10704     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10705     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10706     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10707     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10708     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10709     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10710
10711     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10712     if(appData.roundSync) *syncInterval = gamesPerRound;
10713
10714     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10715
10716     if(appData.tourneyType == 0) {
10717         if(curPairing == (nPlayers-1)/2 ) {
10718             *whitePlayer = curRound;
10719             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10720         } else {
10721             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10722             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10723             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10724             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10725         }
10726     } else if(appData.tourneyType > 1) {
10727         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10728         *whitePlayer = curRound + appData.tourneyType;
10729     } else if(appData.tourneyType > 0) {
10730         *whitePlayer = curPairing;
10731         *blackPlayer = curRound + appData.tourneyType;
10732     }
10733
10734     // take care of white/black alternation per round.
10735     // For cycles and games this is already taken care of by default, derived from matchGame!
10736     return curRound & 1;
10737 }
10738
10739 int
10740 NextTourneyGame (int nr, int *swapColors)
10741 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10742     char *p, *q;
10743     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10744     FILE *tf;
10745     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10746     tf = fopen(appData.tourneyFile, "r");
10747     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10748     ParseArgsFromFile(tf); fclose(tf);
10749     InitTimeControls(); // TC might be altered from tourney file
10750
10751     nPlayers = CountPlayers(appData.participants); // count participants
10752     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10753     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10754
10755     if(syncInterval) {
10756         p = q = appData.results;
10757         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10758         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10759             DisplayMessage(_("Waiting for other game(s)"),"");
10760             waitingForGame = TRUE;
10761             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10762             return 0;
10763         }
10764         waitingForGame = FALSE;
10765     }
10766
10767     if(appData.tourneyType < 0) {
10768         if(nr>=0 && !pairingReceived) {
10769             char buf[1<<16];
10770             if(pairing.pr == NoProc) {
10771                 if(!appData.pairingEngine[0]) {
10772                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10773                     return 0;
10774                 }
10775                 StartChessProgram(&pairing); // starts the pairing engine
10776             }
10777             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10778             SendToProgram(buf, &pairing);
10779             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10780             SendToProgram(buf, &pairing);
10781             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10782         }
10783         pairingReceived = 0;                              // ... so we continue here
10784         *swapColors = 0;
10785         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10786         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10787         matchGame = 1; roundNr = nr / syncInterval + 1;
10788     }
10789
10790     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10791
10792     // redefine engines, engine dir, etc.
10793     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10794     if(first.pr == NoProc) {
10795       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10796       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10797     }
10798     if(second.pr == NoProc) {
10799       SwapEngines(1);
10800       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10801       SwapEngines(1);         // and make that valid for second engine by swapping
10802       InitEngine(&second, 1);
10803     }
10804     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10805     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10806     return OK;
10807 }
10808
10809 void
10810 NextMatchGame ()
10811 {   // performs game initialization that does not invoke engines, and then tries to start the game
10812     int res, firstWhite, swapColors = 0;
10813     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10814     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
10815         char buf[MSG_SIZ];
10816         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10817         if(strcmp(buf, currentDebugFile)) { // name has changed
10818             FILE *f = fopen(buf, "w");
10819             if(f) { // if opening the new file failed, just keep using the old one
10820                 ASSIGN(currentDebugFile, buf);
10821                 fclose(debugFP);
10822                 debugFP = f;
10823             }
10824             if(appData.serverFileName) {
10825                 if(serverFP) fclose(serverFP);
10826                 serverFP = fopen(appData.serverFileName, "w");
10827                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10828                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10829             }
10830         }
10831     }
10832     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10833     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10834     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10835     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10836     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10837     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10838     Reset(FALSE, first.pr != NoProc);
10839     res = LoadGameOrPosition(matchGame); // setup game
10840     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10841     if(!res) return; // abort when bad game/pos file
10842     TwoMachinesEvent();
10843 }
10844
10845 void
10846 UserAdjudicationEvent (int result)
10847 {
10848     ChessMove gameResult = GameIsDrawn;
10849
10850     if( result > 0 ) {
10851         gameResult = WhiteWins;
10852     }
10853     else if( result < 0 ) {
10854         gameResult = BlackWins;
10855     }
10856
10857     if( gameMode == TwoMachinesPlay ) {
10858         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10859     }
10860 }
10861
10862
10863 // [HGM] save: calculate checksum of game to make games easily identifiable
10864 int
10865 StringCheckSum (char *s)
10866 {
10867         int i = 0;
10868         if(s==NULL) return 0;
10869         while(*s) i = i*259 + *s++;
10870         return i;
10871 }
10872
10873 int
10874 GameCheckSum ()
10875 {
10876         int i, sum=0;
10877         for(i=backwardMostMove; i<forwardMostMove; i++) {
10878                 sum += pvInfoList[i].depth;
10879                 sum += StringCheckSum(parseList[i]);
10880                 sum += StringCheckSum(commentList[i]);
10881                 sum *= 261;
10882         }
10883         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10884         return sum + StringCheckSum(commentList[i]);
10885 } // end of save patch
10886
10887 void
10888 GameEnds (ChessMove result, char *resultDetails, int whosays)
10889 {
10890     GameMode nextGameMode;
10891     int isIcsGame;
10892     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10893
10894     if(endingGame) return; /* [HGM] crash: forbid recursion */
10895     endingGame = 1;
10896     if(twoBoards) { // [HGM] dual: switch back to one board
10897         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10898         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10899     }
10900     if (appData.debugMode) {
10901       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10902               result, resultDetails ? resultDetails : "(null)", whosays);
10903     }
10904
10905     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
10906
10907     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
10908
10909     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10910         /* If we are playing on ICS, the server decides when the
10911            game is over, but the engine can offer to draw, claim
10912            a draw, or resign.
10913          */
10914 #if ZIPPY
10915         if (appData.zippyPlay && first.initDone) {
10916             if (result == GameIsDrawn) {
10917                 /* In case draw still needs to be claimed */
10918                 SendToICS(ics_prefix);
10919                 SendToICS("draw\n");
10920             } else if (StrCaseStr(resultDetails, "resign")) {
10921                 SendToICS(ics_prefix);
10922                 SendToICS("resign\n");
10923             }
10924         }
10925 #endif
10926         endingGame = 0; /* [HGM] crash */
10927         return;
10928     }
10929
10930     /* If we're loading the game from a file, stop */
10931     if (whosays == GE_FILE) {
10932       (void) StopLoadGameTimer();
10933       gameFileFP = NULL;
10934     }
10935
10936     /* Cancel draw offers */
10937     first.offeredDraw = second.offeredDraw = 0;
10938
10939     /* If this is an ICS game, only ICS can really say it's done;
10940        if not, anyone can. */
10941     isIcsGame = (gameMode == IcsPlayingWhite ||
10942                  gameMode == IcsPlayingBlack ||
10943                  gameMode == IcsObserving    ||
10944                  gameMode == IcsExamining);
10945
10946     if (!isIcsGame || whosays == GE_ICS) {
10947         /* OK -- not an ICS game, or ICS said it was done */
10948         StopClocks();
10949         if (!isIcsGame && !appData.noChessProgram)
10950           SetUserThinkingEnables();
10951
10952         /* [HGM] if a machine claims the game end we verify this claim */
10953         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10954             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10955                 char claimer;
10956                 ChessMove trueResult = (ChessMove) -1;
10957
10958                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10959                                             first.twoMachinesColor[0] :
10960                                             second.twoMachinesColor[0] ;
10961
10962                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10963                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10964                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10965                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10966                 } else
10967                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10968                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10969                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10970                 } else
10971                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10972                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10973                 }
10974
10975                 // now verify win claims, but not in drop games, as we don't understand those yet
10976                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10977                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10978                     (result == WhiteWins && claimer == 'w' ||
10979                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10980                       if (appData.debugMode) {
10981                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10982                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10983                       }
10984                       if(result != trueResult) {
10985                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10986                               result = claimer == 'w' ? BlackWins : WhiteWins;
10987                               resultDetails = buf;
10988                       }
10989                 } else
10990                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10991                     && (forwardMostMove <= backwardMostMove ||
10992                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10993                         (claimer=='b')==(forwardMostMove&1))
10994                                                                                   ) {
10995                       /* [HGM] verify: draws that were not flagged are false claims */
10996                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10997                       result = claimer == 'w' ? BlackWins : WhiteWins;
10998                       resultDetails = buf;
10999                 }
11000                 /* (Claiming a loss is accepted no questions asked!) */
11001             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11002                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11003                 result = GameUnfinished;
11004                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11005             }
11006             /* [HGM] bare: don't allow bare King to win */
11007             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11008                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11009                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11010                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11011                && result != GameIsDrawn)
11012             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11013                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11014                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11015                         if(p >= 0 && p <= (int)WhiteKing) k++;
11016                 }
11017                 if (appData.debugMode) {
11018                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11019                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11020                 }
11021                 if(k <= 1) {
11022                         result = GameIsDrawn;
11023                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11024                         resultDetails = buf;
11025                 }
11026             }
11027         }
11028
11029
11030         if(serverMoves != NULL && !loadFlag) { char c = '=';
11031             if(result==WhiteWins) c = '+';
11032             if(result==BlackWins) c = '-';
11033             if(resultDetails != NULL)
11034                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11035         }
11036         if (resultDetails != NULL) {
11037             gameInfo.result = result;
11038             gameInfo.resultDetails = StrSave(resultDetails);
11039
11040             /* display last move only if game was not loaded from file */
11041             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11042                 DisplayMove(currentMove - 1);
11043
11044             if (forwardMostMove != 0) {
11045                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11046                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11047                                                                 ) {
11048                     if (*appData.saveGameFile != NULLCHAR) {
11049                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11050                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11051                         else
11052                         SaveGameToFile(appData.saveGameFile, TRUE);
11053                     } else if (appData.autoSaveGames) {
11054                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11055                     }
11056                     if (*appData.savePositionFile != NULLCHAR) {
11057                         SavePositionToFile(appData.savePositionFile);
11058                     }
11059                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11060                 }
11061             }
11062
11063             /* Tell program how game ended in case it is learning */
11064             /* [HGM] Moved this to after saving the PGN, just in case */
11065             /* engine died and we got here through time loss. In that */
11066             /* case we will get a fatal error writing the pipe, which */
11067             /* would otherwise lose us the PGN.                       */
11068             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11069             /* output during GameEnds should never be fatal anymore   */
11070             if (gameMode == MachinePlaysWhite ||
11071                 gameMode == MachinePlaysBlack ||
11072                 gameMode == TwoMachinesPlay ||
11073                 gameMode == IcsPlayingWhite ||
11074                 gameMode == IcsPlayingBlack ||
11075                 gameMode == BeginningOfGame) {
11076                 char buf[MSG_SIZ];
11077                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11078                         resultDetails);
11079                 if (first.pr != NoProc) {
11080                     SendToProgram(buf, &first);
11081                 }
11082                 if (second.pr != NoProc &&
11083                     gameMode == TwoMachinesPlay) {
11084                     SendToProgram(buf, &second);
11085                 }
11086             }
11087         }
11088
11089         if (appData.icsActive) {
11090             if (appData.quietPlay &&
11091                 (gameMode == IcsPlayingWhite ||
11092                  gameMode == IcsPlayingBlack)) {
11093                 SendToICS(ics_prefix);
11094                 SendToICS("set shout 1\n");
11095             }
11096             nextGameMode = IcsIdle;
11097             ics_user_moved = FALSE;
11098             /* clean up premove.  It's ugly when the game has ended and the
11099              * premove highlights are still on the board.
11100              */
11101             if (gotPremove) {
11102               gotPremove = FALSE;
11103               ClearPremoveHighlights();
11104               DrawPosition(FALSE, boards[currentMove]);
11105             }
11106             if (whosays == GE_ICS) {
11107                 switch (result) {
11108                 case WhiteWins:
11109                     if (gameMode == IcsPlayingWhite)
11110                         PlayIcsWinSound();
11111                     else if(gameMode == IcsPlayingBlack)
11112                         PlayIcsLossSound();
11113                     break;
11114                 case BlackWins:
11115                     if (gameMode == IcsPlayingBlack)
11116                         PlayIcsWinSound();
11117                     else if(gameMode == IcsPlayingWhite)
11118                         PlayIcsLossSound();
11119                     break;
11120                 case GameIsDrawn:
11121                     PlayIcsDrawSound();
11122                     break;
11123                 default:
11124                     PlayIcsUnfinishedSound();
11125                 }
11126             }
11127             if(appData.quitNext) { ExitEvent(0); return; }
11128         } else if (gameMode == EditGame ||
11129                    gameMode == PlayFromGameFile ||
11130                    gameMode == AnalyzeMode ||
11131                    gameMode == AnalyzeFile) {
11132             nextGameMode = gameMode;
11133         } else {
11134             nextGameMode = EndOfGame;
11135         }
11136         pausing = FALSE;
11137         ModeHighlight();
11138     } else {
11139         nextGameMode = gameMode;
11140     }
11141
11142     if (appData.noChessProgram) {
11143         gameMode = nextGameMode;
11144         ModeHighlight();
11145         endingGame = 0; /* [HGM] crash */
11146         return;
11147     }
11148
11149     if (first.reuse) {
11150         /* Put first chess program into idle state */
11151         if (first.pr != NoProc &&
11152             (gameMode == MachinePlaysWhite ||
11153              gameMode == MachinePlaysBlack ||
11154              gameMode == TwoMachinesPlay ||
11155              gameMode == IcsPlayingWhite ||
11156              gameMode == IcsPlayingBlack ||
11157              gameMode == BeginningOfGame)) {
11158             SendToProgram("force\n", &first);
11159             if (first.usePing) {
11160               char buf[MSG_SIZ];
11161               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11162               SendToProgram(buf, &first);
11163             }
11164         }
11165     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11166         /* Kill off first chess program */
11167         if (first.isr != NULL)
11168           RemoveInputSource(first.isr);
11169         first.isr = NULL;
11170
11171         if (first.pr != NoProc) {
11172             ExitAnalyzeMode();
11173             DoSleep( appData.delayBeforeQuit );
11174             SendToProgram("quit\n", &first);
11175             DoSleep( appData.delayAfterQuit );
11176             DestroyChildProcess(first.pr, first.useSigterm);
11177             first.reload = TRUE;
11178         }
11179         first.pr = NoProc;
11180     }
11181     if (second.reuse) {
11182         /* Put second chess program into idle state */
11183         if (second.pr != NoProc &&
11184             gameMode == TwoMachinesPlay) {
11185             SendToProgram("force\n", &second);
11186             if (second.usePing) {
11187               char buf[MSG_SIZ];
11188               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11189               SendToProgram(buf, &second);
11190             }
11191         }
11192     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11193         /* Kill off second chess program */
11194         if (second.isr != NULL)
11195           RemoveInputSource(second.isr);
11196         second.isr = NULL;
11197
11198         if (second.pr != NoProc) {
11199             DoSleep( appData.delayBeforeQuit );
11200             SendToProgram("quit\n", &second);
11201             DoSleep( appData.delayAfterQuit );
11202             DestroyChildProcess(second.pr, second.useSigterm);
11203             second.reload = TRUE;
11204         }
11205         second.pr = NoProc;
11206     }
11207
11208     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11209         char resChar = '=';
11210         switch (result) {
11211         case WhiteWins:
11212           resChar = '+';
11213           if (first.twoMachinesColor[0] == 'w') {
11214             first.matchWins++;
11215           } else {
11216             second.matchWins++;
11217           }
11218           break;
11219         case BlackWins:
11220           resChar = '-';
11221           if (first.twoMachinesColor[0] == 'b') {
11222             first.matchWins++;
11223           } else {
11224             second.matchWins++;
11225           }
11226           break;
11227         case GameUnfinished:
11228           resChar = ' ';
11229         default:
11230           break;
11231         }
11232
11233         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11234         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11235             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11236             ReserveGame(nextGame, resChar); // sets nextGame
11237             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11238             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11239         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11240
11241         if (nextGame <= appData.matchGames && !abortMatch) {
11242             gameMode = nextGameMode;
11243             matchGame = nextGame; // this will be overruled in tourney mode!
11244             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11245             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11246             endingGame = 0; /* [HGM] crash */
11247             return;
11248         } else {
11249             gameMode = nextGameMode;
11250             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11251                      first.tidy, second.tidy,
11252                      first.matchWins, second.matchWins,
11253                      appData.matchGames - (first.matchWins + second.matchWins));
11254             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11255             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11256             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11257             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11258                 first.twoMachinesColor = "black\n";
11259                 second.twoMachinesColor = "white\n";
11260             } else {
11261                 first.twoMachinesColor = "white\n";
11262                 second.twoMachinesColor = "black\n";
11263             }
11264         }
11265     }
11266     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11267         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11268       ExitAnalyzeMode();
11269     gameMode = nextGameMode;
11270     ModeHighlight();
11271     endingGame = 0;  /* [HGM] crash */
11272     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11273         if(matchMode == TRUE) { // match through command line: exit with or without popup
11274             if(ranking) {
11275                 ToNrEvent(forwardMostMove);
11276                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11277                 else ExitEvent(0);
11278             } else DisplayFatalError(buf, 0, 0);
11279         } else { // match through menu; just stop, with or without popup
11280             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11281             ModeHighlight();
11282             if(ranking){
11283                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11284             } else DisplayNote(buf);
11285       }
11286       if(ranking) free(ranking);
11287     }
11288 }
11289
11290 /* Assumes program was just initialized (initString sent).
11291    Leaves program in force mode. */
11292 void
11293 FeedMovesToProgram (ChessProgramState *cps, int upto)
11294 {
11295     int i;
11296
11297     if (appData.debugMode)
11298       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11299               startedFromSetupPosition ? "position and " : "",
11300               backwardMostMove, upto, cps->which);
11301     if(currentlyInitializedVariant != gameInfo.variant) {
11302       char buf[MSG_SIZ];
11303         // [HGM] variantswitch: make engine aware of new variant
11304         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
11305                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11306         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11307         SendToProgram(buf, cps);
11308         currentlyInitializedVariant = gameInfo.variant;
11309     }
11310     SendToProgram("force\n", cps);
11311     if (startedFromSetupPosition) {
11312         SendBoard(cps, backwardMostMove);
11313     if (appData.debugMode) {
11314         fprintf(debugFP, "feedMoves\n");
11315     }
11316     }
11317     for (i = backwardMostMove; i < upto; i++) {
11318         SendMoveToProgram(i, cps);
11319     }
11320 }
11321
11322
11323 int
11324 ResurrectChessProgram ()
11325 {
11326      /* The chess program may have exited.
11327         If so, restart it and feed it all the moves made so far. */
11328     static int doInit = 0;
11329
11330     if (appData.noChessProgram) return 1;
11331
11332     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11333         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11334         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11335         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11336     } else {
11337         if (first.pr != NoProc) return 1;
11338         StartChessProgram(&first);
11339     }
11340     InitChessProgram(&first, FALSE);
11341     FeedMovesToProgram(&first, currentMove);
11342
11343     if (!first.sendTime) {
11344         /* can't tell gnuchess what its clock should read,
11345            so we bow to its notion. */
11346         ResetClocks();
11347         timeRemaining[0][currentMove] = whiteTimeRemaining;
11348         timeRemaining[1][currentMove] = blackTimeRemaining;
11349     }
11350
11351     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11352                 appData.icsEngineAnalyze) && first.analysisSupport) {
11353       SendToProgram("analyze\n", &first);
11354       first.analyzing = TRUE;
11355     }
11356     return 1;
11357 }
11358
11359 /*
11360  * Button procedures
11361  */
11362 void
11363 Reset (int redraw, int init)
11364 {
11365     int i;
11366
11367     if (appData.debugMode) {
11368         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11369                 redraw, init, gameMode);
11370     }
11371     CleanupTail(); // [HGM] vari: delete any stored variations
11372     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11373     pausing = pauseExamInvalid = FALSE;
11374     startedFromSetupPosition = blackPlaysFirst = FALSE;
11375     firstMove = TRUE;
11376     whiteFlag = blackFlag = FALSE;
11377     userOfferedDraw = FALSE;
11378     hintRequested = bookRequested = FALSE;
11379     first.maybeThinking = FALSE;
11380     second.maybeThinking = FALSE;
11381     first.bookSuspend = FALSE; // [HGM] book
11382     second.bookSuspend = FALSE;
11383     thinkOutput[0] = NULLCHAR;
11384     lastHint[0] = NULLCHAR;
11385     ClearGameInfo(&gameInfo);
11386     gameInfo.variant = StringToVariant(appData.variant);
11387     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11388     ics_user_moved = ics_clock_paused = FALSE;
11389     ics_getting_history = H_FALSE;
11390     ics_gamenum = -1;
11391     white_holding[0] = black_holding[0] = NULLCHAR;
11392     ClearProgramStats();
11393     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11394
11395     ResetFrontEnd();
11396     ClearHighlights();
11397     flipView = appData.flipView;
11398     ClearPremoveHighlights();
11399     gotPremove = FALSE;
11400     alarmSounded = FALSE;
11401     killX = killY = -1; // [HGM] lion
11402
11403     GameEnds(EndOfFile, NULL, GE_PLAYER);
11404     if(appData.serverMovesName != NULL) {
11405         /* [HGM] prepare to make moves file for broadcasting */
11406         clock_t t = clock();
11407         if(serverMoves != NULL) fclose(serverMoves);
11408         serverMoves = fopen(appData.serverMovesName, "r");
11409         if(serverMoves != NULL) {
11410             fclose(serverMoves);
11411             /* delay 15 sec before overwriting, so all clients can see end */
11412             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11413         }
11414         serverMoves = fopen(appData.serverMovesName, "w");
11415     }
11416
11417     ExitAnalyzeMode();
11418     gameMode = BeginningOfGame;
11419     ModeHighlight();
11420     if(appData.icsActive) gameInfo.variant = VariantNormal;
11421     currentMove = forwardMostMove = backwardMostMove = 0;
11422     MarkTargetSquares(1);
11423     InitPosition(redraw);
11424     for (i = 0; i < MAX_MOVES; i++) {
11425         if (commentList[i] != NULL) {
11426             free(commentList[i]);
11427             commentList[i] = NULL;
11428         }
11429     }
11430     ResetClocks();
11431     timeRemaining[0][0] = whiteTimeRemaining;
11432     timeRemaining[1][0] = blackTimeRemaining;
11433
11434     if (first.pr == NoProc) {
11435         StartChessProgram(&first);
11436     }
11437     if (init) {
11438             InitChessProgram(&first, startedFromSetupPosition);
11439     }
11440     DisplayTitle("");
11441     DisplayMessage("", "");
11442     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11443     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11444     ClearMap();        // [HGM] exclude: invalidate map
11445 }
11446
11447 void
11448 AutoPlayGameLoop ()
11449 {
11450     for (;;) {
11451         if (!AutoPlayOneMove())
11452           return;
11453         if (matchMode || appData.timeDelay == 0)
11454           continue;
11455         if (appData.timeDelay < 0)
11456           return;
11457         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11458         break;
11459     }
11460 }
11461
11462 void
11463 AnalyzeNextGame()
11464 {
11465     ReloadGame(1); // next game
11466 }
11467
11468 int
11469 AutoPlayOneMove ()
11470 {
11471     int fromX, fromY, toX, toY;
11472
11473     if (appData.debugMode) {
11474       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11475     }
11476
11477     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11478       return FALSE;
11479
11480     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11481       pvInfoList[currentMove].depth = programStats.depth;
11482       pvInfoList[currentMove].score = programStats.score;
11483       pvInfoList[currentMove].time  = 0;
11484       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11485       else { // append analysis of final position as comment
11486         char buf[MSG_SIZ];
11487         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11488         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11489       }
11490       programStats.depth = 0;
11491     }
11492
11493     if (currentMove >= forwardMostMove) {
11494       if(gameMode == AnalyzeFile) {
11495           if(appData.loadGameIndex == -1) {
11496             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11497           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11498           } else {
11499           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11500         }
11501       }
11502 //      gameMode = EndOfGame;
11503 //      ModeHighlight();
11504
11505       /* [AS] Clear current move marker at the end of a game */
11506       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11507
11508       return FALSE;
11509     }
11510
11511     toX = moveList[currentMove][2] - AAA;
11512     toY = moveList[currentMove][3] - ONE;
11513
11514     if (moveList[currentMove][1] == '@') {
11515         if (appData.highlightLastMove) {
11516             SetHighlights(-1, -1, toX, toY);
11517         }
11518     } else {
11519         fromX = moveList[currentMove][0] - AAA;
11520         fromY = moveList[currentMove][1] - ONE;
11521
11522         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11523
11524         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11525
11526         if (appData.highlightLastMove) {
11527             SetHighlights(fromX, fromY, toX, toY);
11528         }
11529     }
11530     DisplayMove(currentMove);
11531     SendMoveToProgram(currentMove++, &first);
11532     DisplayBothClocks();
11533     DrawPosition(FALSE, boards[currentMove]);
11534     // [HGM] PV info: always display, routine tests if empty
11535     DisplayComment(currentMove - 1, commentList[currentMove]);
11536     return TRUE;
11537 }
11538
11539
11540 int
11541 LoadGameOneMove (ChessMove readAhead)
11542 {
11543     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11544     char promoChar = NULLCHAR;
11545     ChessMove moveType;
11546     char move[MSG_SIZ];
11547     char *p, *q;
11548
11549     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11550         gameMode != AnalyzeMode && gameMode != Training) {
11551         gameFileFP = NULL;
11552         return FALSE;
11553     }
11554
11555     yyboardindex = forwardMostMove;
11556     if (readAhead != EndOfFile) {
11557       moveType = readAhead;
11558     } else {
11559       if (gameFileFP == NULL)
11560           return FALSE;
11561       moveType = (ChessMove) Myylex();
11562     }
11563
11564     done = FALSE;
11565     switch (moveType) {
11566       case Comment:
11567         if (appData.debugMode)
11568           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11569         p = yy_text;
11570
11571         /* append the comment but don't display it */
11572         AppendComment(currentMove, p, FALSE);
11573         return TRUE;
11574
11575       case WhiteCapturesEnPassant:
11576       case BlackCapturesEnPassant:
11577       case WhitePromotion:
11578       case BlackPromotion:
11579       case WhiteNonPromotion:
11580       case BlackNonPromotion:
11581       case NormalMove:
11582       case FirstLeg:
11583       case WhiteKingSideCastle:
11584       case WhiteQueenSideCastle:
11585       case BlackKingSideCastle:
11586       case BlackQueenSideCastle:
11587       case WhiteKingSideCastleWild:
11588       case WhiteQueenSideCastleWild:
11589       case BlackKingSideCastleWild:
11590       case BlackQueenSideCastleWild:
11591       /* PUSH Fabien */
11592       case WhiteHSideCastleFR:
11593       case WhiteASideCastleFR:
11594       case BlackHSideCastleFR:
11595       case BlackASideCastleFR:
11596       /* POP Fabien */
11597         if (appData.debugMode)
11598           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11599         fromX = currentMoveString[0] - AAA;
11600         fromY = currentMoveString[1] - ONE;
11601         toX = currentMoveString[2] - AAA;
11602         toY = currentMoveString[3] - ONE;
11603         promoChar = currentMoveString[4];
11604         if(promoChar == ';') promoChar = NULLCHAR;
11605         break;
11606
11607       case WhiteDrop:
11608       case BlackDrop:
11609         if (appData.debugMode)
11610           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11611         fromX = moveType == WhiteDrop ?
11612           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11613         (int) CharToPiece(ToLower(currentMoveString[0]));
11614         fromY = DROP_RANK;
11615         toX = currentMoveString[2] - AAA;
11616         toY = currentMoveString[3] - ONE;
11617         break;
11618
11619       case WhiteWins:
11620       case BlackWins:
11621       case GameIsDrawn:
11622       case GameUnfinished:
11623         if (appData.debugMode)
11624           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11625         p = strchr(yy_text, '{');
11626         if (p == NULL) p = strchr(yy_text, '(');
11627         if (p == NULL) {
11628             p = yy_text;
11629             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11630         } else {
11631             q = strchr(p, *p == '{' ? '}' : ')');
11632             if (q != NULL) *q = NULLCHAR;
11633             p++;
11634         }
11635         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11636         GameEnds(moveType, p, GE_FILE);
11637         done = TRUE;
11638         if (cmailMsgLoaded) {
11639             ClearHighlights();
11640             flipView = WhiteOnMove(currentMove);
11641             if (moveType == GameUnfinished) flipView = !flipView;
11642             if (appData.debugMode)
11643               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11644         }
11645         break;
11646
11647       case EndOfFile:
11648         if (appData.debugMode)
11649           fprintf(debugFP, "Parser hit end of file\n");
11650         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11651           case MT_NONE:
11652           case MT_CHECK:
11653             break;
11654           case MT_CHECKMATE:
11655           case MT_STAINMATE:
11656             if (WhiteOnMove(currentMove)) {
11657                 GameEnds(BlackWins, "Black mates", GE_FILE);
11658             } else {
11659                 GameEnds(WhiteWins, "White mates", GE_FILE);
11660             }
11661             break;
11662           case MT_STALEMATE:
11663             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11664             break;
11665         }
11666         done = TRUE;
11667         break;
11668
11669       case MoveNumberOne:
11670         if (lastLoadGameStart == GNUChessGame) {
11671             /* GNUChessGames have numbers, but they aren't move numbers */
11672             if (appData.debugMode)
11673               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11674                       yy_text, (int) moveType);
11675             return LoadGameOneMove(EndOfFile); /* tail recursion */
11676         }
11677         /* else fall thru */
11678
11679       case XBoardGame:
11680       case GNUChessGame:
11681       case PGNTag:
11682         /* Reached start of next game in file */
11683         if (appData.debugMode)
11684           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11685         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11686           case MT_NONE:
11687           case MT_CHECK:
11688             break;
11689           case MT_CHECKMATE:
11690           case MT_STAINMATE:
11691             if (WhiteOnMove(currentMove)) {
11692                 GameEnds(BlackWins, "Black mates", GE_FILE);
11693             } else {
11694                 GameEnds(WhiteWins, "White mates", GE_FILE);
11695             }
11696             break;
11697           case MT_STALEMATE:
11698             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11699             break;
11700         }
11701         done = TRUE;
11702         break;
11703
11704       case PositionDiagram:     /* should not happen; ignore */
11705       case ElapsedTime:         /* ignore */
11706       case NAG:                 /* ignore */
11707         if (appData.debugMode)
11708           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11709                   yy_text, (int) moveType);
11710         return LoadGameOneMove(EndOfFile); /* tail recursion */
11711
11712       case IllegalMove:
11713         if (appData.testLegality) {
11714             if (appData.debugMode)
11715               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11716             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11717                     (forwardMostMove / 2) + 1,
11718                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11719             DisplayError(move, 0);
11720             done = TRUE;
11721         } else {
11722             if (appData.debugMode)
11723               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11724                       yy_text, currentMoveString);
11725             fromX = currentMoveString[0] - AAA;
11726             fromY = currentMoveString[1] - ONE;
11727             toX = currentMoveString[2] - AAA;
11728             toY = currentMoveString[3] - ONE;
11729             promoChar = currentMoveString[4];
11730         }
11731         break;
11732
11733       case AmbiguousMove:
11734         if (appData.debugMode)
11735           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11736         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11737                 (forwardMostMove / 2) + 1,
11738                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11739         DisplayError(move, 0);
11740         done = TRUE;
11741         break;
11742
11743       default:
11744       case ImpossibleMove:
11745         if (appData.debugMode)
11746           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11747         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11748                 (forwardMostMove / 2) + 1,
11749                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11750         DisplayError(move, 0);
11751         done = TRUE;
11752         break;
11753     }
11754
11755     if (done) {
11756         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11757             DrawPosition(FALSE, boards[currentMove]);
11758             DisplayBothClocks();
11759             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11760               DisplayComment(currentMove - 1, commentList[currentMove]);
11761         }
11762         (void) StopLoadGameTimer();
11763         gameFileFP = NULL;
11764         cmailOldMove = forwardMostMove;
11765         return FALSE;
11766     } else {
11767         /* currentMoveString is set as a side-effect of yylex */
11768
11769         thinkOutput[0] = NULLCHAR;
11770         MakeMove(fromX, fromY, toX, toY, promoChar);
11771         killX = killY = -1; // [HGM] lion: used up
11772         currentMove = forwardMostMove;
11773         return TRUE;
11774     }
11775 }
11776
11777 /* Load the nth game from the given file */
11778 int
11779 LoadGameFromFile (char *filename, int n, char *title, int useList)
11780 {
11781     FILE *f;
11782     char buf[MSG_SIZ];
11783
11784     if (strcmp(filename, "-") == 0) {
11785         f = stdin;
11786         title = "stdin";
11787     } else {
11788         f = fopen(filename, "rb");
11789         if (f == NULL) {
11790           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11791             DisplayError(buf, errno);
11792             return FALSE;
11793         }
11794     }
11795     if (fseek(f, 0, 0) == -1) {
11796         /* f is not seekable; probably a pipe */
11797         useList = FALSE;
11798     }
11799     if (useList && n == 0) {
11800         int error = GameListBuild(f);
11801         if (error) {
11802             DisplayError(_("Cannot build game list"), error);
11803         } else if (!ListEmpty(&gameList) &&
11804                    ((ListGame *) gameList.tailPred)->number > 1) {
11805             GameListPopUp(f, title);
11806             return TRUE;
11807         }
11808         GameListDestroy();
11809         n = 1;
11810     }
11811     if (n == 0) n = 1;
11812     return LoadGame(f, n, title, FALSE);
11813 }
11814
11815
11816 void
11817 MakeRegisteredMove ()
11818 {
11819     int fromX, fromY, toX, toY;
11820     char promoChar;
11821     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11822         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11823           case CMAIL_MOVE:
11824           case CMAIL_DRAW:
11825             if (appData.debugMode)
11826               fprintf(debugFP, "Restoring %s for game %d\n",
11827                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11828
11829             thinkOutput[0] = NULLCHAR;
11830             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11831             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11832             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11833             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11834             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11835             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11836             MakeMove(fromX, fromY, toX, toY, promoChar);
11837             ShowMove(fromX, fromY, toX, toY);
11838
11839             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11840               case MT_NONE:
11841               case MT_CHECK:
11842                 break;
11843
11844               case MT_CHECKMATE:
11845               case MT_STAINMATE:
11846                 if (WhiteOnMove(currentMove)) {
11847                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11848                 } else {
11849                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11850                 }
11851                 break;
11852
11853               case MT_STALEMATE:
11854                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11855                 break;
11856             }
11857
11858             break;
11859
11860           case CMAIL_RESIGN:
11861             if (WhiteOnMove(currentMove)) {
11862                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11863             } else {
11864                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11865             }
11866             break;
11867
11868           case CMAIL_ACCEPT:
11869             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11870             break;
11871
11872           default:
11873             break;
11874         }
11875     }
11876
11877     return;
11878 }
11879
11880 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11881 int
11882 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11883 {
11884     int retVal;
11885
11886     if (gameNumber > nCmailGames) {
11887         DisplayError(_("No more games in this message"), 0);
11888         return FALSE;
11889     }
11890     if (f == lastLoadGameFP) {
11891         int offset = gameNumber - lastLoadGameNumber;
11892         if (offset == 0) {
11893             cmailMsg[0] = NULLCHAR;
11894             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11895                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11896                 nCmailMovesRegistered--;
11897             }
11898             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11899             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11900                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11901             }
11902         } else {
11903             if (! RegisterMove()) return FALSE;
11904         }
11905     }
11906
11907     retVal = LoadGame(f, gameNumber, title, useList);
11908
11909     /* Make move registered during previous look at this game, if any */
11910     MakeRegisteredMove();
11911
11912     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11913         commentList[currentMove]
11914           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11915         DisplayComment(currentMove - 1, commentList[currentMove]);
11916     }
11917
11918     return retVal;
11919 }
11920
11921 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11922 int
11923 ReloadGame (int offset)
11924 {
11925     int gameNumber = lastLoadGameNumber + offset;
11926     if (lastLoadGameFP == NULL) {
11927         DisplayError(_("No game has been loaded yet"), 0);
11928         return FALSE;
11929     }
11930     if (gameNumber <= 0) {
11931         DisplayError(_("Can't back up any further"), 0);
11932         return FALSE;
11933     }
11934     if (cmailMsgLoaded) {
11935         return CmailLoadGame(lastLoadGameFP, gameNumber,
11936                              lastLoadGameTitle, lastLoadGameUseList);
11937     } else {
11938         return LoadGame(lastLoadGameFP, gameNumber,
11939                         lastLoadGameTitle, lastLoadGameUseList);
11940     }
11941 }
11942
11943 int keys[EmptySquare+1];
11944
11945 int
11946 PositionMatches (Board b1, Board b2)
11947 {
11948     int r, f, sum=0;
11949     switch(appData.searchMode) {
11950         case 1: return CompareWithRights(b1, b2);
11951         case 2:
11952             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11953                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11954             }
11955             return TRUE;
11956         case 3:
11957             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11958               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11959                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11960             }
11961             return sum==0;
11962         case 4:
11963             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11964                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11965             }
11966             return sum==0;
11967     }
11968     return TRUE;
11969 }
11970
11971 #define Q_PROMO  4
11972 #define Q_EP     3
11973 #define Q_BCASTL 2
11974 #define Q_WCASTL 1
11975
11976 int pieceList[256], quickBoard[256];
11977 ChessSquare pieceType[256] = { EmptySquare };
11978 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11979 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11980 int soughtTotal, turn;
11981 Boolean epOK, flipSearch;
11982
11983 typedef struct {
11984     unsigned char piece, to;
11985 } Move;
11986
11987 #define DSIZE (250000)
11988
11989 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11990 Move *moveDatabase = initialSpace;
11991 unsigned int movePtr, dataSize = DSIZE;
11992
11993 int
11994 MakePieceList (Board board, int *counts)
11995 {
11996     int r, f, n=Q_PROMO, total=0;
11997     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11998     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11999         int sq = f + (r<<4);
12000         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12001             quickBoard[sq] = ++n;
12002             pieceList[n] = sq;
12003             pieceType[n] = board[r][f];
12004             counts[board[r][f]]++;
12005             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12006             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12007             total++;
12008         }
12009     }
12010     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12011     return total;
12012 }
12013
12014 void
12015 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12016 {
12017     int sq = fromX + (fromY<<4);
12018     int piece = quickBoard[sq];
12019     quickBoard[sq] = 0;
12020     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12021     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12022         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12023         moveDatabase[movePtr++].piece = Q_WCASTL;
12024         quickBoard[sq] = piece;
12025         piece = quickBoard[from]; quickBoard[from] = 0;
12026         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12027     } else
12028     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12029         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12030         moveDatabase[movePtr++].piece = Q_BCASTL;
12031         quickBoard[sq] = piece;
12032         piece = quickBoard[from]; quickBoard[from] = 0;
12033         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12034     } else
12035     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12036         quickBoard[(fromY<<4)+toX] = 0;
12037         moveDatabase[movePtr].piece = Q_EP;
12038         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12039         moveDatabase[movePtr].to = sq;
12040     } else
12041     if(promoPiece != pieceType[piece]) {
12042         moveDatabase[movePtr++].piece = Q_PROMO;
12043         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12044     }
12045     moveDatabase[movePtr].piece = piece;
12046     quickBoard[sq] = piece;
12047     movePtr++;
12048 }
12049
12050 int
12051 PackGame (Board board)
12052 {
12053     Move *newSpace = NULL;
12054     moveDatabase[movePtr].piece = 0; // terminate previous game
12055     if(movePtr > dataSize) {
12056         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12057         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12058         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12059         if(newSpace) {
12060             int i;
12061             Move *p = moveDatabase, *q = newSpace;
12062             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12063             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12064             moveDatabase = newSpace;
12065         } else { // calloc failed, we must be out of memory. Too bad...
12066             dataSize = 0; // prevent calloc events for all subsequent games
12067             return 0;     // and signal this one isn't cached
12068         }
12069     }
12070     movePtr++;
12071     MakePieceList(board, counts);
12072     return movePtr;
12073 }
12074
12075 int
12076 QuickCompare (Board board, int *minCounts, int *maxCounts)
12077 {   // compare according to search mode
12078     int r, f;
12079     switch(appData.searchMode)
12080     {
12081       case 1: // exact position match
12082         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12083         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12084             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12085         }
12086         break;
12087       case 2: // can have extra material on empty squares
12088         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12089             if(board[r][f] == EmptySquare) continue;
12090             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12091         }
12092         break;
12093       case 3: // material with exact Pawn structure
12094         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12095             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12096             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12097         } // fall through to material comparison
12098       case 4: // exact material
12099         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12100         break;
12101       case 6: // material range with given imbalance
12102         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12103         // fall through to range comparison
12104       case 5: // material range
12105         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12106     }
12107     return TRUE;
12108 }
12109
12110 int
12111 QuickScan (Board board, Move *move)
12112 {   // reconstruct game,and compare all positions in it
12113     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12114     do {
12115         int piece = move->piece;
12116         int to = move->to, from = pieceList[piece];
12117         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12118           if(!piece) return -1;
12119           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12120             piece = (++move)->piece;
12121             from = pieceList[piece];
12122             counts[pieceType[piece]]--;
12123             pieceType[piece] = (ChessSquare) move->to;
12124             counts[move->to]++;
12125           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12126             counts[pieceType[quickBoard[to]]]--;
12127             quickBoard[to] = 0; total--;
12128             move++;
12129             continue;
12130           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12131             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12132             from  = pieceList[piece]; // so this must be King
12133             quickBoard[from] = 0;
12134             pieceList[piece] = to;
12135             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12136             quickBoard[from] = 0; // rook
12137             quickBoard[to] = piece;
12138             to = move->to; piece = move->piece;
12139             goto aftercastle;
12140           }
12141         }
12142         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12143         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12144         quickBoard[from] = 0;
12145       aftercastle:
12146         quickBoard[to] = piece;
12147         pieceList[piece] = to;
12148         cnt++; turn ^= 3;
12149         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12150            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12151            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12152                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12153           ) {
12154             static int lastCounts[EmptySquare+1];
12155             int i;
12156             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12157             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12158         } else stretch = 0;
12159         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12160         move++;
12161     } while(1);
12162 }
12163
12164 void
12165 InitSearch ()
12166 {
12167     int r, f;
12168     flipSearch = FALSE;
12169     CopyBoard(soughtBoard, boards[currentMove]);
12170     soughtTotal = MakePieceList(soughtBoard, maxSought);
12171     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12172     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12173     CopyBoard(reverseBoard, boards[currentMove]);
12174     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12175         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12176         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12177         reverseBoard[r][f] = piece;
12178     }
12179     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12180     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12181     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12182                  || (boards[currentMove][CASTLING][2] == NoRights ||
12183                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12184                  && (boards[currentMove][CASTLING][5] == NoRights ||
12185                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12186       ) {
12187         flipSearch = TRUE;
12188         CopyBoard(flipBoard, soughtBoard);
12189         CopyBoard(rotateBoard, reverseBoard);
12190         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12191             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12192             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12193         }
12194     }
12195     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12196     if(appData.searchMode >= 5) {
12197         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12198         MakePieceList(soughtBoard, minSought);
12199         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12200     }
12201     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12202         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12203 }
12204
12205 GameInfo dummyInfo;
12206 static int creatingBook;
12207
12208 int
12209 GameContainsPosition (FILE *f, ListGame *lg)
12210 {
12211     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12212     int fromX, fromY, toX, toY;
12213     char promoChar;
12214     static int initDone=FALSE;
12215
12216     // weed out games based on numerical tag comparison
12217     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12218     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12219     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12220     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12221     if(!initDone) {
12222         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12223         initDone = TRUE;
12224     }
12225     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12226     else CopyBoard(boards[scratch], initialPosition); // default start position
12227     if(lg->moves) {
12228         turn = btm + 1;
12229         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12230         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12231     }
12232     if(btm) plyNr++;
12233     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12234     fseek(f, lg->offset, 0);
12235     yynewfile(f);
12236     while(1) {
12237         yyboardindex = scratch;
12238         quickFlag = plyNr+1;
12239         next = Myylex();
12240         quickFlag = 0;
12241         switch(next) {
12242             case PGNTag:
12243                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12244             default:
12245                 continue;
12246
12247             case XBoardGame:
12248             case GNUChessGame:
12249                 if(plyNr) return -1; // after we have seen moves, this is for new game
12250               continue;
12251
12252             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12253             case ImpossibleMove:
12254             case WhiteWins: // game ends here with these four
12255             case BlackWins:
12256             case GameIsDrawn:
12257             case GameUnfinished:
12258                 return -1;
12259
12260             case IllegalMove:
12261                 if(appData.testLegality) return -1;
12262             case WhiteCapturesEnPassant:
12263             case BlackCapturesEnPassant:
12264             case WhitePromotion:
12265             case BlackPromotion:
12266             case WhiteNonPromotion:
12267             case BlackNonPromotion:
12268             case NormalMove:
12269             case FirstLeg:
12270             case WhiteKingSideCastle:
12271             case WhiteQueenSideCastle:
12272             case BlackKingSideCastle:
12273             case BlackQueenSideCastle:
12274             case WhiteKingSideCastleWild:
12275             case WhiteQueenSideCastleWild:
12276             case BlackKingSideCastleWild:
12277             case BlackQueenSideCastleWild:
12278             case WhiteHSideCastleFR:
12279             case WhiteASideCastleFR:
12280             case BlackHSideCastleFR:
12281             case BlackASideCastleFR:
12282                 fromX = currentMoveString[0] - AAA;
12283                 fromY = currentMoveString[1] - ONE;
12284                 toX = currentMoveString[2] - AAA;
12285                 toY = currentMoveString[3] - ONE;
12286                 promoChar = currentMoveString[4];
12287                 break;
12288             case WhiteDrop:
12289             case BlackDrop:
12290                 fromX = next == WhiteDrop ?
12291                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12292                   (int) CharToPiece(ToLower(currentMoveString[0]));
12293                 fromY = DROP_RANK;
12294                 toX = currentMoveString[2] - AAA;
12295                 toY = currentMoveString[3] - ONE;
12296                 promoChar = 0;
12297                 break;
12298         }
12299         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12300         plyNr++;
12301         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12302         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12303         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12304         if(appData.findMirror) {
12305             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12306             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12307         }
12308     }
12309 }
12310
12311 /* Load the nth game from open file f */
12312 int
12313 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12314 {
12315     ChessMove cm;
12316     char buf[MSG_SIZ];
12317     int gn = gameNumber;
12318     ListGame *lg = NULL;
12319     int numPGNTags = 0;
12320     int err, pos = -1;
12321     GameMode oldGameMode;
12322     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12323
12324     if (appData.debugMode)
12325         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12326
12327     if (gameMode == Training )
12328         SetTrainingModeOff();
12329
12330     oldGameMode = gameMode;
12331     if (gameMode != BeginningOfGame) {
12332       Reset(FALSE, TRUE);
12333     }
12334     killX = killY = -1; // [HGM] lion: in case we did not Reset
12335
12336     gameFileFP = f;
12337     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12338         fclose(lastLoadGameFP);
12339     }
12340
12341     if (useList) {
12342         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12343
12344         if (lg) {
12345             fseek(f, lg->offset, 0);
12346             GameListHighlight(gameNumber);
12347             pos = lg->position;
12348             gn = 1;
12349         }
12350         else {
12351             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12352               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12353             else
12354             DisplayError(_("Game number out of range"), 0);
12355             return FALSE;
12356         }
12357     } else {
12358         GameListDestroy();
12359         if (fseek(f, 0, 0) == -1) {
12360             if (f == lastLoadGameFP ?
12361                 gameNumber == lastLoadGameNumber + 1 :
12362                 gameNumber == 1) {
12363                 gn = 1;
12364             } else {
12365                 DisplayError(_("Can't seek on game file"), 0);
12366                 return FALSE;
12367             }
12368         }
12369     }
12370     lastLoadGameFP = f;
12371     lastLoadGameNumber = gameNumber;
12372     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12373     lastLoadGameUseList = useList;
12374
12375     yynewfile(f);
12376
12377     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12378       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12379                 lg->gameInfo.black);
12380             DisplayTitle(buf);
12381     } else if (*title != NULLCHAR) {
12382         if (gameNumber > 1) {
12383           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12384             DisplayTitle(buf);
12385         } else {
12386             DisplayTitle(title);
12387         }
12388     }
12389
12390     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12391         gameMode = PlayFromGameFile;
12392         ModeHighlight();
12393     }
12394
12395     currentMove = forwardMostMove = backwardMostMove = 0;
12396     CopyBoard(boards[0], initialPosition);
12397     StopClocks();
12398
12399     /*
12400      * Skip the first gn-1 games in the file.
12401      * Also skip over anything that precedes an identifiable
12402      * start of game marker, to avoid being confused by
12403      * garbage at the start of the file.  Currently
12404      * recognized start of game markers are the move number "1",
12405      * the pattern "gnuchess .* game", the pattern
12406      * "^[#;%] [^ ]* game file", and a PGN tag block.
12407      * A game that starts with one of the latter two patterns
12408      * will also have a move number 1, possibly
12409      * following a position diagram.
12410      * 5-4-02: Let's try being more lenient and allowing a game to
12411      * start with an unnumbered move.  Does that break anything?
12412      */
12413     cm = lastLoadGameStart = EndOfFile;
12414     while (gn > 0) {
12415         yyboardindex = forwardMostMove;
12416         cm = (ChessMove) Myylex();
12417         switch (cm) {
12418           case EndOfFile:
12419             if (cmailMsgLoaded) {
12420                 nCmailGames = CMAIL_MAX_GAMES - gn;
12421             } else {
12422                 Reset(TRUE, TRUE);
12423                 DisplayError(_("Game not found in file"), 0);
12424             }
12425             return FALSE;
12426
12427           case GNUChessGame:
12428           case XBoardGame:
12429             gn--;
12430             lastLoadGameStart = cm;
12431             break;
12432
12433           case MoveNumberOne:
12434             switch (lastLoadGameStart) {
12435               case GNUChessGame:
12436               case XBoardGame:
12437               case PGNTag:
12438                 break;
12439               case MoveNumberOne:
12440               case EndOfFile:
12441                 gn--;           /* count this game */
12442                 lastLoadGameStart = cm;
12443                 break;
12444               default:
12445                 /* impossible */
12446                 break;
12447             }
12448             break;
12449
12450           case PGNTag:
12451             switch (lastLoadGameStart) {
12452               case GNUChessGame:
12453               case PGNTag:
12454               case MoveNumberOne:
12455               case EndOfFile:
12456                 gn--;           /* count this game */
12457                 lastLoadGameStart = cm;
12458                 break;
12459               case XBoardGame:
12460                 lastLoadGameStart = cm; /* game counted already */
12461                 break;
12462               default:
12463                 /* impossible */
12464                 break;
12465             }
12466             if (gn > 0) {
12467                 do {
12468                     yyboardindex = forwardMostMove;
12469                     cm = (ChessMove) Myylex();
12470                 } while (cm == PGNTag || cm == Comment);
12471             }
12472             break;
12473
12474           case WhiteWins:
12475           case BlackWins:
12476           case GameIsDrawn:
12477             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12478                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12479                     != CMAIL_OLD_RESULT) {
12480                     nCmailResults ++ ;
12481                     cmailResult[  CMAIL_MAX_GAMES
12482                                 - gn - 1] = CMAIL_OLD_RESULT;
12483                 }
12484             }
12485             break;
12486
12487           case NormalMove:
12488           case FirstLeg:
12489             /* Only a NormalMove can be at the start of a game
12490              * without a position diagram. */
12491             if (lastLoadGameStart == EndOfFile ) {
12492               gn--;
12493               lastLoadGameStart = MoveNumberOne;
12494             }
12495             break;
12496
12497           default:
12498             break;
12499         }
12500     }
12501
12502     if (appData.debugMode)
12503       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12504
12505     if (cm == XBoardGame) {
12506         /* Skip any header junk before position diagram and/or move 1 */
12507         for (;;) {
12508             yyboardindex = forwardMostMove;
12509             cm = (ChessMove) Myylex();
12510
12511             if (cm == EndOfFile ||
12512                 cm == GNUChessGame || cm == XBoardGame) {
12513                 /* Empty game; pretend end-of-file and handle later */
12514                 cm = EndOfFile;
12515                 break;
12516             }
12517
12518             if (cm == MoveNumberOne || cm == PositionDiagram ||
12519                 cm == PGNTag || cm == Comment)
12520               break;
12521         }
12522     } else if (cm == GNUChessGame) {
12523         if (gameInfo.event != NULL) {
12524             free(gameInfo.event);
12525         }
12526         gameInfo.event = StrSave(yy_text);
12527     }
12528
12529     startedFromSetupPosition = FALSE;
12530     while (cm == PGNTag) {
12531         if (appData.debugMode)
12532           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12533         err = ParsePGNTag(yy_text, &gameInfo);
12534         if (!err) numPGNTags++;
12535
12536         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12537         if(gameInfo.variant != oldVariant) {
12538             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12539             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12540             InitPosition(TRUE);
12541             oldVariant = gameInfo.variant;
12542             if (appData.debugMode)
12543               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12544         }
12545
12546
12547         if (gameInfo.fen != NULL) {
12548           Board initial_position;
12549           startedFromSetupPosition = TRUE;
12550           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12551             Reset(TRUE, TRUE);
12552             DisplayError(_("Bad FEN position in file"), 0);
12553             return FALSE;
12554           }
12555           CopyBoard(boards[0], initial_position);
12556           if (blackPlaysFirst) {
12557             currentMove = forwardMostMove = backwardMostMove = 1;
12558             CopyBoard(boards[1], initial_position);
12559             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12560             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12561             timeRemaining[0][1] = whiteTimeRemaining;
12562             timeRemaining[1][1] = blackTimeRemaining;
12563             if (commentList[0] != NULL) {
12564               commentList[1] = commentList[0];
12565               commentList[0] = NULL;
12566             }
12567           } else {
12568             currentMove = forwardMostMove = backwardMostMove = 0;
12569           }
12570           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12571           {   int i;
12572               initialRulePlies = FENrulePlies;
12573               for( i=0; i< nrCastlingRights; i++ )
12574                   initialRights[i] = initial_position[CASTLING][i];
12575           }
12576           yyboardindex = forwardMostMove;
12577           free(gameInfo.fen);
12578           gameInfo.fen = NULL;
12579         }
12580
12581         yyboardindex = forwardMostMove;
12582         cm = (ChessMove) Myylex();
12583
12584         /* Handle comments interspersed among the tags */
12585         while (cm == Comment) {
12586             char *p;
12587             if (appData.debugMode)
12588               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12589             p = yy_text;
12590             AppendComment(currentMove, p, FALSE);
12591             yyboardindex = forwardMostMove;
12592             cm = (ChessMove) Myylex();
12593         }
12594     }
12595
12596     /* don't rely on existence of Event tag since if game was
12597      * pasted from clipboard the Event tag may not exist
12598      */
12599     if (numPGNTags > 0){
12600         char *tags;
12601         if (gameInfo.variant == VariantNormal) {
12602           VariantClass v = StringToVariant(gameInfo.event);
12603           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12604           if(v < VariantShogi) gameInfo.variant = v;
12605         }
12606         if (!matchMode) {
12607           if( appData.autoDisplayTags ) {
12608             tags = PGNTags(&gameInfo);
12609             TagsPopUp(tags, CmailMsg());
12610             free(tags);
12611           }
12612         }
12613     } else {
12614         /* Make something up, but don't display it now */
12615         SetGameInfo();
12616         TagsPopDown();
12617     }
12618
12619     if (cm == PositionDiagram) {
12620         int i, j;
12621         char *p;
12622         Board initial_position;
12623
12624         if (appData.debugMode)
12625           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12626
12627         if (!startedFromSetupPosition) {
12628             p = yy_text;
12629             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12630               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12631                 switch (*p) {
12632                   case '{':
12633                   case '[':
12634                   case '-':
12635                   case ' ':
12636                   case '\t':
12637                   case '\n':
12638                   case '\r':
12639                     break;
12640                   default:
12641                     initial_position[i][j++] = CharToPiece(*p);
12642                     break;
12643                 }
12644             while (*p == ' ' || *p == '\t' ||
12645                    *p == '\n' || *p == '\r') p++;
12646
12647             if (strncmp(p, "black", strlen("black"))==0)
12648               blackPlaysFirst = TRUE;
12649             else
12650               blackPlaysFirst = FALSE;
12651             startedFromSetupPosition = TRUE;
12652
12653             CopyBoard(boards[0], initial_position);
12654             if (blackPlaysFirst) {
12655                 currentMove = forwardMostMove = backwardMostMove = 1;
12656                 CopyBoard(boards[1], initial_position);
12657                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12658                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12659                 timeRemaining[0][1] = whiteTimeRemaining;
12660                 timeRemaining[1][1] = blackTimeRemaining;
12661                 if (commentList[0] != NULL) {
12662                     commentList[1] = commentList[0];
12663                     commentList[0] = NULL;
12664                 }
12665             } else {
12666                 currentMove = forwardMostMove = backwardMostMove = 0;
12667             }
12668         }
12669         yyboardindex = forwardMostMove;
12670         cm = (ChessMove) Myylex();
12671     }
12672
12673   if(!creatingBook) {
12674     if (first.pr == NoProc) {
12675         StartChessProgram(&first);
12676     }
12677     InitChessProgram(&first, FALSE);
12678     SendToProgram("force\n", &first);
12679     if (startedFromSetupPosition) {
12680         SendBoard(&first, forwardMostMove);
12681     if (appData.debugMode) {
12682         fprintf(debugFP, "Load Game\n");
12683     }
12684         DisplayBothClocks();
12685     }
12686   }
12687
12688     /* [HGM] server: flag to write setup moves in broadcast file as one */
12689     loadFlag = appData.suppressLoadMoves;
12690
12691     while (cm == Comment) {
12692         char *p;
12693         if (appData.debugMode)
12694           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12695         p = yy_text;
12696         AppendComment(currentMove, p, FALSE);
12697         yyboardindex = forwardMostMove;
12698         cm = (ChessMove) Myylex();
12699     }
12700
12701     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12702         cm == WhiteWins || cm == BlackWins ||
12703         cm == GameIsDrawn || cm == GameUnfinished) {
12704         DisplayMessage("", _("No moves in game"));
12705         if (cmailMsgLoaded) {
12706             if (appData.debugMode)
12707               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12708             ClearHighlights();
12709             flipView = FALSE;
12710         }
12711         DrawPosition(FALSE, boards[currentMove]);
12712         DisplayBothClocks();
12713         gameMode = EditGame;
12714         ModeHighlight();
12715         gameFileFP = NULL;
12716         cmailOldMove = 0;
12717         return TRUE;
12718     }
12719
12720     // [HGM] PV info: routine tests if comment empty
12721     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12722         DisplayComment(currentMove - 1, commentList[currentMove]);
12723     }
12724     if (!matchMode && appData.timeDelay != 0)
12725       DrawPosition(FALSE, boards[currentMove]);
12726
12727     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12728       programStats.ok_to_send = 1;
12729     }
12730
12731     /* if the first token after the PGN tags is a move
12732      * and not move number 1, retrieve it from the parser
12733      */
12734     if (cm != MoveNumberOne)
12735         LoadGameOneMove(cm);
12736
12737     /* load the remaining moves from the file */
12738     while (LoadGameOneMove(EndOfFile)) {
12739       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12740       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12741     }
12742
12743     /* rewind to the start of the game */
12744     currentMove = backwardMostMove;
12745
12746     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12747
12748     if (oldGameMode == AnalyzeFile) {
12749       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12750       AnalyzeFileEvent();
12751     } else
12752     if (oldGameMode == AnalyzeMode) {
12753       AnalyzeFileEvent();
12754     }
12755
12756     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12757         long int w, b; // [HGM] adjourn: restore saved clock times
12758         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12759         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12760             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12761             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12762         }
12763     }
12764
12765     if(creatingBook) return TRUE;
12766     if (!matchMode && pos > 0) {
12767         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12768     } else
12769     if (matchMode || appData.timeDelay == 0) {
12770       ToEndEvent();
12771     } else if (appData.timeDelay > 0) {
12772       AutoPlayGameLoop();
12773     }
12774
12775     if (appData.debugMode)
12776         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12777
12778     loadFlag = 0; /* [HGM] true game starts */
12779     return TRUE;
12780 }
12781
12782 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12783 int
12784 ReloadPosition (int offset)
12785 {
12786     int positionNumber = lastLoadPositionNumber + offset;
12787     if (lastLoadPositionFP == NULL) {
12788         DisplayError(_("No position has been loaded yet"), 0);
12789         return FALSE;
12790     }
12791     if (positionNumber <= 0) {
12792         DisplayError(_("Can't back up any further"), 0);
12793         return FALSE;
12794     }
12795     return LoadPosition(lastLoadPositionFP, positionNumber,
12796                         lastLoadPositionTitle);
12797 }
12798
12799 /* Load the nth position from the given file */
12800 int
12801 LoadPositionFromFile (char *filename, int n, char *title)
12802 {
12803     FILE *f;
12804     char buf[MSG_SIZ];
12805
12806     if (strcmp(filename, "-") == 0) {
12807         return LoadPosition(stdin, n, "stdin");
12808     } else {
12809         f = fopen(filename, "rb");
12810         if (f == NULL) {
12811             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12812             DisplayError(buf, errno);
12813             return FALSE;
12814         } else {
12815             return LoadPosition(f, n, title);
12816         }
12817     }
12818 }
12819
12820 /* Load the nth position from the given open file, and close it */
12821 int
12822 LoadPosition (FILE *f, int positionNumber, char *title)
12823 {
12824     char *p, line[MSG_SIZ];
12825     Board initial_position;
12826     int i, j, fenMode, pn;
12827
12828     if (gameMode == Training )
12829         SetTrainingModeOff();
12830
12831     if (gameMode != BeginningOfGame) {
12832         Reset(FALSE, TRUE);
12833     }
12834     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12835         fclose(lastLoadPositionFP);
12836     }
12837     if (positionNumber == 0) positionNumber = 1;
12838     lastLoadPositionFP = f;
12839     lastLoadPositionNumber = positionNumber;
12840     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12841     if (first.pr == NoProc && !appData.noChessProgram) {
12842       StartChessProgram(&first);
12843       InitChessProgram(&first, FALSE);
12844     }
12845     pn = positionNumber;
12846     if (positionNumber < 0) {
12847         /* Negative position number means to seek to that byte offset */
12848         if (fseek(f, -positionNumber, 0) == -1) {
12849             DisplayError(_("Can't seek on position file"), 0);
12850             return FALSE;
12851         };
12852         pn = 1;
12853     } else {
12854         if (fseek(f, 0, 0) == -1) {
12855             if (f == lastLoadPositionFP ?
12856                 positionNumber == lastLoadPositionNumber + 1 :
12857                 positionNumber == 1) {
12858                 pn = 1;
12859             } else {
12860                 DisplayError(_("Can't seek on position file"), 0);
12861                 return FALSE;
12862             }
12863         }
12864     }
12865     /* See if this file is FEN or old-style xboard */
12866     if (fgets(line, MSG_SIZ, f) == NULL) {
12867         DisplayError(_("Position not found in file"), 0);
12868         return FALSE;
12869     }
12870     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12871     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12872
12873     if (pn >= 2) {
12874         if (fenMode || line[0] == '#') pn--;
12875         while (pn > 0) {
12876             /* skip positions before number pn */
12877             if (fgets(line, MSG_SIZ, f) == NULL) {
12878                 Reset(TRUE, TRUE);
12879                 DisplayError(_("Position not found in file"), 0);
12880                 return FALSE;
12881             }
12882             if (fenMode || line[0] == '#') pn--;
12883         }
12884     }
12885
12886     if (fenMode) {
12887         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
12888             DisplayError(_("Bad FEN position in file"), 0);
12889             return FALSE;
12890         }
12891     } else {
12892         (void) fgets(line, MSG_SIZ, f);
12893         (void) fgets(line, MSG_SIZ, f);
12894
12895         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12896             (void) fgets(line, MSG_SIZ, f);
12897             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12898                 if (*p == ' ')
12899                   continue;
12900                 initial_position[i][j++] = CharToPiece(*p);
12901             }
12902         }
12903
12904         blackPlaysFirst = FALSE;
12905         if (!feof(f)) {
12906             (void) fgets(line, MSG_SIZ, f);
12907             if (strncmp(line, "black", strlen("black"))==0)
12908               blackPlaysFirst = TRUE;
12909         }
12910     }
12911     startedFromSetupPosition = TRUE;
12912
12913     CopyBoard(boards[0], initial_position);
12914     if (blackPlaysFirst) {
12915         currentMove = forwardMostMove = backwardMostMove = 1;
12916         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12917         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12918         CopyBoard(boards[1], initial_position);
12919         DisplayMessage("", _("Black to play"));
12920     } else {
12921         currentMove = forwardMostMove = backwardMostMove = 0;
12922         DisplayMessage("", _("White to play"));
12923     }
12924     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12925     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12926         SendToProgram("force\n", &first);
12927         SendBoard(&first, forwardMostMove);
12928     }
12929     if (appData.debugMode) {
12930 int i, j;
12931   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12932   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12933         fprintf(debugFP, "Load Position\n");
12934     }
12935
12936     if (positionNumber > 1) {
12937       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12938         DisplayTitle(line);
12939     } else {
12940         DisplayTitle(title);
12941     }
12942     gameMode = EditGame;
12943     ModeHighlight();
12944     ResetClocks();
12945     timeRemaining[0][1] = whiteTimeRemaining;
12946     timeRemaining[1][1] = blackTimeRemaining;
12947     DrawPosition(FALSE, boards[currentMove]);
12948
12949     return TRUE;
12950 }
12951
12952
12953 void
12954 CopyPlayerNameIntoFileName (char **dest, char *src)
12955 {
12956     while (*src != NULLCHAR && *src != ',') {
12957         if (*src == ' ') {
12958             *(*dest)++ = '_';
12959             src++;
12960         } else {
12961             *(*dest)++ = *src++;
12962         }
12963     }
12964 }
12965
12966 char *
12967 DefaultFileName (char *ext)
12968 {
12969     static char def[MSG_SIZ];
12970     char *p;
12971
12972     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12973         p = def;
12974         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12975         *p++ = '-';
12976         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12977         *p++ = '.';
12978         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12979     } else {
12980         def[0] = NULLCHAR;
12981     }
12982     return def;
12983 }
12984
12985 /* Save the current game to the given file */
12986 int
12987 SaveGameToFile (char *filename, int append)
12988 {
12989     FILE *f;
12990     char buf[MSG_SIZ];
12991     int result, i, t,tot=0;
12992
12993     if (strcmp(filename, "-") == 0) {
12994         return SaveGame(stdout, 0, NULL);
12995     } else {
12996         for(i=0; i<10; i++) { // upto 10 tries
12997              f = fopen(filename, append ? "a" : "w");
12998              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12999              if(f || errno != 13) break;
13000              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13001              tot += t;
13002         }
13003         if (f == NULL) {
13004             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13005             DisplayError(buf, errno);
13006             return FALSE;
13007         } else {
13008             safeStrCpy(buf, lastMsg, MSG_SIZ);
13009             DisplayMessage(_("Waiting for access to save file"), "");
13010             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13011             DisplayMessage(_("Saving game"), "");
13012             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13013             result = SaveGame(f, 0, NULL);
13014             DisplayMessage(buf, "");
13015             return result;
13016         }
13017     }
13018 }
13019
13020 char *
13021 SavePart (char *str)
13022 {
13023     static char buf[MSG_SIZ];
13024     char *p;
13025
13026     p = strchr(str, ' ');
13027     if (p == NULL) return str;
13028     strncpy(buf, str, p - str);
13029     buf[p - str] = NULLCHAR;
13030     return buf;
13031 }
13032
13033 #define PGN_MAX_LINE 75
13034
13035 #define PGN_SIDE_WHITE  0
13036 #define PGN_SIDE_BLACK  1
13037
13038 static int
13039 FindFirstMoveOutOfBook (int side)
13040 {
13041     int result = -1;
13042
13043     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13044         int index = backwardMostMove;
13045         int has_book_hit = 0;
13046
13047         if( (index % 2) != side ) {
13048             index++;
13049         }
13050
13051         while( index < forwardMostMove ) {
13052             /* Check to see if engine is in book */
13053             int depth = pvInfoList[index].depth;
13054             int score = pvInfoList[index].score;
13055             int in_book = 0;
13056
13057             if( depth <= 2 ) {
13058                 in_book = 1;
13059             }
13060             else if( score == 0 && depth == 63 ) {
13061                 in_book = 1; /* Zappa */
13062             }
13063             else if( score == 2 && depth == 99 ) {
13064                 in_book = 1; /* Abrok */
13065             }
13066
13067             has_book_hit += in_book;
13068
13069             if( ! in_book ) {
13070                 result = index;
13071
13072                 break;
13073             }
13074
13075             index += 2;
13076         }
13077     }
13078
13079     return result;
13080 }
13081
13082 void
13083 GetOutOfBookInfo (char * buf)
13084 {
13085     int oob[2];
13086     int i;
13087     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13088
13089     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13090     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13091
13092     *buf = '\0';
13093
13094     if( oob[0] >= 0 || oob[1] >= 0 ) {
13095         for( i=0; i<2; i++ ) {
13096             int idx = oob[i];
13097
13098             if( idx >= 0 ) {
13099                 if( i > 0 && oob[0] >= 0 ) {
13100                     strcat( buf, "   " );
13101                 }
13102
13103                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13104                 sprintf( buf+strlen(buf), "%s%.2f",
13105                     pvInfoList[idx].score >= 0 ? "+" : "",
13106                     pvInfoList[idx].score / 100.0 );
13107             }
13108         }
13109     }
13110 }
13111
13112 /* Save game in PGN style and close the file */
13113 int
13114 SaveGamePGN (FILE *f)
13115 {
13116     int i, offset, linelen, newblock;
13117 //    char *movetext;
13118     char numtext[32];
13119     int movelen, numlen, blank;
13120     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13121
13122     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13123
13124     PrintPGNTags(f, &gameInfo);
13125
13126     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13127
13128     if (backwardMostMove > 0 || startedFromSetupPosition) {
13129         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13130         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13131         fprintf(f, "\n{--------------\n");
13132         PrintPosition(f, backwardMostMove);
13133         fprintf(f, "--------------}\n");
13134         free(fen);
13135     }
13136     else {
13137         /* [AS] Out of book annotation */
13138         if( appData.saveOutOfBookInfo ) {
13139             char buf[64];
13140
13141             GetOutOfBookInfo( buf );
13142
13143             if( buf[0] != '\0' ) {
13144                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13145             }
13146         }
13147
13148         fprintf(f, "\n");
13149     }
13150
13151     i = backwardMostMove;
13152     linelen = 0;
13153     newblock = TRUE;
13154
13155     while (i < forwardMostMove) {
13156         /* Print comments preceding this move */
13157         if (commentList[i] != NULL) {
13158             if (linelen > 0) fprintf(f, "\n");
13159             fprintf(f, "%s", commentList[i]);
13160             linelen = 0;
13161             newblock = TRUE;
13162         }
13163
13164         /* Format move number */
13165         if ((i % 2) == 0)
13166           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13167         else
13168           if (newblock)
13169             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13170           else
13171             numtext[0] = NULLCHAR;
13172
13173         numlen = strlen(numtext);
13174         newblock = FALSE;
13175
13176         /* Print move number */
13177         blank = linelen > 0 && numlen > 0;
13178         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13179             fprintf(f, "\n");
13180             linelen = 0;
13181             blank = 0;
13182         }
13183         if (blank) {
13184             fprintf(f, " ");
13185             linelen++;
13186         }
13187         fprintf(f, "%s", numtext);
13188         linelen += numlen;
13189
13190         /* Get move */
13191         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13192         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13193
13194         /* Print move */
13195         blank = linelen > 0 && movelen > 0;
13196         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13197             fprintf(f, "\n");
13198             linelen = 0;
13199             blank = 0;
13200         }
13201         if (blank) {
13202             fprintf(f, " ");
13203             linelen++;
13204         }
13205         fprintf(f, "%s", move_buffer);
13206         linelen += movelen;
13207
13208         /* [AS] Add PV info if present */
13209         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13210             /* [HGM] add time */
13211             char buf[MSG_SIZ]; int seconds;
13212
13213             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13214
13215             if( seconds <= 0)
13216               buf[0] = 0;
13217             else
13218               if( seconds < 30 )
13219                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13220               else
13221                 {
13222                   seconds = (seconds + 4)/10; // round to full seconds
13223                   if( seconds < 60 )
13224                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13225                   else
13226                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13227                 }
13228
13229             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13230                       pvInfoList[i].score >= 0 ? "+" : "",
13231                       pvInfoList[i].score / 100.0,
13232                       pvInfoList[i].depth,
13233                       buf );
13234
13235             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13236
13237             /* Print score/depth */
13238             blank = linelen > 0 && movelen > 0;
13239             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13240                 fprintf(f, "\n");
13241                 linelen = 0;
13242                 blank = 0;
13243             }
13244             if (blank) {
13245                 fprintf(f, " ");
13246                 linelen++;
13247             }
13248             fprintf(f, "%s", move_buffer);
13249             linelen += movelen;
13250         }
13251
13252         i++;
13253     }
13254
13255     /* Start a new line */
13256     if (linelen > 0) fprintf(f, "\n");
13257
13258     /* Print comments after last move */
13259     if (commentList[i] != NULL) {
13260         fprintf(f, "%s\n", commentList[i]);
13261     }
13262
13263     /* Print result */
13264     if (gameInfo.resultDetails != NULL &&
13265         gameInfo.resultDetails[0] != NULLCHAR) {
13266         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13267         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13268            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13269             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13270         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13271     } else {
13272         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13273     }
13274
13275     fclose(f);
13276     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13277     return TRUE;
13278 }
13279
13280 /* Save game in old style and close the file */
13281 int
13282 SaveGameOldStyle (FILE *f)
13283 {
13284     int i, offset;
13285     time_t tm;
13286
13287     tm = time((time_t *) NULL);
13288
13289     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13290     PrintOpponents(f);
13291
13292     if (backwardMostMove > 0 || startedFromSetupPosition) {
13293         fprintf(f, "\n[--------------\n");
13294         PrintPosition(f, backwardMostMove);
13295         fprintf(f, "--------------]\n");
13296     } else {
13297         fprintf(f, "\n");
13298     }
13299
13300     i = backwardMostMove;
13301     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13302
13303     while (i < forwardMostMove) {
13304         if (commentList[i] != NULL) {
13305             fprintf(f, "[%s]\n", commentList[i]);
13306         }
13307
13308         if ((i % 2) == 1) {
13309             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13310             i++;
13311         } else {
13312             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13313             i++;
13314             if (commentList[i] != NULL) {
13315                 fprintf(f, "\n");
13316                 continue;
13317             }
13318             if (i >= forwardMostMove) {
13319                 fprintf(f, "\n");
13320                 break;
13321             }
13322             fprintf(f, "%s\n", parseList[i]);
13323             i++;
13324         }
13325     }
13326
13327     if (commentList[i] != NULL) {
13328         fprintf(f, "[%s]\n", commentList[i]);
13329     }
13330
13331     /* This isn't really the old style, but it's close enough */
13332     if (gameInfo.resultDetails != NULL &&
13333         gameInfo.resultDetails[0] != NULLCHAR) {
13334         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13335                 gameInfo.resultDetails);
13336     } else {
13337         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13338     }
13339
13340     fclose(f);
13341     return TRUE;
13342 }
13343
13344 /* Save the current game to open file f and close the file */
13345 int
13346 SaveGame (FILE *f, int dummy, char *dummy2)
13347 {
13348     if (gameMode == EditPosition) EditPositionDone(TRUE);
13349     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13350     if (appData.oldSaveStyle)
13351       return SaveGameOldStyle(f);
13352     else
13353       return SaveGamePGN(f);
13354 }
13355
13356 /* Save the current position to the given file */
13357 int
13358 SavePositionToFile (char *filename)
13359 {
13360     FILE *f;
13361     char buf[MSG_SIZ];
13362
13363     if (strcmp(filename, "-") == 0) {
13364         return SavePosition(stdout, 0, NULL);
13365     } else {
13366         f = fopen(filename, "a");
13367         if (f == NULL) {
13368             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13369             DisplayError(buf, errno);
13370             return FALSE;
13371         } else {
13372             safeStrCpy(buf, lastMsg, MSG_SIZ);
13373             DisplayMessage(_("Waiting for access to save file"), "");
13374             flock(fileno(f), LOCK_EX); // [HGM] lock
13375             DisplayMessage(_("Saving position"), "");
13376             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13377             SavePosition(f, 0, NULL);
13378             DisplayMessage(buf, "");
13379             return TRUE;
13380         }
13381     }
13382 }
13383
13384 /* Save the current position to the given open file and close the file */
13385 int
13386 SavePosition (FILE *f, int dummy, char *dummy2)
13387 {
13388     time_t tm;
13389     char *fen;
13390
13391     if (gameMode == EditPosition) EditPositionDone(TRUE);
13392     if (appData.oldSaveStyle) {
13393         tm = time((time_t *) NULL);
13394
13395         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13396         PrintOpponents(f);
13397         fprintf(f, "[--------------\n");
13398         PrintPosition(f, currentMove);
13399         fprintf(f, "--------------]\n");
13400     } else {
13401         fen = PositionToFEN(currentMove, NULL, 1);
13402         fprintf(f, "%s\n", fen);
13403         free(fen);
13404     }
13405     fclose(f);
13406     return TRUE;
13407 }
13408
13409 void
13410 ReloadCmailMsgEvent (int unregister)
13411 {
13412 #if !WIN32
13413     static char *inFilename = NULL;
13414     static char *outFilename;
13415     int i;
13416     struct stat inbuf, outbuf;
13417     int status;
13418
13419     /* Any registered moves are unregistered if unregister is set, */
13420     /* i.e. invoked by the signal handler */
13421     if (unregister) {
13422         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13423             cmailMoveRegistered[i] = FALSE;
13424             if (cmailCommentList[i] != NULL) {
13425                 free(cmailCommentList[i]);
13426                 cmailCommentList[i] = NULL;
13427             }
13428         }
13429         nCmailMovesRegistered = 0;
13430     }
13431
13432     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13433         cmailResult[i] = CMAIL_NOT_RESULT;
13434     }
13435     nCmailResults = 0;
13436
13437     if (inFilename == NULL) {
13438         /* Because the filenames are static they only get malloced once  */
13439         /* and they never get freed                                      */
13440         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13441         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13442
13443         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13444         sprintf(outFilename, "%s.out", appData.cmailGameName);
13445     }
13446
13447     status = stat(outFilename, &outbuf);
13448     if (status < 0) {
13449         cmailMailedMove = FALSE;
13450     } else {
13451         status = stat(inFilename, &inbuf);
13452         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13453     }
13454
13455     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13456        counts the games, notes how each one terminated, etc.
13457
13458        It would be nice to remove this kludge and instead gather all
13459        the information while building the game list.  (And to keep it
13460        in the game list nodes instead of having a bunch of fixed-size
13461        parallel arrays.)  Note this will require getting each game's
13462        termination from the PGN tags, as the game list builder does
13463        not process the game moves.  --mann
13464        */
13465     cmailMsgLoaded = TRUE;
13466     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13467
13468     /* Load first game in the file or popup game menu */
13469     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13470
13471 #endif /* !WIN32 */
13472     return;
13473 }
13474
13475 int
13476 RegisterMove ()
13477 {
13478     FILE *f;
13479     char string[MSG_SIZ];
13480
13481     if (   cmailMailedMove
13482         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13483         return TRUE;            /* Allow free viewing  */
13484     }
13485
13486     /* Unregister move to ensure that we don't leave RegisterMove        */
13487     /* with the move registered when the conditions for registering no   */
13488     /* longer hold                                                       */
13489     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13490         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13491         nCmailMovesRegistered --;
13492
13493         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13494           {
13495               free(cmailCommentList[lastLoadGameNumber - 1]);
13496               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13497           }
13498     }
13499
13500     if (cmailOldMove == -1) {
13501         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13502         return FALSE;
13503     }
13504
13505     if (currentMove > cmailOldMove + 1) {
13506         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13507         return FALSE;
13508     }
13509
13510     if (currentMove < cmailOldMove) {
13511         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13512         return FALSE;
13513     }
13514
13515     if (forwardMostMove > currentMove) {
13516         /* Silently truncate extra moves */
13517         TruncateGame();
13518     }
13519
13520     if (   (currentMove == cmailOldMove + 1)
13521         || (   (currentMove == cmailOldMove)
13522             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13523                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13524         if (gameInfo.result != GameUnfinished) {
13525             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13526         }
13527
13528         if (commentList[currentMove] != NULL) {
13529             cmailCommentList[lastLoadGameNumber - 1]
13530               = StrSave(commentList[currentMove]);
13531         }
13532         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13533
13534         if (appData.debugMode)
13535           fprintf(debugFP, "Saving %s for game %d\n",
13536                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13537
13538         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13539
13540         f = fopen(string, "w");
13541         if (appData.oldSaveStyle) {
13542             SaveGameOldStyle(f); /* also closes the file */
13543
13544             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13545             f = fopen(string, "w");
13546             SavePosition(f, 0, NULL); /* also closes the file */
13547         } else {
13548             fprintf(f, "{--------------\n");
13549             PrintPosition(f, currentMove);
13550             fprintf(f, "--------------}\n\n");
13551
13552             SaveGame(f, 0, NULL); /* also closes the file*/
13553         }
13554
13555         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13556         nCmailMovesRegistered ++;
13557     } else if (nCmailGames == 1) {
13558         DisplayError(_("You have not made a move yet"), 0);
13559         return FALSE;
13560     }
13561
13562     return TRUE;
13563 }
13564
13565 void
13566 MailMoveEvent ()
13567 {
13568 #if !WIN32
13569     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13570     FILE *commandOutput;
13571     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13572     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13573     int nBuffers;
13574     int i;
13575     int archived;
13576     char *arcDir;
13577
13578     if (! cmailMsgLoaded) {
13579         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13580         return;
13581     }
13582
13583     if (nCmailGames == nCmailResults) {
13584         DisplayError(_("No unfinished games"), 0);
13585         return;
13586     }
13587
13588 #if CMAIL_PROHIBIT_REMAIL
13589     if (cmailMailedMove) {
13590       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);
13591         DisplayError(msg, 0);
13592         return;
13593     }
13594 #endif
13595
13596     if (! (cmailMailedMove || RegisterMove())) return;
13597
13598     if (   cmailMailedMove
13599         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13600       snprintf(string, MSG_SIZ, partCommandString,
13601                appData.debugMode ? " -v" : "", appData.cmailGameName);
13602         commandOutput = popen(string, "r");
13603
13604         if (commandOutput == NULL) {
13605             DisplayError(_("Failed to invoke cmail"), 0);
13606         } else {
13607             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13608                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13609             }
13610             if (nBuffers > 1) {
13611                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13612                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13613                 nBytes = MSG_SIZ - 1;
13614             } else {
13615                 (void) memcpy(msg, buffer, nBytes);
13616             }
13617             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13618
13619             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13620                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13621
13622                 archived = TRUE;
13623                 for (i = 0; i < nCmailGames; i ++) {
13624                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13625                         archived = FALSE;
13626                     }
13627                 }
13628                 if (   archived
13629                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13630                         != NULL)) {
13631                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13632                            arcDir,
13633                            appData.cmailGameName,
13634                            gameInfo.date);
13635                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13636                     cmailMsgLoaded = FALSE;
13637                 }
13638             }
13639
13640             DisplayInformation(msg);
13641             pclose(commandOutput);
13642         }
13643     } else {
13644         if ((*cmailMsg) != '\0') {
13645             DisplayInformation(cmailMsg);
13646         }
13647     }
13648
13649     return;
13650 #endif /* !WIN32 */
13651 }
13652
13653 char *
13654 CmailMsg ()
13655 {
13656 #if WIN32
13657     return NULL;
13658 #else
13659     int  prependComma = 0;
13660     char number[5];
13661     char string[MSG_SIZ];       /* Space for game-list */
13662     int  i;
13663
13664     if (!cmailMsgLoaded) return "";
13665
13666     if (cmailMailedMove) {
13667       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13668     } else {
13669         /* Create a list of games left */
13670       snprintf(string, MSG_SIZ, "[");
13671         for (i = 0; i < nCmailGames; i ++) {
13672             if (! (   cmailMoveRegistered[i]
13673                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13674                 if (prependComma) {
13675                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13676                 } else {
13677                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13678                     prependComma = 1;
13679                 }
13680
13681                 strcat(string, number);
13682             }
13683         }
13684         strcat(string, "]");
13685
13686         if (nCmailMovesRegistered + nCmailResults == 0) {
13687             switch (nCmailGames) {
13688               case 1:
13689                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13690                 break;
13691
13692               case 2:
13693                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13694                 break;
13695
13696               default:
13697                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13698                          nCmailGames);
13699                 break;
13700             }
13701         } else {
13702             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13703               case 1:
13704                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13705                          string);
13706                 break;
13707
13708               case 0:
13709                 if (nCmailResults == nCmailGames) {
13710                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13711                 } else {
13712                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13713                 }
13714                 break;
13715
13716               default:
13717                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13718                          string);
13719             }
13720         }
13721     }
13722     return cmailMsg;
13723 #endif /* WIN32 */
13724 }
13725
13726 void
13727 ResetGameEvent ()
13728 {
13729     if (gameMode == Training)
13730       SetTrainingModeOff();
13731
13732     Reset(TRUE, TRUE);
13733     cmailMsgLoaded = FALSE;
13734     if (appData.icsActive) {
13735       SendToICS(ics_prefix);
13736       SendToICS("refresh\n");
13737     }
13738 }
13739
13740 void
13741 ExitEvent (int status)
13742 {
13743     exiting++;
13744     if (exiting > 2) {
13745       /* Give up on clean exit */
13746       exit(status);
13747     }
13748     if (exiting > 1) {
13749       /* Keep trying for clean exit */
13750       return;
13751     }
13752
13753     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13754
13755     if (telnetISR != NULL) {
13756       RemoveInputSource(telnetISR);
13757     }
13758     if (icsPR != NoProc) {
13759       DestroyChildProcess(icsPR, TRUE);
13760     }
13761
13762     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13763     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13764
13765     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13766     /* make sure this other one finishes before killing it!                  */
13767     if(endingGame) { int count = 0;
13768         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13769         while(endingGame && count++ < 10) DoSleep(1);
13770         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13771     }
13772
13773     /* Kill off chess programs */
13774     if (first.pr != NoProc) {
13775         ExitAnalyzeMode();
13776
13777         DoSleep( appData.delayBeforeQuit );
13778         SendToProgram("quit\n", &first);
13779         DoSleep( appData.delayAfterQuit );
13780         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13781     }
13782     if (second.pr != NoProc) {
13783         DoSleep( appData.delayBeforeQuit );
13784         SendToProgram("quit\n", &second);
13785         DoSleep( appData.delayAfterQuit );
13786         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13787     }
13788     if (first.isr != NULL) {
13789         RemoveInputSource(first.isr);
13790     }
13791     if (second.isr != NULL) {
13792         RemoveInputSource(second.isr);
13793     }
13794
13795     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13796     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13797
13798     ShutDownFrontEnd();
13799     exit(status);
13800 }
13801
13802 void
13803 PauseEngine (ChessProgramState *cps)
13804 {
13805     SendToProgram("pause\n", cps);
13806     cps->pause = 2;
13807 }
13808
13809 void
13810 UnPauseEngine (ChessProgramState *cps)
13811 {
13812     SendToProgram("resume\n", cps);
13813     cps->pause = 1;
13814 }
13815
13816 void
13817 PauseEvent ()
13818 {
13819     if (appData.debugMode)
13820         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13821     if (pausing) {
13822         pausing = FALSE;
13823         ModeHighlight();
13824         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
13825             StartClocks();
13826             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
13827                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
13828                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
13829             }
13830             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
13831             HandleMachineMove(stashedInputMove, stalledEngine);
13832             stalledEngine = NULL;
13833             return;
13834         }
13835         if (gameMode == MachinePlaysWhite ||
13836             gameMode == TwoMachinesPlay   ||
13837             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
13838             if(first.pause)  UnPauseEngine(&first);
13839             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
13840             if(second.pause) UnPauseEngine(&second);
13841             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
13842             StartClocks();
13843         } else {
13844             DisplayBothClocks();
13845         }
13846         if (gameMode == PlayFromGameFile) {
13847             if (appData.timeDelay >= 0)
13848                 AutoPlayGameLoop();
13849         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13850             Reset(FALSE, TRUE);
13851             SendToICS(ics_prefix);
13852             SendToICS("refresh\n");
13853         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
13854             ForwardInner(forwardMostMove);
13855         }
13856         pauseExamInvalid = FALSE;
13857     } else {
13858         switch (gameMode) {
13859           default:
13860             return;
13861           case IcsExamining:
13862             pauseExamForwardMostMove = forwardMostMove;
13863             pauseExamInvalid = FALSE;
13864             /* fall through */
13865           case IcsObserving:
13866           case IcsPlayingWhite:
13867           case IcsPlayingBlack:
13868             pausing = TRUE;
13869             ModeHighlight();
13870             return;
13871           case PlayFromGameFile:
13872             (void) StopLoadGameTimer();
13873             pausing = TRUE;
13874             ModeHighlight();
13875             break;
13876           case BeginningOfGame:
13877             if (appData.icsActive) return;
13878             /* else fall through */
13879           case MachinePlaysWhite:
13880           case MachinePlaysBlack:
13881           case TwoMachinesPlay:
13882             if (forwardMostMove == 0)
13883               return;           /* don't pause if no one has moved */
13884             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
13885                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
13886                 if(onMove->pause) {           // thinking engine can be paused
13887                     PauseEngine(onMove);      // do it
13888                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
13889                         PauseEngine(onMove->other);
13890                     else
13891                         SendToProgram("easy\n", onMove->other);
13892                     StopClocks();
13893                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
13894             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
13895                 if(first.pause) {
13896                     PauseEngine(&first);
13897                     StopClocks();
13898                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
13899             } else { // human on move, pause pondering by either method
13900                 if(first.pause)
13901                     PauseEngine(&first);
13902                 else if(appData.ponderNextMove)
13903                     SendToProgram("easy\n", &first);
13904                 StopClocks();
13905             }
13906             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
13907           case AnalyzeMode:
13908             pausing = TRUE;
13909             ModeHighlight();
13910             break;
13911         }
13912     }
13913 }
13914
13915 void
13916 EditCommentEvent ()
13917 {
13918     char title[MSG_SIZ];
13919
13920     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13921       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13922     } else {
13923       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13924                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13925                parseList[currentMove - 1]);
13926     }
13927
13928     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13929 }
13930
13931
13932 void
13933 EditTagsEvent ()
13934 {
13935     char *tags = PGNTags(&gameInfo);
13936     bookUp = FALSE;
13937     EditTagsPopUp(tags, NULL);
13938     free(tags);
13939 }
13940
13941 void
13942 ToggleSecond ()
13943 {
13944   if(second.analyzing) {
13945     SendToProgram("exit\n", &second);
13946     second.analyzing = FALSE;
13947   } else {
13948     if (second.pr == NoProc) StartChessProgram(&second);
13949     InitChessProgram(&second, FALSE);
13950     FeedMovesToProgram(&second, currentMove);
13951
13952     SendToProgram("analyze\n", &second);
13953     second.analyzing = TRUE;
13954   }
13955 }
13956
13957 /* Toggle ShowThinking */
13958 void
13959 ToggleShowThinking()
13960 {
13961   appData.showThinking = !appData.showThinking;
13962   ShowThinkingEvent();
13963 }
13964
13965 int
13966 AnalyzeModeEvent ()
13967 {
13968     char buf[MSG_SIZ];
13969
13970     if (!first.analysisSupport) {
13971       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
13972       DisplayError(buf, 0);
13973       return 0;
13974     }
13975     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
13976     if (appData.icsActive) {
13977         if (gameMode != IcsObserving) {
13978           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
13979             DisplayError(buf, 0);
13980             /* secure check */
13981             if (appData.icsEngineAnalyze) {
13982                 if (appData.debugMode)
13983                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
13984                 ExitAnalyzeMode();
13985                 ModeHighlight();
13986             }
13987             return 0;
13988         }
13989         /* if enable, user wants to disable icsEngineAnalyze */
13990         if (appData.icsEngineAnalyze) {
13991                 ExitAnalyzeMode();
13992                 ModeHighlight();
13993                 return 0;
13994         }
13995         appData.icsEngineAnalyze = TRUE;
13996         if (appData.debugMode)
13997             fprintf(debugFP, "ICS engine analyze starting... \n");
13998     }
13999
14000     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14001     if (appData.noChessProgram || gameMode == AnalyzeMode)
14002       return 0;
14003
14004     if (gameMode != AnalyzeFile) {
14005         if (!appData.icsEngineAnalyze) {
14006                EditGameEvent();
14007                if (gameMode != EditGame) return 0;
14008         }
14009         if (!appData.showThinking) ToggleShowThinking();
14010         ResurrectChessProgram();
14011         SendToProgram("analyze\n", &first);
14012         first.analyzing = TRUE;
14013         /*first.maybeThinking = TRUE;*/
14014         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14015         EngineOutputPopUp();
14016     }
14017     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14018     pausing = FALSE;
14019     ModeHighlight();
14020     SetGameInfo();
14021
14022     StartAnalysisClock();
14023     GetTimeMark(&lastNodeCountTime);
14024     lastNodeCount = 0;
14025     return 1;
14026 }
14027
14028 void
14029 AnalyzeFileEvent ()
14030 {
14031     if (appData.noChessProgram || gameMode == AnalyzeFile)
14032       return;
14033
14034     if (!first.analysisSupport) {
14035       char buf[MSG_SIZ];
14036       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14037       DisplayError(buf, 0);
14038       return;
14039     }
14040
14041     if (gameMode != AnalyzeMode) {
14042         keepInfo = 1; // mere annotating should not alter PGN tags
14043         EditGameEvent();
14044         keepInfo = 0;
14045         if (gameMode != EditGame) return;
14046         if (!appData.showThinking) ToggleShowThinking();
14047         ResurrectChessProgram();
14048         SendToProgram("analyze\n", &first);
14049         first.analyzing = TRUE;
14050         /*first.maybeThinking = TRUE;*/
14051         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14052         EngineOutputPopUp();
14053     }
14054     gameMode = AnalyzeFile;
14055     pausing = FALSE;
14056     ModeHighlight();
14057
14058     StartAnalysisClock();
14059     GetTimeMark(&lastNodeCountTime);
14060     lastNodeCount = 0;
14061     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14062     AnalysisPeriodicEvent(1);
14063 }
14064
14065 void
14066 MachineWhiteEvent ()
14067 {
14068     char buf[MSG_SIZ];
14069     char *bookHit = NULL;
14070
14071     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14072       return;
14073
14074
14075     if (gameMode == PlayFromGameFile ||
14076         gameMode == TwoMachinesPlay  ||
14077         gameMode == Training         ||
14078         gameMode == AnalyzeMode      ||
14079         gameMode == EndOfGame)
14080         EditGameEvent();
14081
14082     if (gameMode == EditPosition)
14083         EditPositionDone(TRUE);
14084
14085     if (!WhiteOnMove(currentMove)) {
14086         DisplayError(_("It is not White's turn"), 0);
14087         return;
14088     }
14089
14090     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14091       ExitAnalyzeMode();
14092
14093     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14094         gameMode == AnalyzeFile)
14095         TruncateGame();
14096
14097     ResurrectChessProgram();    /* in case it isn't running */
14098     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14099         gameMode = MachinePlaysWhite;
14100         ResetClocks();
14101     } else
14102     gameMode = MachinePlaysWhite;
14103     pausing = FALSE;
14104     ModeHighlight();
14105     SetGameInfo();
14106     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14107     DisplayTitle(buf);
14108     if (first.sendName) {
14109       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14110       SendToProgram(buf, &first);
14111     }
14112     if (first.sendTime) {
14113       if (first.useColors) {
14114         SendToProgram("black\n", &first); /*gnu kludge*/
14115       }
14116       SendTimeRemaining(&first, TRUE);
14117     }
14118     if (first.useColors) {
14119       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14120     }
14121     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14122     SetMachineThinkingEnables();
14123     first.maybeThinking = TRUE;
14124     StartClocks();
14125     firstMove = FALSE;
14126
14127     if (appData.autoFlipView && !flipView) {
14128       flipView = !flipView;
14129       DrawPosition(FALSE, NULL);
14130       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14131     }
14132
14133     if(bookHit) { // [HGM] book: simulate book reply
14134         static char bookMove[MSG_SIZ]; // a bit generous?
14135
14136         programStats.nodes = programStats.depth = programStats.time =
14137         programStats.score = programStats.got_only_move = 0;
14138         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14139
14140         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14141         strcat(bookMove, bookHit);
14142         HandleMachineMove(bookMove, &first);
14143     }
14144 }
14145
14146 void
14147 MachineBlackEvent ()
14148 {
14149   char buf[MSG_SIZ];
14150   char *bookHit = NULL;
14151
14152     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14153         return;
14154
14155
14156     if (gameMode == PlayFromGameFile ||
14157         gameMode == TwoMachinesPlay  ||
14158         gameMode == Training         ||
14159         gameMode == AnalyzeMode      ||
14160         gameMode == EndOfGame)
14161         EditGameEvent();
14162
14163     if (gameMode == EditPosition)
14164         EditPositionDone(TRUE);
14165
14166     if (WhiteOnMove(currentMove)) {
14167         DisplayError(_("It is not Black's turn"), 0);
14168         return;
14169     }
14170
14171     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14172       ExitAnalyzeMode();
14173
14174     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14175         gameMode == AnalyzeFile)
14176         TruncateGame();
14177
14178     ResurrectChessProgram();    /* in case it isn't running */
14179     gameMode = MachinePlaysBlack;
14180     pausing = FALSE;
14181     ModeHighlight();
14182     SetGameInfo();
14183     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14184     DisplayTitle(buf);
14185     if (first.sendName) {
14186       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14187       SendToProgram(buf, &first);
14188     }
14189     if (first.sendTime) {
14190       if (first.useColors) {
14191         SendToProgram("white\n", &first); /*gnu kludge*/
14192       }
14193       SendTimeRemaining(&first, FALSE);
14194     }
14195     if (first.useColors) {
14196       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14197     }
14198     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14199     SetMachineThinkingEnables();
14200     first.maybeThinking = TRUE;
14201     StartClocks();
14202
14203     if (appData.autoFlipView && flipView) {
14204       flipView = !flipView;
14205       DrawPosition(FALSE, NULL);
14206       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14207     }
14208     if(bookHit) { // [HGM] book: simulate book reply
14209         static char bookMove[MSG_SIZ]; // a bit generous?
14210
14211         programStats.nodes = programStats.depth = programStats.time =
14212         programStats.score = programStats.got_only_move = 0;
14213         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14214
14215         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14216         strcat(bookMove, bookHit);
14217         HandleMachineMove(bookMove, &first);
14218     }
14219 }
14220
14221
14222 void
14223 DisplayTwoMachinesTitle ()
14224 {
14225     char buf[MSG_SIZ];
14226     if (appData.matchGames > 0) {
14227         if(appData.tourneyFile[0]) {
14228           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14229                    gameInfo.white, _("vs."), gameInfo.black,
14230                    nextGame+1, appData.matchGames+1,
14231                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14232         } else
14233         if (first.twoMachinesColor[0] == 'w') {
14234           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14235                    gameInfo.white, _("vs."),  gameInfo.black,
14236                    first.matchWins, second.matchWins,
14237                    matchGame - 1 - (first.matchWins + second.matchWins));
14238         } else {
14239           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14240                    gameInfo.white, _("vs."), gameInfo.black,
14241                    second.matchWins, first.matchWins,
14242                    matchGame - 1 - (first.matchWins + second.matchWins));
14243         }
14244     } else {
14245       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14246     }
14247     DisplayTitle(buf);
14248 }
14249
14250 void
14251 SettingsMenuIfReady ()
14252 {
14253   if (second.lastPing != second.lastPong) {
14254     DisplayMessage("", _("Waiting for second chess program"));
14255     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14256     return;
14257   }
14258   ThawUI();
14259   DisplayMessage("", "");
14260   SettingsPopUp(&second);
14261 }
14262
14263 int
14264 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14265 {
14266     char buf[MSG_SIZ];
14267     if (cps->pr == NoProc) {
14268         StartChessProgram(cps);
14269         if (cps->protocolVersion == 1) {
14270           retry();
14271           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14272         } else {
14273           /* kludge: allow timeout for initial "feature" command */
14274           if(retry != TwoMachinesEventIfReady) FreezeUI();
14275           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14276           DisplayMessage("", buf);
14277           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14278         }
14279         return 1;
14280     }
14281     return 0;
14282 }
14283
14284 void
14285 TwoMachinesEvent P((void))
14286 {
14287     int i;
14288     char buf[MSG_SIZ];
14289     ChessProgramState *onmove;
14290     char *bookHit = NULL;
14291     static int stalling = 0;
14292     TimeMark now;
14293     long wait;
14294
14295     if (appData.noChessProgram) return;
14296
14297     switch (gameMode) {
14298       case TwoMachinesPlay:
14299         return;
14300       case MachinePlaysWhite:
14301       case MachinePlaysBlack:
14302         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14303             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14304             return;
14305         }
14306         /* fall through */
14307       case BeginningOfGame:
14308       case PlayFromGameFile:
14309       case EndOfGame:
14310         EditGameEvent();
14311         if (gameMode != EditGame) return;
14312         break;
14313       case EditPosition:
14314         EditPositionDone(TRUE);
14315         break;
14316       case AnalyzeMode:
14317       case AnalyzeFile:
14318         ExitAnalyzeMode();
14319         break;
14320       case EditGame:
14321       default:
14322         break;
14323     }
14324
14325 //    forwardMostMove = currentMove;
14326     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14327     startingEngine = TRUE;
14328
14329     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14330
14331     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14332     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14333       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14334       return;
14335     }
14336     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14337
14338     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
14339         startingEngine = FALSE;
14340         DisplayError("second engine does not play this", 0);
14341         return;
14342     }
14343
14344     if(!stalling) {
14345       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14346       SendToProgram("force\n", &second);
14347       stalling = 1;
14348       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14349       return;
14350     }
14351     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14352     if(appData.matchPause>10000 || appData.matchPause<10)
14353                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14354     wait = SubtractTimeMarks(&now, &pauseStart);
14355     if(wait < appData.matchPause) {
14356         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14357         return;
14358     }
14359     // we are now committed to starting the game
14360     stalling = 0;
14361     DisplayMessage("", "");
14362     if (startedFromSetupPosition) {
14363         SendBoard(&second, backwardMostMove);
14364     if (appData.debugMode) {
14365         fprintf(debugFP, "Two Machines\n");
14366     }
14367     }
14368     for (i = backwardMostMove; i < forwardMostMove; i++) {
14369         SendMoveToProgram(i, &second);
14370     }
14371
14372     gameMode = TwoMachinesPlay;
14373     pausing = startingEngine = FALSE;
14374     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14375     SetGameInfo();
14376     DisplayTwoMachinesTitle();
14377     firstMove = TRUE;
14378     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14379         onmove = &first;
14380     } else {
14381         onmove = &second;
14382     }
14383     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14384     SendToProgram(first.computerString, &first);
14385     if (first.sendName) {
14386       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14387       SendToProgram(buf, &first);
14388     }
14389     SendToProgram(second.computerString, &second);
14390     if (second.sendName) {
14391       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14392       SendToProgram(buf, &second);
14393     }
14394
14395     ResetClocks();
14396     if (!first.sendTime || !second.sendTime) {
14397         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14398         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14399     }
14400     if (onmove->sendTime) {
14401       if (onmove->useColors) {
14402         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14403       }
14404       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14405     }
14406     if (onmove->useColors) {
14407       SendToProgram(onmove->twoMachinesColor, onmove);
14408     }
14409     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14410 //    SendToProgram("go\n", onmove);
14411     onmove->maybeThinking = TRUE;
14412     SetMachineThinkingEnables();
14413
14414     StartClocks();
14415
14416     if(bookHit) { // [HGM] book: simulate book reply
14417         static char bookMove[MSG_SIZ]; // a bit generous?
14418
14419         programStats.nodes = programStats.depth = programStats.time =
14420         programStats.score = programStats.got_only_move = 0;
14421         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14422
14423         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14424         strcat(bookMove, bookHit);
14425         savedMessage = bookMove; // args for deferred call
14426         savedState = onmove;
14427         ScheduleDelayedEvent(DeferredBookMove, 1);
14428     }
14429 }
14430
14431 void
14432 TrainingEvent ()
14433 {
14434     if (gameMode == Training) {
14435       SetTrainingModeOff();
14436       gameMode = PlayFromGameFile;
14437       DisplayMessage("", _("Training mode off"));
14438     } else {
14439       gameMode = Training;
14440       animateTraining = appData.animate;
14441
14442       /* make sure we are not already at the end of the game */
14443       if (currentMove < forwardMostMove) {
14444         SetTrainingModeOn();
14445         DisplayMessage("", _("Training mode on"));
14446       } else {
14447         gameMode = PlayFromGameFile;
14448         DisplayError(_("Already at end of game"), 0);
14449       }
14450     }
14451     ModeHighlight();
14452 }
14453
14454 void
14455 IcsClientEvent ()
14456 {
14457     if (!appData.icsActive) return;
14458     switch (gameMode) {
14459       case IcsPlayingWhite:
14460       case IcsPlayingBlack:
14461       case IcsObserving:
14462       case IcsIdle:
14463       case BeginningOfGame:
14464       case IcsExamining:
14465         return;
14466
14467       case EditGame:
14468         break;
14469
14470       case EditPosition:
14471         EditPositionDone(TRUE);
14472         break;
14473
14474       case AnalyzeMode:
14475       case AnalyzeFile:
14476         ExitAnalyzeMode();
14477         break;
14478
14479       default:
14480         EditGameEvent();
14481         break;
14482     }
14483
14484     gameMode = IcsIdle;
14485     ModeHighlight();
14486     return;
14487 }
14488
14489 void
14490 EditGameEvent ()
14491 {
14492     int i;
14493
14494     switch (gameMode) {
14495       case Training:
14496         SetTrainingModeOff();
14497         break;
14498       case MachinePlaysWhite:
14499       case MachinePlaysBlack:
14500       case BeginningOfGame:
14501         SendToProgram("force\n", &first);
14502         SetUserThinkingEnables();
14503         break;
14504       case PlayFromGameFile:
14505         (void) StopLoadGameTimer();
14506         if (gameFileFP != NULL) {
14507             gameFileFP = NULL;
14508         }
14509         break;
14510       case EditPosition:
14511         EditPositionDone(TRUE);
14512         break;
14513       case AnalyzeMode:
14514       case AnalyzeFile:
14515         ExitAnalyzeMode();
14516         SendToProgram("force\n", &first);
14517         break;
14518       case TwoMachinesPlay:
14519         GameEnds(EndOfFile, NULL, GE_PLAYER);
14520         ResurrectChessProgram();
14521         SetUserThinkingEnables();
14522         break;
14523       case EndOfGame:
14524         ResurrectChessProgram();
14525         break;
14526       case IcsPlayingBlack:
14527       case IcsPlayingWhite:
14528         DisplayError(_("Warning: You are still playing a game"), 0);
14529         break;
14530       case IcsObserving:
14531         DisplayError(_("Warning: You are still observing a game"), 0);
14532         break;
14533       case IcsExamining:
14534         DisplayError(_("Warning: You are still examining a game"), 0);
14535         break;
14536       case IcsIdle:
14537         break;
14538       case EditGame:
14539       default:
14540         return;
14541     }
14542
14543     pausing = FALSE;
14544     StopClocks();
14545     first.offeredDraw = second.offeredDraw = 0;
14546
14547     if (gameMode == PlayFromGameFile) {
14548         whiteTimeRemaining = timeRemaining[0][currentMove];
14549         blackTimeRemaining = timeRemaining[1][currentMove];
14550         DisplayTitle("");
14551     }
14552
14553     if (gameMode == MachinePlaysWhite ||
14554         gameMode == MachinePlaysBlack ||
14555         gameMode == TwoMachinesPlay ||
14556         gameMode == EndOfGame) {
14557         i = forwardMostMove;
14558         while (i > currentMove) {
14559             SendToProgram("undo\n", &first);
14560             i--;
14561         }
14562         if(!adjustedClock) {
14563         whiteTimeRemaining = timeRemaining[0][currentMove];
14564         blackTimeRemaining = timeRemaining[1][currentMove];
14565         DisplayBothClocks();
14566         }
14567         if (whiteFlag || blackFlag) {
14568             whiteFlag = blackFlag = 0;
14569         }
14570         DisplayTitle("");
14571     }
14572
14573     gameMode = EditGame;
14574     ModeHighlight();
14575     SetGameInfo();
14576 }
14577
14578
14579 void
14580 EditPositionEvent ()
14581 {
14582     if (gameMode == EditPosition) {
14583         EditGameEvent();
14584         return;
14585     }
14586
14587     EditGameEvent();
14588     if (gameMode != EditGame) return;
14589
14590     gameMode = EditPosition;
14591     ModeHighlight();
14592     SetGameInfo();
14593     if (currentMove > 0)
14594       CopyBoard(boards[0], boards[currentMove]);
14595
14596     blackPlaysFirst = !WhiteOnMove(currentMove);
14597     ResetClocks();
14598     currentMove = forwardMostMove = backwardMostMove = 0;
14599     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14600     DisplayMove(-1);
14601     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14602 }
14603
14604 void
14605 ExitAnalyzeMode ()
14606 {
14607     /* [DM] icsEngineAnalyze - possible call from other functions */
14608     if (appData.icsEngineAnalyze) {
14609         appData.icsEngineAnalyze = FALSE;
14610
14611         DisplayMessage("",_("Close ICS engine analyze..."));
14612     }
14613     if (first.analysisSupport && first.analyzing) {
14614       SendToBoth("exit\n");
14615       first.analyzing = second.analyzing = FALSE;
14616     }
14617     thinkOutput[0] = NULLCHAR;
14618 }
14619
14620 void
14621 EditPositionDone (Boolean fakeRights)
14622 {
14623     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14624
14625     startedFromSetupPosition = TRUE;
14626     InitChessProgram(&first, FALSE);
14627     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14628       boards[0][EP_STATUS] = EP_NONE;
14629       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14630       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14631         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14632         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14633       } else boards[0][CASTLING][2] = NoRights;
14634       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14635         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14636         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14637       } else boards[0][CASTLING][5] = NoRights;
14638       if(gameInfo.variant == VariantSChess) {
14639         int i;
14640         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14641           boards[0][VIRGIN][i] = 0;
14642           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14643           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14644         }
14645       }
14646     }
14647     SendToProgram("force\n", &first);
14648     if (blackPlaysFirst) {
14649         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14650         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14651         currentMove = forwardMostMove = backwardMostMove = 1;
14652         CopyBoard(boards[1], boards[0]);
14653     } else {
14654         currentMove = forwardMostMove = backwardMostMove = 0;
14655     }
14656     SendBoard(&first, forwardMostMove);
14657     if (appData.debugMode) {
14658         fprintf(debugFP, "EditPosDone\n");
14659     }
14660     DisplayTitle("");
14661     DisplayMessage("", "");
14662     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14663     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14664     gameMode = EditGame;
14665     ModeHighlight();
14666     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14667     ClearHighlights(); /* [AS] */
14668 }
14669
14670 /* Pause for `ms' milliseconds */
14671 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14672 void
14673 TimeDelay (long ms)
14674 {
14675     TimeMark m1, m2;
14676
14677     GetTimeMark(&m1);
14678     do {
14679         GetTimeMark(&m2);
14680     } while (SubtractTimeMarks(&m2, &m1) < ms);
14681 }
14682
14683 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14684 void
14685 SendMultiLineToICS (char *buf)
14686 {
14687     char temp[MSG_SIZ+1], *p;
14688     int len;
14689
14690     len = strlen(buf);
14691     if (len > MSG_SIZ)
14692       len = MSG_SIZ;
14693
14694     strncpy(temp, buf, len);
14695     temp[len] = 0;
14696
14697     p = temp;
14698     while (*p) {
14699         if (*p == '\n' || *p == '\r')
14700           *p = ' ';
14701         ++p;
14702     }
14703
14704     strcat(temp, "\n");
14705     SendToICS(temp);
14706     SendToPlayer(temp, strlen(temp));
14707 }
14708
14709 void
14710 SetWhiteToPlayEvent ()
14711 {
14712     if (gameMode == EditPosition) {
14713         blackPlaysFirst = FALSE;
14714         DisplayBothClocks();    /* works because currentMove is 0 */
14715     } else if (gameMode == IcsExamining) {
14716         SendToICS(ics_prefix);
14717         SendToICS("tomove white\n");
14718     }
14719 }
14720
14721 void
14722 SetBlackToPlayEvent ()
14723 {
14724     if (gameMode == EditPosition) {
14725         blackPlaysFirst = TRUE;
14726         currentMove = 1;        /* kludge */
14727         DisplayBothClocks();
14728         currentMove = 0;
14729     } else if (gameMode == IcsExamining) {
14730         SendToICS(ics_prefix);
14731         SendToICS("tomove black\n");
14732     }
14733 }
14734
14735 void
14736 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14737 {
14738     char buf[MSG_SIZ];
14739     ChessSquare piece = boards[0][y][x];
14740     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14741     static int lastVariant;
14742
14743     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14744
14745     switch (selection) {
14746       case ClearBoard:
14747         CopyBoard(currentBoard, boards[0]);
14748         CopyBoard(menuBoard, initialPosition);
14749         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14750             SendToICS(ics_prefix);
14751             SendToICS("bsetup clear\n");
14752         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14753             SendToICS(ics_prefix);
14754             SendToICS("clearboard\n");
14755         } else {
14756             int nonEmpty = 0;
14757             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14758                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14759                 for (y = 0; y < BOARD_HEIGHT; y++) {
14760                     if (gameMode == IcsExamining) {
14761                         if (boards[currentMove][y][x] != EmptySquare) {
14762                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14763                                     AAA + x, ONE + y);
14764                             SendToICS(buf);
14765                         }
14766                     } else {
14767                         if(boards[0][y][x] != p) nonEmpty++;
14768                         boards[0][y][x] = p;
14769                     }
14770                 }
14771                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14772             }
14773             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14774                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14775                     ChessSquare p = menuBoard[0][x];
14776                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14777                     p = menuBoard[BOARD_HEIGHT-1][x];
14778                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14779                 }
14780                 DisplayMessage("Clicking clock again restores position", "");
14781                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14782                 if(!nonEmpty) { // asked to clear an empty board
14783                     CopyBoard(boards[0], menuBoard);
14784                 } else
14785                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14786                     CopyBoard(boards[0], initialPosition);
14787                 } else
14788                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14789                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14790                     CopyBoard(boards[0], erasedBoard);
14791                 } else
14792                     CopyBoard(erasedBoard, currentBoard);
14793
14794             }
14795         }
14796         if (gameMode == EditPosition) {
14797             DrawPosition(FALSE, boards[0]);
14798         }
14799         break;
14800
14801       case WhitePlay:
14802         SetWhiteToPlayEvent();
14803         break;
14804
14805       case BlackPlay:
14806         SetBlackToPlayEvent();
14807         break;
14808
14809       case EmptySquare:
14810         if (gameMode == IcsExamining) {
14811             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14812             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14813             SendToICS(buf);
14814         } else {
14815             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14816                 if(x == BOARD_LEFT-2) {
14817                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14818                     boards[0][y][1] = 0;
14819                 } else
14820                 if(x == BOARD_RGHT+1) {
14821                     if(y >= gameInfo.holdingsSize) break;
14822                     boards[0][y][BOARD_WIDTH-2] = 0;
14823                 } else break;
14824             }
14825             boards[0][y][x] = EmptySquare;
14826             DrawPosition(FALSE, boards[0]);
14827         }
14828         break;
14829
14830       case PromotePiece:
14831         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14832            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14833             selection = (ChessSquare) (PROMOTED piece);
14834         } else if(piece == EmptySquare) selection = WhiteSilver;
14835         else selection = (ChessSquare)((int)piece - 1);
14836         goto defaultlabel;
14837
14838       case DemotePiece:
14839         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14840            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14841             selection = (ChessSquare) (DEMOTED piece);
14842         } else if(piece == EmptySquare) selection = BlackSilver;
14843         else selection = (ChessSquare)((int)piece + 1);
14844         goto defaultlabel;
14845
14846       case WhiteQueen:
14847       case BlackQueen:
14848         if(gameInfo.variant == VariantShatranj ||
14849            gameInfo.variant == VariantXiangqi  ||
14850            gameInfo.variant == VariantCourier  ||
14851            gameInfo.variant == VariantASEAN    ||
14852            gameInfo.variant == VariantMakruk     )
14853             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14854         goto defaultlabel;
14855
14856       case WhiteKing:
14857       case BlackKing:
14858         if(gameInfo.variant == VariantXiangqi)
14859             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14860         if(gameInfo.variant == VariantKnightmate)
14861             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14862       default:
14863         defaultlabel:
14864         if (gameMode == IcsExamining) {
14865             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14866             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14867                      PieceToChar(selection), AAA + x, ONE + y);
14868             SendToICS(buf);
14869         } else {
14870             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14871                 int n;
14872                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14873                     n = PieceToNumber(selection - BlackPawn);
14874                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14875                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14876                     boards[0][BOARD_HEIGHT-1-n][1]++;
14877                 } else
14878                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14879                     n = PieceToNumber(selection);
14880                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14881                     boards[0][n][BOARD_WIDTH-1] = selection;
14882                     boards[0][n][BOARD_WIDTH-2]++;
14883                 }
14884             } else
14885             boards[0][y][x] = selection;
14886             DrawPosition(TRUE, boards[0]);
14887             ClearHighlights();
14888             fromX = fromY = -1;
14889         }
14890         break;
14891     }
14892 }
14893
14894
14895 void
14896 DropMenuEvent (ChessSquare selection, int x, int y)
14897 {
14898     ChessMove moveType;
14899
14900     switch (gameMode) {
14901       case IcsPlayingWhite:
14902       case MachinePlaysBlack:
14903         if (!WhiteOnMove(currentMove)) {
14904             DisplayMoveError(_("It is Black's turn"));
14905             return;
14906         }
14907         moveType = WhiteDrop;
14908         break;
14909       case IcsPlayingBlack:
14910       case MachinePlaysWhite:
14911         if (WhiteOnMove(currentMove)) {
14912             DisplayMoveError(_("It is White's turn"));
14913             return;
14914         }
14915         moveType = BlackDrop;
14916         break;
14917       case EditGame:
14918         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14919         break;
14920       default:
14921         return;
14922     }
14923
14924     if (moveType == BlackDrop && selection < BlackPawn) {
14925       selection = (ChessSquare) ((int) selection
14926                                  + (int) BlackPawn - (int) WhitePawn);
14927     }
14928     if (boards[currentMove][y][x] != EmptySquare) {
14929         DisplayMoveError(_("That square is occupied"));
14930         return;
14931     }
14932
14933     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14934 }
14935
14936 void
14937 AcceptEvent ()
14938 {
14939     /* Accept a pending offer of any kind from opponent */
14940
14941     if (appData.icsActive) {
14942         SendToICS(ics_prefix);
14943         SendToICS("accept\n");
14944     } else if (cmailMsgLoaded) {
14945         if (currentMove == cmailOldMove &&
14946             commentList[cmailOldMove] != NULL &&
14947             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14948                    "Black offers a draw" : "White offers a draw")) {
14949             TruncateGame();
14950             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14951             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14952         } else {
14953             DisplayError(_("There is no pending offer on this move"), 0);
14954             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14955         }
14956     } else {
14957         /* Not used for offers from chess program */
14958     }
14959 }
14960
14961 void
14962 DeclineEvent ()
14963 {
14964     /* Decline a pending offer of any kind from opponent */
14965
14966     if (appData.icsActive) {
14967         SendToICS(ics_prefix);
14968         SendToICS("decline\n");
14969     } else if (cmailMsgLoaded) {
14970         if (currentMove == cmailOldMove &&
14971             commentList[cmailOldMove] != NULL &&
14972             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14973                    "Black offers a draw" : "White offers a draw")) {
14974 #ifdef NOTDEF
14975             AppendComment(cmailOldMove, "Draw declined", TRUE);
14976             DisplayComment(cmailOldMove - 1, "Draw declined");
14977 #endif /*NOTDEF*/
14978         } else {
14979             DisplayError(_("There is no pending offer on this move"), 0);
14980         }
14981     } else {
14982         /* Not used for offers from chess program */
14983     }
14984 }
14985
14986 void
14987 RematchEvent ()
14988 {
14989     /* Issue ICS rematch command */
14990     if (appData.icsActive) {
14991         SendToICS(ics_prefix);
14992         SendToICS("rematch\n");
14993     }
14994 }
14995
14996 void
14997 CallFlagEvent ()
14998 {
14999     /* Call your opponent's flag (claim a win on time) */
15000     if (appData.icsActive) {
15001         SendToICS(ics_prefix);
15002         SendToICS("flag\n");
15003     } else {
15004         switch (gameMode) {
15005           default:
15006             return;
15007           case MachinePlaysWhite:
15008             if (whiteFlag) {
15009                 if (blackFlag)
15010                   GameEnds(GameIsDrawn, "Both players ran out of time",
15011                            GE_PLAYER);
15012                 else
15013                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15014             } else {
15015                 DisplayError(_("Your opponent is not out of time"), 0);
15016             }
15017             break;
15018           case MachinePlaysBlack:
15019             if (blackFlag) {
15020                 if (whiteFlag)
15021                   GameEnds(GameIsDrawn, "Both players ran out of time",
15022                            GE_PLAYER);
15023                 else
15024                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15025             } else {
15026                 DisplayError(_("Your opponent is not out of time"), 0);
15027             }
15028             break;
15029         }
15030     }
15031 }
15032
15033 void
15034 ClockClick (int which)
15035 {       // [HGM] code moved to back-end from winboard.c
15036         if(which) { // black clock
15037           if (gameMode == EditPosition || gameMode == IcsExamining) {
15038             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15039             SetBlackToPlayEvent();
15040           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15041           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15042           } else if (shiftKey) {
15043             AdjustClock(which, -1);
15044           } else if (gameMode == IcsPlayingWhite ||
15045                      gameMode == MachinePlaysBlack) {
15046             CallFlagEvent();
15047           }
15048         } else { // white clock
15049           if (gameMode == EditPosition || gameMode == IcsExamining) {
15050             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15051             SetWhiteToPlayEvent();
15052           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15053           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15054           } else if (shiftKey) {
15055             AdjustClock(which, -1);
15056           } else if (gameMode == IcsPlayingBlack ||
15057                    gameMode == MachinePlaysWhite) {
15058             CallFlagEvent();
15059           }
15060         }
15061 }
15062
15063 void
15064 DrawEvent ()
15065 {
15066     /* Offer draw or accept pending draw offer from opponent */
15067
15068     if (appData.icsActive) {
15069         /* Note: tournament rules require draw offers to be
15070            made after you make your move but before you punch
15071            your clock.  Currently ICS doesn't let you do that;
15072            instead, you immediately punch your clock after making
15073            a move, but you can offer a draw at any time. */
15074
15075         SendToICS(ics_prefix);
15076         SendToICS("draw\n");
15077         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15078     } else if (cmailMsgLoaded) {
15079         if (currentMove == cmailOldMove &&
15080             commentList[cmailOldMove] != NULL &&
15081             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15082                    "Black offers a draw" : "White offers a draw")) {
15083             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15084             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15085         } else if (currentMove == cmailOldMove + 1) {
15086             char *offer = WhiteOnMove(cmailOldMove) ?
15087               "White offers a draw" : "Black offers a draw";
15088             AppendComment(currentMove, offer, TRUE);
15089             DisplayComment(currentMove - 1, offer);
15090             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15091         } else {
15092             DisplayError(_("You must make your move before offering a draw"), 0);
15093             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15094         }
15095     } else if (first.offeredDraw) {
15096         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15097     } else {
15098         if (first.sendDrawOffers) {
15099             SendToProgram("draw\n", &first);
15100             userOfferedDraw = TRUE;
15101         }
15102     }
15103 }
15104
15105 void
15106 AdjournEvent ()
15107 {
15108     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15109
15110     if (appData.icsActive) {
15111         SendToICS(ics_prefix);
15112         SendToICS("adjourn\n");
15113     } else {
15114         /* Currently GNU Chess doesn't offer or accept Adjourns */
15115     }
15116 }
15117
15118
15119 void
15120 AbortEvent ()
15121 {
15122     /* Offer Abort or accept pending Abort offer from opponent */
15123
15124     if (appData.icsActive) {
15125         SendToICS(ics_prefix);
15126         SendToICS("abort\n");
15127     } else {
15128         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15129     }
15130 }
15131
15132 void
15133 ResignEvent ()
15134 {
15135     /* Resign.  You can do this even if it's not your turn. */
15136
15137     if (appData.icsActive) {
15138         SendToICS(ics_prefix);
15139         SendToICS("resign\n");
15140     } else {
15141         switch (gameMode) {
15142           case MachinePlaysWhite:
15143             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15144             break;
15145           case MachinePlaysBlack:
15146             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15147             break;
15148           case EditGame:
15149             if (cmailMsgLoaded) {
15150                 TruncateGame();
15151                 if (WhiteOnMove(cmailOldMove)) {
15152                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15153                 } else {
15154                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15155                 }
15156                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15157             }
15158             break;
15159           default:
15160             break;
15161         }
15162     }
15163 }
15164
15165
15166 void
15167 StopObservingEvent ()
15168 {
15169     /* Stop observing current games */
15170     SendToICS(ics_prefix);
15171     SendToICS("unobserve\n");
15172 }
15173
15174 void
15175 StopExaminingEvent ()
15176 {
15177     /* Stop observing current game */
15178     SendToICS(ics_prefix);
15179     SendToICS("unexamine\n");
15180 }
15181
15182 void
15183 ForwardInner (int target)
15184 {
15185     int limit; int oldSeekGraphUp = seekGraphUp;
15186
15187     if (appData.debugMode)
15188         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15189                 target, currentMove, forwardMostMove);
15190
15191     if (gameMode == EditPosition)
15192       return;
15193
15194     seekGraphUp = FALSE;
15195     MarkTargetSquares(1);
15196
15197     if (gameMode == PlayFromGameFile && !pausing)
15198       PauseEvent();
15199
15200     if (gameMode == IcsExamining && pausing)
15201       limit = pauseExamForwardMostMove;
15202     else
15203       limit = forwardMostMove;
15204
15205     if (target > limit) target = limit;
15206
15207     if (target > 0 && moveList[target - 1][0]) {
15208         int fromX, fromY, toX, toY;
15209         toX = moveList[target - 1][2] - AAA;
15210         toY = moveList[target - 1][3] - ONE;
15211         if (moveList[target - 1][1] == '@') {
15212             if (appData.highlightLastMove) {
15213                 SetHighlights(-1, -1, toX, toY);
15214             }
15215         } else {
15216             fromX = moveList[target - 1][0] - AAA;
15217             fromY = moveList[target - 1][1] - ONE;
15218             if (target == currentMove + 1) {
15219                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15220             }
15221             if (appData.highlightLastMove) {
15222                 SetHighlights(fromX, fromY, toX, toY);
15223             }
15224         }
15225     }
15226     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15227         gameMode == Training || gameMode == PlayFromGameFile ||
15228         gameMode == AnalyzeFile) {
15229         while (currentMove < target) {
15230             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15231             SendMoveToProgram(currentMove++, &first);
15232         }
15233     } else {
15234         currentMove = target;
15235     }
15236
15237     if (gameMode == EditGame || gameMode == EndOfGame) {
15238         whiteTimeRemaining = timeRemaining[0][currentMove];
15239         blackTimeRemaining = timeRemaining[1][currentMove];
15240     }
15241     DisplayBothClocks();
15242     DisplayMove(currentMove - 1);
15243     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15244     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15245     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15246         DisplayComment(currentMove - 1, commentList[currentMove]);
15247     }
15248     ClearMap(); // [HGM] exclude: invalidate map
15249 }
15250
15251
15252 void
15253 ForwardEvent ()
15254 {
15255     if (gameMode == IcsExamining && !pausing) {
15256         SendToICS(ics_prefix);
15257         SendToICS("forward\n");
15258     } else {
15259         ForwardInner(currentMove + 1);
15260     }
15261 }
15262
15263 void
15264 ToEndEvent ()
15265 {
15266     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15267         /* to optimze, we temporarily turn off analysis mode while we feed
15268          * the remaining moves to the engine. Otherwise we get analysis output
15269          * after each move.
15270          */
15271         if (first.analysisSupport) {
15272           SendToProgram("exit\nforce\n", &first);
15273           first.analyzing = FALSE;
15274         }
15275     }
15276
15277     if (gameMode == IcsExamining && !pausing) {
15278         SendToICS(ics_prefix);
15279         SendToICS("forward 999999\n");
15280     } else {
15281         ForwardInner(forwardMostMove);
15282     }
15283
15284     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15285         /* we have fed all the moves, so reactivate analysis mode */
15286         SendToProgram("analyze\n", &first);
15287         first.analyzing = TRUE;
15288         /*first.maybeThinking = TRUE;*/
15289         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15290     }
15291 }
15292
15293 void
15294 BackwardInner (int target)
15295 {
15296     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15297
15298     if (appData.debugMode)
15299         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15300                 target, currentMove, forwardMostMove);
15301
15302     if (gameMode == EditPosition) return;
15303     seekGraphUp = FALSE;
15304     MarkTargetSquares(1);
15305     if (currentMove <= backwardMostMove) {
15306         ClearHighlights();
15307         DrawPosition(full_redraw, boards[currentMove]);
15308         return;
15309     }
15310     if (gameMode == PlayFromGameFile && !pausing)
15311       PauseEvent();
15312
15313     if (moveList[target][0]) {
15314         int fromX, fromY, toX, toY;
15315         toX = moveList[target][2] - AAA;
15316         toY = moveList[target][3] - ONE;
15317         if (moveList[target][1] == '@') {
15318             if (appData.highlightLastMove) {
15319                 SetHighlights(-1, -1, toX, toY);
15320             }
15321         } else {
15322             fromX = moveList[target][0] - AAA;
15323             fromY = moveList[target][1] - ONE;
15324             if (target == currentMove - 1) {
15325                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15326             }
15327             if (appData.highlightLastMove) {
15328                 SetHighlights(fromX, fromY, toX, toY);
15329             }
15330         }
15331     }
15332     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15333         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15334         while (currentMove > target) {
15335             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15336                 // null move cannot be undone. Reload program with move history before it.
15337                 int i;
15338                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15339                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15340                 }
15341                 SendBoard(&first, i);
15342               if(second.analyzing) SendBoard(&second, i);
15343                 for(currentMove=i; currentMove<target; currentMove++) {
15344                     SendMoveToProgram(currentMove, &first);
15345                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15346                 }
15347                 break;
15348             }
15349             SendToBoth("undo\n");
15350             currentMove--;
15351         }
15352     } else {
15353         currentMove = target;
15354     }
15355
15356     if (gameMode == EditGame || gameMode == EndOfGame) {
15357         whiteTimeRemaining = timeRemaining[0][currentMove];
15358         blackTimeRemaining = timeRemaining[1][currentMove];
15359     }
15360     DisplayBothClocks();
15361     DisplayMove(currentMove - 1);
15362     DrawPosition(full_redraw, boards[currentMove]);
15363     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15364     // [HGM] PV info: routine tests if comment empty
15365     DisplayComment(currentMove - 1, commentList[currentMove]);
15366     ClearMap(); // [HGM] exclude: invalidate map
15367 }
15368
15369 void
15370 BackwardEvent ()
15371 {
15372     if (gameMode == IcsExamining && !pausing) {
15373         SendToICS(ics_prefix);
15374         SendToICS("backward\n");
15375     } else {
15376         BackwardInner(currentMove - 1);
15377     }
15378 }
15379
15380 void
15381 ToStartEvent ()
15382 {
15383     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15384         /* to optimize, we temporarily turn off analysis mode while we undo
15385          * all the moves. Otherwise we get analysis output after each undo.
15386          */
15387         if (first.analysisSupport) {
15388           SendToProgram("exit\nforce\n", &first);
15389           first.analyzing = FALSE;
15390         }
15391     }
15392
15393     if (gameMode == IcsExamining && !pausing) {
15394         SendToICS(ics_prefix);
15395         SendToICS("backward 999999\n");
15396     } else {
15397         BackwardInner(backwardMostMove);
15398     }
15399
15400     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15401         /* we have fed all the moves, so reactivate analysis mode */
15402         SendToProgram("analyze\n", &first);
15403         first.analyzing = TRUE;
15404         /*first.maybeThinking = TRUE;*/
15405         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15406     }
15407 }
15408
15409 void
15410 ToNrEvent (int to)
15411 {
15412   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15413   if (to >= forwardMostMove) to = forwardMostMove;
15414   if (to <= backwardMostMove) to = backwardMostMove;
15415   if (to < currentMove) {
15416     BackwardInner(to);
15417   } else {
15418     ForwardInner(to);
15419   }
15420 }
15421
15422 void
15423 RevertEvent (Boolean annotate)
15424 {
15425     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15426         return;
15427     }
15428     if (gameMode != IcsExamining) {
15429         DisplayError(_("You are not examining a game"), 0);
15430         return;
15431     }
15432     if (pausing) {
15433         DisplayError(_("You can't revert while pausing"), 0);
15434         return;
15435     }
15436     SendToICS(ics_prefix);
15437     SendToICS("revert\n");
15438 }
15439
15440 void
15441 RetractMoveEvent ()
15442 {
15443     switch (gameMode) {
15444       case MachinePlaysWhite:
15445       case MachinePlaysBlack:
15446         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15447             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15448             return;
15449         }
15450         if (forwardMostMove < 2) return;
15451         currentMove = forwardMostMove = forwardMostMove - 2;
15452         whiteTimeRemaining = timeRemaining[0][currentMove];
15453         blackTimeRemaining = timeRemaining[1][currentMove];
15454         DisplayBothClocks();
15455         DisplayMove(currentMove - 1);
15456         ClearHighlights();/*!! could figure this out*/
15457         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15458         SendToProgram("remove\n", &first);
15459         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15460         break;
15461
15462       case BeginningOfGame:
15463       default:
15464         break;
15465
15466       case IcsPlayingWhite:
15467       case IcsPlayingBlack:
15468         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15469             SendToICS(ics_prefix);
15470             SendToICS("takeback 2\n");
15471         } else {
15472             SendToICS(ics_prefix);
15473             SendToICS("takeback 1\n");
15474         }
15475         break;
15476     }
15477 }
15478
15479 void
15480 MoveNowEvent ()
15481 {
15482     ChessProgramState *cps;
15483
15484     switch (gameMode) {
15485       case MachinePlaysWhite:
15486         if (!WhiteOnMove(forwardMostMove)) {
15487             DisplayError(_("It is your turn"), 0);
15488             return;
15489         }
15490         cps = &first;
15491         break;
15492       case MachinePlaysBlack:
15493         if (WhiteOnMove(forwardMostMove)) {
15494             DisplayError(_("It is your turn"), 0);
15495             return;
15496         }
15497         cps = &first;
15498         break;
15499       case TwoMachinesPlay:
15500         if (WhiteOnMove(forwardMostMove) ==
15501             (first.twoMachinesColor[0] == 'w')) {
15502             cps = &first;
15503         } else {
15504             cps = &second;
15505         }
15506         break;
15507       case BeginningOfGame:
15508       default:
15509         return;
15510     }
15511     SendToProgram("?\n", cps);
15512 }
15513
15514 void
15515 TruncateGameEvent ()
15516 {
15517     EditGameEvent();
15518     if (gameMode != EditGame) return;
15519     TruncateGame();
15520 }
15521
15522 void
15523 TruncateGame ()
15524 {
15525     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15526     if (forwardMostMove > currentMove) {
15527         if (gameInfo.resultDetails != NULL) {
15528             free(gameInfo.resultDetails);
15529             gameInfo.resultDetails = NULL;
15530             gameInfo.result = GameUnfinished;
15531         }
15532         forwardMostMove = currentMove;
15533         HistorySet(parseList, backwardMostMove, forwardMostMove,
15534                    currentMove-1);
15535     }
15536 }
15537
15538 void
15539 HintEvent ()
15540 {
15541     if (appData.noChessProgram) return;
15542     switch (gameMode) {
15543       case MachinePlaysWhite:
15544         if (WhiteOnMove(forwardMostMove)) {
15545             DisplayError(_("Wait until your turn."), 0);
15546             return;
15547         }
15548         break;
15549       case BeginningOfGame:
15550       case MachinePlaysBlack:
15551         if (!WhiteOnMove(forwardMostMove)) {
15552             DisplayError(_("Wait until your turn."), 0);
15553             return;
15554         }
15555         break;
15556       default:
15557         DisplayError(_("No hint available"), 0);
15558         return;
15559     }
15560     SendToProgram("hint\n", &first);
15561     hintRequested = TRUE;
15562 }
15563
15564 void
15565 CreateBookEvent ()
15566 {
15567     ListGame * lg = (ListGame *) gameList.head;
15568     FILE *f, *g;
15569     int nItem;
15570     static int secondTime = FALSE;
15571
15572     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15573         DisplayError(_("Game list not loaded or empty"), 0);
15574         return;
15575     }
15576
15577     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15578         fclose(g);
15579         secondTime++;
15580         DisplayNote(_("Book file exists! Try again for overwrite."));
15581         return;
15582     }
15583
15584     creatingBook = TRUE;
15585     secondTime = FALSE;
15586
15587     /* Get list size */
15588     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15589         LoadGame(f, nItem, "", TRUE);
15590         AddGameToBook(TRUE);
15591         lg = (ListGame *) lg->node.succ;
15592     }
15593
15594     creatingBook = FALSE;
15595     FlushBook();
15596 }
15597
15598 void
15599 BookEvent ()
15600 {
15601     if (appData.noChessProgram) return;
15602     switch (gameMode) {
15603       case MachinePlaysWhite:
15604         if (WhiteOnMove(forwardMostMove)) {
15605             DisplayError(_("Wait until your turn."), 0);
15606             return;
15607         }
15608         break;
15609       case BeginningOfGame:
15610       case MachinePlaysBlack:
15611         if (!WhiteOnMove(forwardMostMove)) {
15612             DisplayError(_("Wait until your turn."), 0);
15613             return;
15614         }
15615         break;
15616       case EditPosition:
15617         EditPositionDone(TRUE);
15618         break;
15619       case TwoMachinesPlay:
15620         return;
15621       default:
15622         break;
15623     }
15624     SendToProgram("bk\n", &first);
15625     bookOutput[0] = NULLCHAR;
15626     bookRequested = TRUE;
15627 }
15628
15629 void
15630 AboutGameEvent ()
15631 {
15632     char *tags = PGNTags(&gameInfo);
15633     TagsPopUp(tags, CmailMsg());
15634     free(tags);
15635 }
15636
15637 /* end button procedures */
15638
15639 void
15640 PrintPosition (FILE *fp, int move)
15641 {
15642     int i, j;
15643
15644     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15645         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15646             char c = PieceToChar(boards[move][i][j]);
15647             fputc(c == 'x' ? '.' : c, fp);
15648             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15649         }
15650     }
15651     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15652       fprintf(fp, "white to play\n");
15653     else
15654       fprintf(fp, "black to play\n");
15655 }
15656
15657 void
15658 PrintOpponents (FILE *fp)
15659 {
15660     if (gameInfo.white != NULL) {
15661         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15662     } else {
15663         fprintf(fp, "\n");
15664     }
15665 }
15666
15667 /* Find last component of program's own name, using some heuristics */
15668 void
15669 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15670 {
15671     char *p, *q, c;
15672     int local = (strcmp(host, "localhost") == 0);
15673     while (!local && (p = strchr(prog, ';')) != NULL) {
15674         p++;
15675         while (*p == ' ') p++;
15676         prog = p;
15677     }
15678     if (*prog == '"' || *prog == '\'') {
15679         q = strchr(prog + 1, *prog);
15680     } else {
15681         q = strchr(prog, ' ');
15682     }
15683     if (q == NULL) q = prog + strlen(prog);
15684     p = q;
15685     while (p >= prog && *p != '/' && *p != '\\') p--;
15686     p++;
15687     if(p == prog && *p == '"') p++;
15688     c = *q; *q = 0;
15689     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15690     memcpy(buf, p, q - p);
15691     buf[q - p] = NULLCHAR;
15692     if (!local) {
15693         strcat(buf, "@");
15694         strcat(buf, host);
15695     }
15696 }
15697
15698 char *
15699 TimeControlTagValue ()
15700 {
15701     char buf[MSG_SIZ];
15702     if (!appData.clockMode) {
15703       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15704     } else if (movesPerSession > 0) {
15705       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15706     } else if (timeIncrement == 0) {
15707       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15708     } else {
15709       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15710     }
15711     return StrSave(buf);
15712 }
15713
15714 void
15715 SetGameInfo ()
15716 {
15717     /* This routine is used only for certain modes */
15718     VariantClass v = gameInfo.variant;
15719     ChessMove r = GameUnfinished;
15720     char *p = NULL;
15721
15722     if(keepInfo) return;
15723
15724     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15725         r = gameInfo.result;
15726         p = gameInfo.resultDetails;
15727         gameInfo.resultDetails = NULL;
15728     }
15729     ClearGameInfo(&gameInfo);
15730     gameInfo.variant = v;
15731
15732     switch (gameMode) {
15733       case MachinePlaysWhite:
15734         gameInfo.event = StrSave( appData.pgnEventHeader );
15735         gameInfo.site = StrSave(HostName());
15736         gameInfo.date = PGNDate();
15737         gameInfo.round = StrSave("-");
15738         gameInfo.white = StrSave(first.tidy);
15739         gameInfo.black = StrSave(UserName());
15740         gameInfo.timeControl = TimeControlTagValue();
15741         break;
15742
15743       case MachinePlaysBlack:
15744         gameInfo.event = StrSave( appData.pgnEventHeader );
15745         gameInfo.site = StrSave(HostName());
15746         gameInfo.date = PGNDate();
15747         gameInfo.round = StrSave("-");
15748         gameInfo.white = StrSave(UserName());
15749         gameInfo.black = StrSave(first.tidy);
15750         gameInfo.timeControl = TimeControlTagValue();
15751         break;
15752
15753       case TwoMachinesPlay:
15754         gameInfo.event = StrSave( appData.pgnEventHeader );
15755         gameInfo.site = StrSave(HostName());
15756         gameInfo.date = PGNDate();
15757         if (roundNr > 0) {
15758             char buf[MSG_SIZ];
15759             snprintf(buf, MSG_SIZ, "%d", roundNr);
15760             gameInfo.round = StrSave(buf);
15761         } else {
15762             gameInfo.round = StrSave("-");
15763         }
15764         if (first.twoMachinesColor[0] == 'w') {
15765             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15766             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15767         } else {
15768             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15769             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15770         }
15771         gameInfo.timeControl = TimeControlTagValue();
15772         break;
15773
15774       case EditGame:
15775         gameInfo.event = StrSave("Edited game");
15776         gameInfo.site = StrSave(HostName());
15777         gameInfo.date = PGNDate();
15778         gameInfo.round = StrSave("-");
15779         gameInfo.white = StrSave("-");
15780         gameInfo.black = StrSave("-");
15781         gameInfo.result = r;
15782         gameInfo.resultDetails = p;
15783         break;
15784
15785       case EditPosition:
15786         gameInfo.event = StrSave("Edited position");
15787         gameInfo.site = StrSave(HostName());
15788         gameInfo.date = PGNDate();
15789         gameInfo.round = StrSave("-");
15790         gameInfo.white = StrSave("-");
15791         gameInfo.black = StrSave("-");
15792         break;
15793
15794       case IcsPlayingWhite:
15795       case IcsPlayingBlack:
15796       case IcsObserving:
15797       case IcsExamining:
15798         break;
15799
15800       case PlayFromGameFile:
15801         gameInfo.event = StrSave("Game from non-PGN file");
15802         gameInfo.site = StrSave(HostName());
15803         gameInfo.date = PGNDate();
15804         gameInfo.round = StrSave("-");
15805         gameInfo.white = StrSave("?");
15806         gameInfo.black = StrSave("?");
15807         break;
15808
15809       default:
15810         break;
15811     }
15812 }
15813
15814 void
15815 ReplaceComment (int index, char *text)
15816 {
15817     int len;
15818     char *p;
15819     float score;
15820
15821     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
15822        pvInfoList[index-1].depth == len &&
15823        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15824        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15825     while (*text == '\n') text++;
15826     len = strlen(text);
15827     while (len > 0 && text[len - 1] == '\n') len--;
15828
15829     if (commentList[index] != NULL)
15830       free(commentList[index]);
15831
15832     if (len == 0) {
15833         commentList[index] = NULL;
15834         return;
15835     }
15836   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15837       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15838       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15839     commentList[index] = (char *) malloc(len + 2);
15840     strncpy(commentList[index], text, len);
15841     commentList[index][len] = '\n';
15842     commentList[index][len + 1] = NULLCHAR;
15843   } else {
15844     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15845     char *p;
15846     commentList[index] = (char *) malloc(len + 7);
15847     safeStrCpy(commentList[index], "{\n", 3);
15848     safeStrCpy(commentList[index]+2, text, len+1);
15849     commentList[index][len+2] = NULLCHAR;
15850     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15851     strcat(commentList[index], "\n}\n");
15852   }
15853 }
15854
15855 void
15856 CrushCRs (char *text)
15857 {
15858   char *p = text;
15859   char *q = text;
15860   char ch;
15861
15862   do {
15863     ch = *p++;
15864     if (ch == '\r') continue;
15865     *q++ = ch;
15866   } while (ch != '\0');
15867 }
15868
15869 void
15870 AppendComment (int index, char *text, Boolean addBraces)
15871 /* addBraces  tells if we should add {} */
15872 {
15873     int oldlen, len;
15874     char *old;
15875
15876 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
15877     if(addBraces == 3) addBraces = 0; else // force appending literally
15878     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15879
15880     CrushCRs(text);
15881     while (*text == '\n') text++;
15882     len = strlen(text);
15883     while (len > 0 && text[len - 1] == '\n') len--;
15884     text[len] = NULLCHAR;
15885
15886     if (len == 0) return;
15887
15888     if (commentList[index] != NULL) {
15889       Boolean addClosingBrace = addBraces;
15890         old = commentList[index];
15891         oldlen = strlen(old);
15892         while(commentList[index][oldlen-1] ==  '\n')
15893           commentList[index][--oldlen] = NULLCHAR;
15894         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15895         safeStrCpy(commentList[index], old, oldlen + len + 6);
15896         free(old);
15897         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15898         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15899           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15900           while (*text == '\n') { text++; len--; }
15901           commentList[index][--oldlen] = NULLCHAR;
15902       }
15903         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15904         else          strcat(commentList[index], "\n");
15905         strcat(commentList[index], text);
15906         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15907         else          strcat(commentList[index], "\n");
15908     } else {
15909         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15910         if(addBraces)
15911           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15912         else commentList[index][0] = NULLCHAR;
15913         strcat(commentList[index], text);
15914         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15915         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15916     }
15917 }
15918
15919 static char *
15920 FindStr (char * text, char * sub_text)
15921 {
15922     char * result = strstr( text, sub_text );
15923
15924     if( result != NULL ) {
15925         result += strlen( sub_text );
15926     }
15927
15928     return result;
15929 }
15930
15931 /* [AS] Try to extract PV info from PGN comment */
15932 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15933 char *
15934 GetInfoFromComment (int index, char * text)
15935 {
15936     char * sep = text, *p;
15937
15938     if( text != NULL && index > 0 ) {
15939         int score = 0;
15940         int depth = 0;
15941         int time = -1, sec = 0, deci;
15942         char * s_eval = FindStr( text, "[%eval " );
15943         char * s_emt = FindStr( text, "[%emt " );
15944 #if 0
15945         if( s_eval != NULL || s_emt != NULL ) {
15946 #else
15947         if(0) { // [HGM] this code is not finished, and could actually be detrimental
15948 #endif
15949             /* New style */
15950             char delim;
15951
15952             if( s_eval != NULL ) {
15953                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15954                     return text;
15955                 }
15956
15957                 if( delim != ']' ) {
15958                     return text;
15959                 }
15960             }
15961
15962             if( s_emt != NULL ) {
15963             }
15964                 return text;
15965         }
15966         else {
15967             /* We expect something like: [+|-]nnn.nn/dd */
15968             int score_lo = 0;
15969
15970             if(*text != '{') return text; // [HGM] braces: must be normal comment
15971
15972             sep = strchr( text, '/' );
15973             if( sep == NULL || sep < (text+4) ) {
15974                 return text;
15975             }
15976
15977             p = text;
15978             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
15979             if(p[1] == '(') { // comment starts with PV
15980                p = strchr(p, ')'); // locate end of PV
15981                if(p == NULL || sep < p+5) return text;
15982                // at this point we have something like "{(.*) +0.23/6 ..."
15983                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15984                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15985                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15986             }
15987             time = -1; sec = -1; deci = -1;
15988             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15989                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15990                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15991                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15992                 return text;
15993             }
15994
15995             if( score_lo < 0 || score_lo >= 100 ) {
15996                 return text;
15997             }
15998
15999             if(sec >= 0) time = 600*time + 10*sec; else
16000             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16001
16002             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16003
16004             /* [HGM] PV time: now locate end of PV info */
16005             while( *++sep >= '0' && *sep <= '9'); // strip depth
16006             if(time >= 0)
16007             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16008             if(sec >= 0)
16009             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16010             if(deci >= 0)
16011             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16012             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16013         }
16014
16015         if( depth <= 0 ) {
16016             return text;
16017         }
16018
16019         if( time < 0 ) {
16020             time = -1;
16021         }
16022
16023         pvInfoList[index-1].depth = depth;
16024         pvInfoList[index-1].score = score;
16025         pvInfoList[index-1].time  = 10*time; // centi-sec
16026         if(*sep == '}') *sep = 0; else *--sep = '{';
16027         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16028     }
16029     return sep;
16030 }
16031
16032 void
16033 SendToProgram (char *message, ChessProgramState *cps)
16034 {
16035     int count, outCount, error;
16036     char buf[MSG_SIZ];
16037
16038     if (cps->pr == NoProc) return;
16039     Attention(cps);
16040
16041     if (appData.debugMode) {
16042         TimeMark now;
16043         GetTimeMark(&now);
16044         fprintf(debugFP, "%ld >%-6s: %s",
16045                 SubtractTimeMarks(&now, &programStartTime),
16046                 cps->which, message);
16047         if(serverFP)
16048             fprintf(serverFP, "%ld >%-6s: %s",
16049                 SubtractTimeMarks(&now, &programStartTime),
16050                 cps->which, message), fflush(serverFP);
16051     }
16052
16053     count = strlen(message);
16054     outCount = OutputToProcess(cps->pr, message, count, &error);
16055     if (outCount < count && !exiting
16056                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16057       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16058       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16059         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16060             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16061                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16062                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16063                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16064             } else {
16065                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16066                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16067                 gameInfo.result = res;
16068             }
16069             gameInfo.resultDetails = StrSave(buf);
16070         }
16071         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16072         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16073     }
16074 }
16075
16076 void
16077 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16078 {
16079     char *end_str;
16080     char buf[MSG_SIZ];
16081     ChessProgramState *cps = (ChessProgramState *)closure;
16082
16083     if (isr != cps->isr) return; /* Killed intentionally */
16084     if (count <= 0) {
16085         if (count == 0) {
16086             RemoveInputSource(cps->isr);
16087             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16088                     _(cps->which), cps->program);
16089             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16090             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16091                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16092                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16093                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16094                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16095                 } else {
16096                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16097                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16098                     gameInfo.result = res;
16099                 }
16100                 gameInfo.resultDetails = StrSave(buf);
16101             }
16102             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16103             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16104         } else {
16105             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16106                     _(cps->which), cps->program);
16107             RemoveInputSource(cps->isr);
16108
16109             /* [AS] Program is misbehaving badly... kill it */
16110             if( count == -2 ) {
16111                 DestroyChildProcess( cps->pr, 9 );
16112                 cps->pr = NoProc;
16113             }
16114
16115             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16116         }
16117         return;
16118     }
16119
16120     if ((end_str = strchr(message, '\r')) != NULL)
16121       *end_str = NULLCHAR;
16122     if ((end_str = strchr(message, '\n')) != NULL)
16123       *end_str = NULLCHAR;
16124
16125     if (appData.debugMode) {
16126         TimeMark now; int print = 1;
16127         char *quote = ""; char c; int i;
16128
16129         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16130                 char start = message[0];
16131                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16132                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16133                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16134                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16135                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16136                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16137                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16138                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16139                    sscanf(message, "hint: %c", &c)!=1 &&
16140                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16141                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16142                     print = (appData.engineComments >= 2);
16143                 }
16144                 message[0] = start; // restore original message
16145         }
16146         if(print) {
16147                 GetTimeMark(&now);
16148                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16149                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16150                         quote,
16151                         message);
16152                 if(serverFP)
16153                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16154                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16155                         quote,
16156                         message), fflush(serverFP);
16157         }
16158     }
16159
16160     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16161     if (appData.icsEngineAnalyze) {
16162         if (strstr(message, "whisper") != NULL ||
16163              strstr(message, "kibitz") != NULL ||
16164             strstr(message, "tellics") != NULL) return;
16165     }
16166
16167     HandleMachineMove(message, cps);
16168 }
16169
16170
16171 void
16172 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16173 {
16174     char buf[MSG_SIZ];
16175     int seconds;
16176
16177     if( timeControl_2 > 0 ) {
16178         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16179             tc = timeControl_2;
16180         }
16181     }
16182     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16183     inc /= cps->timeOdds;
16184     st  /= cps->timeOdds;
16185
16186     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16187
16188     if (st > 0) {
16189       /* Set exact time per move, normally using st command */
16190       if (cps->stKludge) {
16191         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16192         seconds = st % 60;
16193         if (seconds == 0) {
16194           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16195         } else {
16196           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16197         }
16198       } else {
16199         snprintf(buf, MSG_SIZ, "st %d\n", st);
16200       }
16201     } else {
16202       /* Set conventional or incremental time control, using level command */
16203       if (seconds == 0) {
16204         /* Note old gnuchess bug -- minutes:seconds used to not work.
16205            Fixed in later versions, but still avoid :seconds
16206            when seconds is 0. */
16207         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16208       } else {
16209         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16210                  seconds, inc/1000.);
16211       }
16212     }
16213     SendToProgram(buf, cps);
16214
16215     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16216     /* Orthogonally, limit search to given depth */
16217     if (sd > 0) {
16218       if (cps->sdKludge) {
16219         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16220       } else {
16221         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16222       }
16223       SendToProgram(buf, cps);
16224     }
16225
16226     if(cps->nps >= 0) { /* [HGM] nps */
16227         if(cps->supportsNPS == FALSE)
16228           cps->nps = -1; // don't use if engine explicitly says not supported!
16229         else {
16230           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16231           SendToProgram(buf, cps);
16232         }
16233     }
16234 }
16235
16236 ChessProgramState *
16237 WhitePlayer ()
16238 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16239 {
16240     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16241        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16242         return &second;
16243     return &first;
16244 }
16245
16246 void
16247 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16248 {
16249     char message[MSG_SIZ];
16250     long time, otime;
16251
16252     /* Note: this routine must be called when the clocks are stopped
16253        or when they have *just* been set or switched; otherwise
16254        it will be off by the time since the current tick started.
16255     */
16256     if (machineWhite) {
16257         time = whiteTimeRemaining / 10;
16258         otime = blackTimeRemaining / 10;
16259     } else {
16260         time = blackTimeRemaining / 10;
16261         otime = whiteTimeRemaining / 10;
16262     }
16263     /* [HGM] translate opponent's time by time-odds factor */
16264     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16265
16266     if (time <= 0) time = 1;
16267     if (otime <= 0) otime = 1;
16268
16269     snprintf(message, MSG_SIZ, "time %ld\n", time);
16270     SendToProgram(message, cps);
16271
16272     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16273     SendToProgram(message, cps);
16274 }
16275
16276 char *
16277 EngineDefinedVariant (ChessProgramState *cps, int n)
16278 {   // return name of n-th unknown variant that engine supports
16279     static char buf[MSG_SIZ];
16280     char *p, *s = cps->variants;
16281     if(!s) return NULL;
16282     do { // parse string from variants feature
16283       VariantClass v;
16284         p = strchr(s, ',');
16285         if(p) *p = NULLCHAR;
16286       v = StringToVariant(s);
16287       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16288         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16289             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16290         }
16291         if(p) *p++ = ',';
16292         if(n < 0) return buf;
16293     } while(s = p);
16294     return NULL;
16295 }
16296
16297 int
16298 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16299 {
16300   char buf[MSG_SIZ];
16301   int len = strlen(name);
16302   int val;
16303
16304   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16305     (*p) += len + 1;
16306     sscanf(*p, "%d", &val);
16307     *loc = (val != 0);
16308     while (**p && **p != ' ')
16309       (*p)++;
16310     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16311     SendToProgram(buf, cps);
16312     return TRUE;
16313   }
16314   return FALSE;
16315 }
16316
16317 int
16318 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16319 {
16320   char buf[MSG_SIZ];
16321   int len = strlen(name);
16322   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16323     (*p) += len + 1;
16324     sscanf(*p, "%d", loc);
16325     while (**p && **p != ' ') (*p)++;
16326     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16327     SendToProgram(buf, cps);
16328     return TRUE;
16329   }
16330   return FALSE;
16331 }
16332
16333 int
16334 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16335 {
16336   char buf[MSG_SIZ];
16337   int len = strlen(name);
16338   if (strncmp((*p), name, len) == 0
16339       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16340     (*p) += len + 2;
16341     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16342     sscanf(*p, "%[^\"]", *loc);
16343     while (**p && **p != '\"') (*p)++;
16344     if (**p == '\"') (*p)++;
16345     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16346     SendToProgram(buf, cps);
16347     return TRUE;
16348   }
16349   return FALSE;
16350 }
16351
16352 int
16353 ParseOption (Option *opt, ChessProgramState *cps)
16354 // [HGM] options: process the string that defines an engine option, and determine
16355 // name, type, default value, and allowed value range
16356 {
16357         char *p, *q, buf[MSG_SIZ];
16358         int n, min = (-1)<<31, max = 1<<31, def;
16359
16360         if(p = strstr(opt->name, " -spin ")) {
16361             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16362             if(max < min) max = min; // enforce consistency
16363             if(def < min) def = min;
16364             if(def > max) def = max;
16365             opt->value = def;
16366             opt->min = min;
16367             opt->max = max;
16368             opt->type = Spin;
16369         } else if((p = strstr(opt->name, " -slider "))) {
16370             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16371             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16372             if(max < min) max = min; // enforce consistency
16373             if(def < min) def = min;
16374             if(def > max) def = max;
16375             opt->value = def;
16376             opt->min = min;
16377             opt->max = max;
16378             opt->type = Spin; // Slider;
16379         } else if((p = strstr(opt->name, " -string "))) {
16380             opt->textValue = p+9;
16381             opt->type = TextBox;
16382         } else if((p = strstr(opt->name, " -file "))) {
16383             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16384             opt->textValue = p+7;
16385             opt->type = FileName; // FileName;
16386         } else if((p = strstr(opt->name, " -path "))) {
16387             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16388             opt->textValue = p+7;
16389             opt->type = PathName; // PathName;
16390         } else if(p = strstr(opt->name, " -check ")) {
16391             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16392             opt->value = (def != 0);
16393             opt->type = CheckBox;
16394         } else if(p = strstr(opt->name, " -combo ")) {
16395             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16396             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16397             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16398             opt->value = n = 0;
16399             while(q = StrStr(q, " /// ")) {
16400                 n++; *q = 0;    // count choices, and null-terminate each of them
16401                 q += 5;
16402                 if(*q == '*') { // remember default, which is marked with * prefix
16403                     q++;
16404                     opt->value = n;
16405                 }
16406                 cps->comboList[cps->comboCnt++] = q;
16407             }
16408             cps->comboList[cps->comboCnt++] = NULL;
16409             opt->max = n + 1;
16410             opt->type = ComboBox;
16411         } else if(p = strstr(opt->name, " -button")) {
16412             opt->type = Button;
16413         } else if(p = strstr(opt->name, " -save")) {
16414             opt->type = SaveButton;
16415         } else return FALSE;
16416         *p = 0; // terminate option name
16417         // now look if the command-line options define a setting for this engine option.
16418         if(cps->optionSettings && cps->optionSettings[0])
16419             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16420         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16421           snprintf(buf, MSG_SIZ, "option %s", p);
16422                 if(p = strstr(buf, ",")) *p = 0;
16423                 if(q = strchr(buf, '=')) switch(opt->type) {
16424                     case ComboBox:
16425                         for(n=0; n<opt->max; n++)
16426                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16427                         break;
16428                     case TextBox:
16429                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16430                         break;
16431                     case Spin:
16432                     case CheckBox:
16433                         opt->value = atoi(q+1);
16434                     default:
16435                         break;
16436                 }
16437                 strcat(buf, "\n");
16438                 SendToProgram(buf, cps);
16439         }
16440         return TRUE;
16441 }
16442
16443 void
16444 FeatureDone (ChessProgramState *cps, int val)
16445 {
16446   DelayedEventCallback cb = GetDelayedEvent();
16447   if ((cb == InitBackEnd3 && cps == &first) ||
16448       (cb == SettingsMenuIfReady && cps == &second) ||
16449       (cb == LoadEngine) ||
16450       (cb == TwoMachinesEventIfReady)) {
16451     CancelDelayedEvent();
16452     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16453   }
16454   cps->initDone = val;
16455   if(val) cps->reload = FALSE;
16456 }
16457
16458 /* Parse feature command from engine */
16459 void
16460 ParseFeatures (char *args, ChessProgramState *cps)
16461 {
16462   char *p = args;
16463   char *q = NULL;
16464   int val;
16465   char buf[MSG_SIZ];
16466
16467   for (;;) {
16468     while (*p == ' ') p++;
16469     if (*p == NULLCHAR) return;
16470
16471     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16472     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16473     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16474     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16475     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16476     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16477     if (BoolFeature(&p, "reuse", &val, cps)) {
16478       /* Engine can disable reuse, but can't enable it if user said no */
16479       if (!val) cps->reuse = FALSE;
16480       continue;
16481     }
16482     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16483     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16484       if (gameMode == TwoMachinesPlay) {
16485         DisplayTwoMachinesTitle();
16486       } else {
16487         DisplayTitle("");
16488       }
16489       continue;
16490     }
16491     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16492     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16493     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16494     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16495     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16496     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16497     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16498     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16499     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16500     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16501     if (IntFeature(&p, "done", &val, cps)) {
16502       FeatureDone(cps, val);
16503       continue;
16504     }
16505     /* Added by Tord: */
16506     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16507     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16508     /* End of additions by Tord */
16509
16510     /* [HGM] added features: */
16511     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16512     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16513     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16514     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16515     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16516     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16517     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16518     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16519         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16520         FREE(cps->option[cps->nrOptions].name);
16521         cps->option[cps->nrOptions].name = q; q = NULL;
16522         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16523           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16524             SendToProgram(buf, cps);
16525             continue;
16526         }
16527         if(cps->nrOptions >= MAX_OPTIONS) {
16528             cps->nrOptions--;
16529             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16530             DisplayError(buf, 0);
16531         }
16532         continue;
16533     }
16534     /* End of additions by HGM */
16535
16536     /* unknown feature: complain and skip */
16537     q = p;
16538     while (*q && *q != '=') q++;
16539     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16540     SendToProgram(buf, cps);
16541     p = q;
16542     if (*p == '=') {
16543       p++;
16544       if (*p == '\"') {
16545         p++;
16546         while (*p && *p != '\"') p++;
16547         if (*p == '\"') p++;
16548       } else {
16549         while (*p && *p != ' ') p++;
16550       }
16551     }
16552   }
16553
16554 }
16555
16556 void
16557 PeriodicUpdatesEvent (int newState)
16558 {
16559     if (newState == appData.periodicUpdates)
16560       return;
16561
16562     appData.periodicUpdates=newState;
16563
16564     /* Display type changes, so update it now */
16565 //    DisplayAnalysis();
16566
16567     /* Get the ball rolling again... */
16568     if (newState) {
16569         AnalysisPeriodicEvent(1);
16570         StartAnalysisClock();
16571     }
16572 }
16573
16574 void
16575 PonderNextMoveEvent (int newState)
16576 {
16577     if (newState == appData.ponderNextMove) return;
16578     if (gameMode == EditPosition) EditPositionDone(TRUE);
16579     if (newState) {
16580         SendToProgram("hard\n", &first);
16581         if (gameMode == TwoMachinesPlay) {
16582             SendToProgram("hard\n", &second);
16583         }
16584     } else {
16585         SendToProgram("easy\n", &first);
16586         thinkOutput[0] = NULLCHAR;
16587         if (gameMode == TwoMachinesPlay) {
16588             SendToProgram("easy\n", &second);
16589         }
16590     }
16591     appData.ponderNextMove = newState;
16592 }
16593
16594 void
16595 NewSettingEvent (int option, int *feature, char *command, int value)
16596 {
16597     char buf[MSG_SIZ];
16598
16599     if (gameMode == EditPosition) EditPositionDone(TRUE);
16600     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16601     if(feature == NULL || *feature) SendToProgram(buf, &first);
16602     if (gameMode == TwoMachinesPlay) {
16603         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16604     }
16605 }
16606
16607 void
16608 ShowThinkingEvent ()
16609 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16610 {
16611     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16612     int newState = appData.showThinking
16613         // [HGM] thinking: other features now need thinking output as well
16614         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16615
16616     if (oldState == newState) return;
16617     oldState = newState;
16618     if (gameMode == EditPosition) EditPositionDone(TRUE);
16619     if (oldState) {
16620         SendToProgram("post\n", &first);
16621         if (gameMode == TwoMachinesPlay) {
16622             SendToProgram("post\n", &second);
16623         }
16624     } else {
16625         SendToProgram("nopost\n", &first);
16626         thinkOutput[0] = NULLCHAR;
16627         if (gameMode == TwoMachinesPlay) {
16628             SendToProgram("nopost\n", &second);
16629         }
16630     }
16631 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16632 }
16633
16634 void
16635 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16636 {
16637   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16638   if (pr == NoProc) return;
16639   AskQuestion(title, question, replyPrefix, pr);
16640 }
16641
16642 void
16643 TypeInEvent (char firstChar)
16644 {
16645     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16646         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16647         gameMode == AnalyzeMode || gameMode == EditGame ||
16648         gameMode == EditPosition || gameMode == IcsExamining ||
16649         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16650         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16651                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16652                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16653         gameMode == Training) PopUpMoveDialog(firstChar);
16654 }
16655
16656 void
16657 TypeInDoneEvent (char *move)
16658 {
16659         Board board;
16660         int n, fromX, fromY, toX, toY;
16661         char promoChar;
16662         ChessMove moveType;
16663
16664         // [HGM] FENedit
16665         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16666                 EditPositionPasteFEN(move);
16667                 return;
16668         }
16669         // [HGM] movenum: allow move number to be typed in any mode
16670         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16671           ToNrEvent(2*n-1);
16672           return;
16673         }
16674         // undocumented kludge: allow command-line option to be typed in!
16675         // (potentially fatal, and does not implement the effect of the option.)
16676         // should only be used for options that are values on which future decisions will be made,
16677         // and definitely not on options that would be used during initialization.
16678         if(strstr(move, "!!! -") == move) {
16679             ParseArgsFromString(move+4);
16680             return;
16681         }
16682
16683       if (gameMode != EditGame && currentMove != forwardMostMove &&
16684         gameMode != Training) {
16685         DisplayMoveError(_("Displayed move is not current"));
16686       } else {
16687         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16688           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16689         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16690         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16691           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16692           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16693         } else {
16694           DisplayMoveError(_("Could not parse move"));
16695         }
16696       }
16697 }
16698
16699 void
16700 DisplayMove (int moveNumber)
16701 {
16702     char message[MSG_SIZ];
16703     char res[MSG_SIZ];
16704     char cpThinkOutput[MSG_SIZ];
16705
16706     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16707
16708     if (moveNumber == forwardMostMove - 1 ||
16709         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16710
16711         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16712
16713         if (strchr(cpThinkOutput, '\n')) {
16714             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16715         }
16716     } else {
16717         *cpThinkOutput = NULLCHAR;
16718     }
16719
16720     /* [AS] Hide thinking from human user */
16721     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16722         *cpThinkOutput = NULLCHAR;
16723         if( thinkOutput[0] != NULLCHAR ) {
16724             int i;
16725
16726             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16727                 cpThinkOutput[i] = '.';
16728             }
16729             cpThinkOutput[i] = NULLCHAR;
16730             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16731         }
16732     }
16733
16734     if (moveNumber == forwardMostMove - 1 &&
16735         gameInfo.resultDetails != NULL) {
16736         if (gameInfo.resultDetails[0] == NULLCHAR) {
16737           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16738         } else {
16739           snprintf(res, MSG_SIZ, " {%s} %s",
16740                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16741         }
16742     } else {
16743         res[0] = NULLCHAR;
16744     }
16745
16746     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16747         DisplayMessage(res, cpThinkOutput);
16748     } else {
16749       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16750                 WhiteOnMove(moveNumber) ? " " : ".. ",
16751                 parseList[moveNumber], res);
16752         DisplayMessage(message, cpThinkOutput);
16753     }
16754 }
16755
16756 void
16757 DisplayComment (int moveNumber, char *text)
16758 {
16759     char title[MSG_SIZ];
16760
16761     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16762       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16763     } else {
16764       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16765               WhiteOnMove(moveNumber) ? " " : ".. ",
16766               parseList[moveNumber]);
16767     }
16768     if (text != NULL && (appData.autoDisplayComment || commentUp))
16769         CommentPopUp(title, text);
16770 }
16771
16772 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16773  * might be busy thinking or pondering.  It can be omitted if your
16774  * gnuchess is configured to stop thinking immediately on any user
16775  * input.  However, that gnuchess feature depends on the FIONREAD
16776  * ioctl, which does not work properly on some flavors of Unix.
16777  */
16778 void
16779 Attention (ChessProgramState *cps)
16780 {
16781 #if ATTENTION
16782     if (!cps->useSigint) return;
16783     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16784     switch (gameMode) {
16785       case MachinePlaysWhite:
16786       case MachinePlaysBlack:
16787       case TwoMachinesPlay:
16788       case IcsPlayingWhite:
16789       case IcsPlayingBlack:
16790       case AnalyzeMode:
16791       case AnalyzeFile:
16792         /* Skip if we know it isn't thinking */
16793         if (!cps->maybeThinking) return;
16794         if (appData.debugMode)
16795           fprintf(debugFP, "Interrupting %s\n", cps->which);
16796         InterruptChildProcess(cps->pr);
16797         cps->maybeThinking = FALSE;
16798         break;
16799       default:
16800         break;
16801     }
16802 #endif /*ATTENTION*/
16803 }
16804
16805 int
16806 CheckFlags ()
16807 {
16808     if (whiteTimeRemaining <= 0) {
16809         if (!whiteFlag) {
16810             whiteFlag = TRUE;
16811             if (appData.icsActive) {
16812                 if (appData.autoCallFlag &&
16813                     gameMode == IcsPlayingBlack && !blackFlag) {
16814                   SendToICS(ics_prefix);
16815                   SendToICS("flag\n");
16816                 }
16817             } else {
16818                 if (blackFlag) {
16819                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16820                 } else {
16821                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
16822                     if (appData.autoCallFlag) {
16823                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
16824                         return TRUE;
16825                     }
16826                 }
16827             }
16828         }
16829     }
16830     if (blackTimeRemaining <= 0) {
16831         if (!blackFlag) {
16832             blackFlag = TRUE;
16833             if (appData.icsActive) {
16834                 if (appData.autoCallFlag &&
16835                     gameMode == IcsPlayingWhite && !whiteFlag) {
16836                   SendToICS(ics_prefix);
16837                   SendToICS("flag\n");
16838                 }
16839             } else {
16840                 if (whiteFlag) {
16841                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
16842                 } else {
16843                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
16844                     if (appData.autoCallFlag) {
16845                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
16846                         return TRUE;
16847                     }
16848                 }
16849             }
16850         }
16851     }
16852     return FALSE;
16853 }
16854
16855 void
16856 CheckTimeControl ()
16857 {
16858     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16859         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16860
16861     /*
16862      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16863      */
16864     if ( !WhiteOnMove(forwardMostMove) ) {
16865         /* White made time control */
16866         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16867         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16868         /* [HGM] time odds: correct new time quota for time odds! */
16869                                             / WhitePlayer()->timeOdds;
16870         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16871     } else {
16872         lastBlack -= blackTimeRemaining;
16873         /* Black made time control */
16874         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16875                                             / WhitePlayer()->other->timeOdds;
16876         lastWhite = whiteTimeRemaining;
16877     }
16878 }
16879
16880 void
16881 DisplayBothClocks ()
16882 {
16883     int wom = gameMode == EditPosition ?
16884       !blackPlaysFirst : WhiteOnMove(currentMove);
16885     DisplayWhiteClock(whiteTimeRemaining, wom);
16886     DisplayBlackClock(blackTimeRemaining, !wom);
16887 }
16888
16889
16890 /* Timekeeping seems to be a portability nightmare.  I think everyone
16891    has ftime(), but I'm really not sure, so I'm including some ifdefs
16892    to use other calls if you don't.  Clocks will be less accurate if
16893    you have neither ftime nor gettimeofday.
16894 */
16895
16896 /* VS 2008 requires the #include outside of the function */
16897 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16898 #include <sys/timeb.h>
16899 #endif
16900
16901 /* Get the current time as a TimeMark */
16902 void
16903 GetTimeMark (TimeMark *tm)
16904 {
16905 #if HAVE_GETTIMEOFDAY
16906
16907     struct timeval timeVal;
16908     struct timezone timeZone;
16909
16910     gettimeofday(&timeVal, &timeZone);
16911     tm->sec = (long) timeVal.tv_sec;
16912     tm->ms = (int) (timeVal.tv_usec / 1000L);
16913
16914 #else /*!HAVE_GETTIMEOFDAY*/
16915 #if HAVE_FTIME
16916
16917 // include <sys/timeb.h> / moved to just above start of function
16918     struct timeb timeB;
16919
16920     ftime(&timeB);
16921     tm->sec = (long) timeB.time;
16922     tm->ms = (int) timeB.millitm;
16923
16924 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16925     tm->sec = (long) time(NULL);
16926     tm->ms = 0;
16927 #endif
16928 #endif
16929 }
16930
16931 /* Return the difference in milliseconds between two
16932    time marks.  We assume the difference will fit in a long!
16933 */
16934 long
16935 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16936 {
16937     return 1000L*(tm2->sec - tm1->sec) +
16938            (long) (tm2->ms - tm1->ms);
16939 }
16940
16941
16942 /*
16943  * Code to manage the game clocks.
16944  *
16945  * In tournament play, black starts the clock and then white makes a move.
16946  * We give the human user a slight advantage if he is playing white---the
16947  * clocks don't run until he makes his first move, so it takes zero time.
16948  * Also, we don't account for network lag, so we could get out of sync
16949  * with GNU Chess's clock -- but then, referees are always right.
16950  */
16951
16952 static TimeMark tickStartTM;
16953 static long intendedTickLength;
16954
16955 long
16956 NextTickLength (long timeRemaining)
16957 {
16958     long nominalTickLength, nextTickLength;
16959
16960     if (timeRemaining > 0L && timeRemaining <= 10000L)
16961       nominalTickLength = 100L;
16962     else
16963       nominalTickLength = 1000L;
16964     nextTickLength = timeRemaining % nominalTickLength;
16965     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16966
16967     return nextTickLength;
16968 }
16969
16970 /* Adjust clock one minute up or down */
16971 void
16972 AdjustClock (Boolean which, int dir)
16973 {
16974     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16975     if(which) blackTimeRemaining += 60000*dir;
16976     else      whiteTimeRemaining += 60000*dir;
16977     DisplayBothClocks();
16978     adjustedClock = TRUE;
16979 }
16980
16981 /* Stop clocks and reset to a fresh time control */
16982 void
16983 ResetClocks ()
16984 {
16985     (void) StopClockTimer();
16986     if (appData.icsActive) {
16987         whiteTimeRemaining = blackTimeRemaining = 0;
16988     } else if (searchTime) {
16989         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16990         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16991     } else { /* [HGM] correct new time quote for time odds */
16992         whiteTC = blackTC = fullTimeControlString;
16993         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16994         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16995     }
16996     if (whiteFlag || blackFlag) {
16997         DisplayTitle("");
16998         whiteFlag = blackFlag = FALSE;
16999     }
17000     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17001     DisplayBothClocks();
17002     adjustedClock = FALSE;
17003 }
17004
17005 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17006
17007 /* Decrement running clock by amount of time that has passed */
17008 void
17009 DecrementClocks ()
17010 {
17011     long timeRemaining;
17012     long lastTickLength, fudge;
17013     TimeMark now;
17014
17015     if (!appData.clockMode) return;
17016     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17017
17018     GetTimeMark(&now);
17019
17020     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17021
17022     /* Fudge if we woke up a little too soon */
17023     fudge = intendedTickLength - lastTickLength;
17024     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17025
17026     if (WhiteOnMove(forwardMostMove)) {
17027         if(whiteNPS >= 0) lastTickLength = 0;
17028         timeRemaining = whiteTimeRemaining -= lastTickLength;
17029         if(timeRemaining < 0 && !appData.icsActive) {
17030             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17031             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17032                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17033                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17034             }
17035         }
17036         DisplayWhiteClock(whiteTimeRemaining - fudge,
17037                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17038     } else {
17039         if(blackNPS >= 0) lastTickLength = 0;
17040         timeRemaining = blackTimeRemaining -= lastTickLength;
17041         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17042             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17043             if(suddenDeath) {
17044                 blackStartMove = forwardMostMove;
17045                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17046             }
17047         }
17048         DisplayBlackClock(blackTimeRemaining - fudge,
17049                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17050     }
17051     if (CheckFlags()) return;
17052
17053     if(twoBoards) { // count down secondary board's clocks as well
17054         activePartnerTime -= lastTickLength;
17055         partnerUp = 1;
17056         if(activePartner == 'W')
17057             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17058         else
17059             DisplayBlackClock(activePartnerTime, TRUE);
17060         partnerUp = 0;
17061     }
17062
17063     tickStartTM = now;
17064     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17065     StartClockTimer(intendedTickLength);
17066
17067     /* if the time remaining has fallen below the alarm threshold, sound the
17068      * alarm. if the alarm has sounded and (due to a takeback or time control
17069      * with increment) the time remaining has increased to a level above the
17070      * threshold, reset the alarm so it can sound again.
17071      */
17072
17073     if (appData.icsActive && appData.icsAlarm) {
17074
17075         /* make sure we are dealing with the user's clock */
17076         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17077                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17078            )) return;
17079
17080         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17081             alarmSounded = FALSE;
17082         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17083             PlayAlarmSound();
17084             alarmSounded = TRUE;
17085         }
17086     }
17087 }
17088
17089
17090 /* A player has just moved, so stop the previously running
17091    clock and (if in clock mode) start the other one.
17092    We redisplay both clocks in case we're in ICS mode, because
17093    ICS gives us an update to both clocks after every move.
17094    Note that this routine is called *after* forwardMostMove
17095    is updated, so the last fractional tick must be subtracted
17096    from the color that is *not* on move now.
17097 */
17098 void
17099 SwitchClocks (int newMoveNr)
17100 {
17101     long lastTickLength;
17102     TimeMark now;
17103     int flagged = FALSE;
17104
17105     GetTimeMark(&now);
17106
17107     if (StopClockTimer() && appData.clockMode) {
17108         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17109         if (!WhiteOnMove(forwardMostMove)) {
17110             if(blackNPS >= 0) lastTickLength = 0;
17111             blackTimeRemaining -= lastTickLength;
17112            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17113 //         if(pvInfoList[forwardMostMove].time == -1)
17114                  pvInfoList[forwardMostMove].time =               // use GUI time
17115                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17116         } else {
17117            if(whiteNPS >= 0) lastTickLength = 0;
17118            whiteTimeRemaining -= lastTickLength;
17119            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17120 //         if(pvInfoList[forwardMostMove].time == -1)
17121                  pvInfoList[forwardMostMove].time =
17122                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17123         }
17124         flagged = CheckFlags();
17125     }
17126     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17127     CheckTimeControl();
17128
17129     if (flagged || !appData.clockMode) return;
17130
17131     switch (gameMode) {
17132       case MachinePlaysBlack:
17133       case MachinePlaysWhite:
17134       case BeginningOfGame:
17135         if (pausing) return;
17136         break;
17137
17138       case EditGame:
17139       case PlayFromGameFile:
17140       case IcsExamining:
17141         return;
17142
17143       default:
17144         break;
17145     }
17146
17147     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17148         if(WhiteOnMove(forwardMostMove))
17149              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17150         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17151     }
17152
17153     tickStartTM = now;
17154     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17155       whiteTimeRemaining : blackTimeRemaining);
17156     StartClockTimer(intendedTickLength);
17157 }
17158
17159
17160 /* Stop both clocks */
17161 void
17162 StopClocks ()
17163 {
17164     long lastTickLength;
17165     TimeMark now;
17166
17167     if (!StopClockTimer()) return;
17168     if (!appData.clockMode) return;
17169
17170     GetTimeMark(&now);
17171
17172     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17173     if (WhiteOnMove(forwardMostMove)) {
17174         if(whiteNPS >= 0) lastTickLength = 0;
17175         whiteTimeRemaining -= lastTickLength;
17176         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17177     } else {
17178         if(blackNPS >= 0) lastTickLength = 0;
17179         blackTimeRemaining -= lastTickLength;
17180         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17181     }
17182     CheckFlags();
17183 }
17184
17185 /* Start clock of player on move.  Time may have been reset, so
17186    if clock is already running, stop and restart it. */
17187 void
17188 StartClocks ()
17189 {
17190     (void) StopClockTimer(); /* in case it was running already */
17191     DisplayBothClocks();
17192     if (CheckFlags()) return;
17193
17194     if (!appData.clockMode) return;
17195     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17196
17197     GetTimeMark(&tickStartTM);
17198     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17199       whiteTimeRemaining : blackTimeRemaining);
17200
17201    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17202     whiteNPS = blackNPS = -1;
17203     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17204        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17205         whiteNPS = first.nps;
17206     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17207        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17208         blackNPS = first.nps;
17209     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17210         whiteNPS = second.nps;
17211     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17212         blackNPS = second.nps;
17213     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17214
17215     StartClockTimer(intendedTickLength);
17216 }
17217
17218 char *
17219 TimeString (long ms)
17220 {
17221     long second, minute, hour, day;
17222     char *sign = "";
17223     static char buf[32];
17224
17225     if (ms > 0 && ms <= 9900) {
17226       /* convert milliseconds to tenths, rounding up */
17227       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17228
17229       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17230       return buf;
17231     }
17232
17233     /* convert milliseconds to seconds, rounding up */
17234     /* use floating point to avoid strangeness of integer division
17235        with negative dividends on many machines */
17236     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17237
17238     if (second < 0) {
17239         sign = "-";
17240         second = -second;
17241     }
17242
17243     day = second / (60 * 60 * 24);
17244     second = second % (60 * 60 * 24);
17245     hour = second / (60 * 60);
17246     second = second % (60 * 60);
17247     minute = second / 60;
17248     second = second % 60;
17249
17250     if (day > 0)
17251       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17252               sign, day, hour, minute, second);
17253     else if (hour > 0)
17254       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17255     else
17256       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17257
17258     return buf;
17259 }
17260
17261
17262 /*
17263  * This is necessary because some C libraries aren't ANSI C compliant yet.
17264  */
17265 char *
17266 StrStr (char *string, char *match)
17267 {
17268     int i, length;
17269
17270     length = strlen(match);
17271
17272     for (i = strlen(string) - length; i >= 0; i--, string++)
17273       if (!strncmp(match, string, length))
17274         return string;
17275
17276     return NULL;
17277 }
17278
17279 char *
17280 StrCaseStr (char *string, char *match)
17281 {
17282     int i, j, length;
17283
17284     length = strlen(match);
17285
17286     for (i = strlen(string) - length; i >= 0; i--, string++) {
17287         for (j = 0; j < length; j++) {
17288             if (ToLower(match[j]) != ToLower(string[j]))
17289               break;
17290         }
17291         if (j == length) return string;
17292     }
17293
17294     return NULL;
17295 }
17296
17297 #ifndef _amigados
17298 int
17299 StrCaseCmp (char *s1, char *s2)
17300 {
17301     char c1, c2;
17302
17303     for (;;) {
17304         c1 = ToLower(*s1++);
17305         c2 = ToLower(*s2++);
17306         if (c1 > c2) return 1;
17307         if (c1 < c2) return -1;
17308         if (c1 == NULLCHAR) return 0;
17309     }
17310 }
17311
17312
17313 int
17314 ToLower (int c)
17315 {
17316     return isupper(c) ? tolower(c) : c;
17317 }
17318
17319
17320 int
17321 ToUpper (int c)
17322 {
17323     return islower(c) ? toupper(c) : c;
17324 }
17325 #endif /* !_amigados    */
17326
17327 char *
17328 StrSave (char *s)
17329 {
17330   char *ret;
17331
17332   if ((ret = (char *) malloc(strlen(s) + 1)))
17333     {
17334       safeStrCpy(ret, s, strlen(s)+1);
17335     }
17336   return ret;
17337 }
17338
17339 char *
17340 StrSavePtr (char *s, char **savePtr)
17341 {
17342     if (*savePtr) {
17343         free(*savePtr);
17344     }
17345     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17346       safeStrCpy(*savePtr, s, strlen(s)+1);
17347     }
17348     return(*savePtr);
17349 }
17350
17351 char *
17352 PGNDate ()
17353 {
17354     time_t clock;
17355     struct tm *tm;
17356     char buf[MSG_SIZ];
17357
17358     clock = time((time_t *)NULL);
17359     tm = localtime(&clock);
17360     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17361             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17362     return StrSave(buf);
17363 }
17364
17365
17366 char *
17367 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17368 {
17369     int i, j, fromX, fromY, toX, toY;
17370     int whiteToPlay;
17371     char buf[MSG_SIZ];
17372     char *p, *q;
17373     int emptycount;
17374     ChessSquare piece;
17375
17376     whiteToPlay = (gameMode == EditPosition) ?
17377       !blackPlaysFirst : (move % 2 == 0);
17378     p = buf;
17379
17380     /* Piece placement data */
17381     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17382         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17383         emptycount = 0;
17384         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17385             if (boards[move][i][j] == EmptySquare) {
17386                 emptycount++;
17387             } else { ChessSquare piece = boards[move][i][j];
17388                 if (emptycount > 0) {
17389                     if(emptycount<10) /* [HGM] can be >= 10 */
17390                         *p++ = '0' + emptycount;
17391                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17392                     emptycount = 0;
17393                 }
17394                 if(PieceToChar(piece) == '+') {
17395                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17396                     *p++ = '+';
17397                     piece = (ChessSquare)(DEMOTED piece);
17398                 }
17399                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17400                 if(p[-1] == '~') {
17401                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17402                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17403                     *p++ = '~';
17404                 }
17405             }
17406         }
17407         if (emptycount > 0) {
17408             if(emptycount<10) /* [HGM] can be >= 10 */
17409                 *p++ = '0' + emptycount;
17410             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17411             emptycount = 0;
17412         }
17413         *p++ = '/';
17414     }
17415     *(p - 1) = ' ';
17416
17417     /* [HGM] print Crazyhouse or Shogi holdings */
17418     if( gameInfo.holdingsWidth ) {
17419         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17420         q = p;
17421         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17422             piece = boards[move][i][BOARD_WIDTH-1];
17423             if( piece != EmptySquare )
17424               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17425                   *p++ = PieceToChar(piece);
17426         }
17427         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17428             piece = boards[move][BOARD_HEIGHT-i-1][0];
17429             if( piece != EmptySquare )
17430               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17431                   *p++ = PieceToChar(piece);
17432         }
17433
17434         if( q == p ) *p++ = '-';
17435         *p++ = ']';
17436         *p++ = ' ';
17437     }
17438
17439     /* Active color */
17440     *p++ = whiteToPlay ? 'w' : 'b';
17441     *p++ = ' ';
17442
17443   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17444     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17445   } else {
17446   if(nrCastlingRights) {
17447      q = p;
17448      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17449        /* [HGM] write directly from rights */
17450            if(boards[move][CASTLING][2] != NoRights &&
17451               boards[move][CASTLING][0] != NoRights   )
17452                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17453            if(boards[move][CASTLING][2] != NoRights &&
17454               boards[move][CASTLING][1] != NoRights   )
17455                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17456            if(boards[move][CASTLING][5] != NoRights &&
17457               boards[move][CASTLING][3] != NoRights   )
17458                 *p++ = boards[move][CASTLING][3] + AAA;
17459            if(boards[move][CASTLING][5] != NoRights &&
17460               boards[move][CASTLING][4] != NoRights   )
17461                 *p++ = boards[move][CASTLING][4] + AAA;
17462      } else {
17463
17464         /* [HGM] write true castling rights */
17465         if( nrCastlingRights == 6 ) {
17466             int q, k=0;
17467             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17468                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17469             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17470                  boards[move][CASTLING][2] != NoRights  );
17471             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17472                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17473                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17474                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17475                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17476             }
17477             if(q) *p++ = 'Q';
17478             k = 0;
17479             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17480                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17481             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17482                  boards[move][CASTLING][5] != NoRights  );
17483             if(gameInfo.variant == VariantSChess) {
17484                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17485                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17486                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17487                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17488             }
17489             if(q) *p++ = 'q';
17490         }
17491      }
17492      if (q == p) *p++ = '-'; /* No castling rights */
17493      *p++ = ' ';
17494   }
17495
17496   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17497      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17498      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17499     /* En passant target square */
17500     if (move > backwardMostMove) {
17501         fromX = moveList[move - 1][0] - AAA;
17502         fromY = moveList[move - 1][1] - ONE;
17503         toX = moveList[move - 1][2] - AAA;
17504         toY = moveList[move - 1][3] - ONE;
17505         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17506             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17507             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17508             fromX == toX) {
17509             /* 2-square pawn move just happened */
17510             *p++ = toX + AAA;
17511             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17512         } else {
17513             *p++ = '-';
17514         }
17515     } else if(move == backwardMostMove) {
17516         // [HGM] perhaps we should always do it like this, and forget the above?
17517         if((signed char)boards[move][EP_STATUS] >= 0) {
17518             *p++ = boards[move][EP_STATUS] + AAA;
17519             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17520         } else {
17521             *p++ = '-';
17522         }
17523     } else {
17524         *p++ = '-';
17525     }
17526     *p++ = ' ';
17527   }
17528   }
17529
17530     if(moveCounts)
17531     {   int i = 0, j=move;
17532
17533         /* [HGM] find reversible plies */
17534         if (appData.debugMode) { int k;
17535             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17536             for(k=backwardMostMove; k<=forwardMostMove; k++)
17537                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17538
17539         }
17540
17541         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17542         if( j == backwardMostMove ) i += initialRulePlies;
17543         sprintf(p, "%d ", i);
17544         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17545
17546         /* Fullmove number */
17547         sprintf(p, "%d", (move / 2) + 1);
17548     } else *--p = NULLCHAR;
17549
17550     return StrSave(buf);
17551 }
17552
17553 Boolean
17554 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17555 {
17556     int i, j, k, w=0;
17557     char *p, c;
17558     int emptycount, virgin[BOARD_FILES];
17559     ChessSquare piece;
17560
17561     p = fen;
17562
17563     /* Piece placement data */
17564     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17565         j = 0;
17566         for (;;) {
17567             if (*p == '/' || *p == ' ' || *p == '[' ) {
17568                 if(j > w) w = j;
17569                 emptycount = gameInfo.boardWidth - j;
17570                 while (emptycount--)
17571                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17572                 if (*p == '/') p++;
17573                 else if(autoSize) { // we stumbled unexpectedly into end of board
17574                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17575                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17576                     }
17577                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17578                 }
17579                 break;
17580 #if(BOARD_FILES >= 10)
17581             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17582                 p++; emptycount=10;
17583                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17584                 while (emptycount--)
17585                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17586 #endif
17587             } else if (*p == '*') {
17588                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17589             } else if (isdigit(*p)) {
17590                 emptycount = *p++ - '0';
17591                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17592                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17593                 while (emptycount--)
17594                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17595             } else if (*p == '+' || isalpha(*p)) {
17596                 if (j >= gameInfo.boardWidth) return FALSE;
17597                 if(*p=='+') {
17598                     piece = CharToPiece(*++p);
17599                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17600                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17601                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17602                 } else piece = CharToPiece(*p++);
17603
17604                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17605                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17606                     piece = (ChessSquare) (PROMOTED piece);
17607                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17608                     p++;
17609                 }
17610                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17611             } else {
17612                 return FALSE;
17613             }
17614         }
17615     }
17616     while (*p == '/' || *p == ' ') p++;
17617
17618     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17619
17620     /* [HGM] by default clear Crazyhouse holdings, if present */
17621     if(gameInfo.holdingsWidth) {
17622        for(i=0; i<BOARD_HEIGHT; i++) {
17623            board[i][0]             = EmptySquare; /* black holdings */
17624            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17625            board[i][1]             = (ChessSquare) 0; /* black counts */
17626            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17627        }
17628     }
17629
17630     /* [HGM] look for Crazyhouse holdings here */
17631     while(*p==' ') p++;
17632     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17633         if(*p == '[') p++;
17634         if(*p == '-' ) p++; /* empty holdings */ else {
17635             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17636             /* if we would allow FEN reading to set board size, we would   */
17637             /* have to add holdings and shift the board read so far here   */
17638             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17639                 p++;
17640                 if((int) piece >= (int) BlackPawn ) {
17641                     i = (int)piece - (int)BlackPawn;
17642                     i = PieceToNumber((ChessSquare)i);
17643                     if( i >= gameInfo.holdingsSize ) return FALSE;
17644                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17645                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17646                 } else {
17647                     i = (int)piece - (int)WhitePawn;
17648                     i = PieceToNumber((ChessSquare)i);
17649                     if( i >= gameInfo.holdingsSize ) return FALSE;
17650                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17651                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17652                 }
17653             }
17654         }
17655         if(*p == ']') p++;
17656     }
17657
17658     while(*p == ' ') p++;
17659
17660     /* Active color */
17661     c = *p++;
17662     if(appData.colorNickNames) {
17663       if( c == appData.colorNickNames[0] ) c = 'w'; else
17664       if( c == appData.colorNickNames[1] ) c = 'b';
17665     }
17666     switch (c) {
17667       case 'w':
17668         *blackPlaysFirst = FALSE;
17669         break;
17670       case 'b':
17671         *blackPlaysFirst = TRUE;
17672         break;
17673       default:
17674         return FALSE;
17675     }
17676
17677     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17678     /* return the extra info in global variiables             */
17679
17680     /* set defaults in case FEN is incomplete */
17681     board[EP_STATUS] = EP_UNKNOWN;
17682     for(i=0; i<nrCastlingRights; i++ ) {
17683         board[CASTLING][i] =
17684             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17685     }   /* assume possible unless obviously impossible */
17686     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17687     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17688     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17689                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17690     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17691     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17692     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17693                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17694     FENrulePlies = 0;
17695
17696     while(*p==' ') p++;
17697     if(nrCastlingRights) {
17698       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17699       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17700           /* castling indicator present, so default becomes no castlings */
17701           for(i=0; i<nrCastlingRights; i++ ) {
17702                  board[CASTLING][i] = NoRights;
17703           }
17704       }
17705       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17706              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17707              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17708              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17709         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17710
17711         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17712             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17713             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17714         }
17715         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17716             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17717         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17718                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17719         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17720                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17721         switch(c) {
17722           case'K':
17723               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17724               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17725               board[CASTLING][2] = whiteKingFile;
17726               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17727               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17728               break;
17729           case'Q':
17730               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17731               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17732               board[CASTLING][2] = whiteKingFile;
17733               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17734               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17735               break;
17736           case'k':
17737               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17738               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17739               board[CASTLING][5] = blackKingFile;
17740               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17741               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17742               break;
17743           case'q':
17744               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17745               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17746               board[CASTLING][5] = blackKingFile;
17747               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17748               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17749           case '-':
17750               break;
17751           default: /* FRC castlings */
17752               if(c >= 'a') { /* black rights */
17753                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17754                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17755                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17756                   if(i == BOARD_RGHT) break;
17757                   board[CASTLING][5] = i;
17758                   c -= AAA;
17759                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17760                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17761                   if(c > i)
17762                       board[CASTLING][3] = c;
17763                   else
17764                       board[CASTLING][4] = c;
17765               } else { /* white rights */
17766                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17767                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17768                     if(board[0][i] == WhiteKing) break;
17769                   if(i == BOARD_RGHT) break;
17770                   board[CASTLING][2] = i;
17771                   c -= AAA - 'a' + 'A';
17772                   if(board[0][c] >= WhiteKing) break;
17773                   if(c > i)
17774                       board[CASTLING][0] = c;
17775                   else
17776                       board[CASTLING][1] = c;
17777               }
17778         }
17779       }
17780       for(i=0; i<nrCastlingRights; i++)
17781         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17782       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17783     if (appData.debugMode) {
17784         fprintf(debugFP, "FEN castling rights:");
17785         for(i=0; i<nrCastlingRights; i++)
17786         fprintf(debugFP, " %d", board[CASTLING][i]);
17787         fprintf(debugFP, "\n");
17788     }
17789
17790       while(*p==' ') p++;
17791     }
17792
17793     /* read e.p. field in games that know e.p. capture */
17794     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17795        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17796        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17797       if(*p=='-') {
17798         p++; board[EP_STATUS] = EP_NONE;
17799       } else {
17800          char c = *p++ - AAA;
17801
17802          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17803          if(*p >= '0' && *p <='9') p++;
17804          board[EP_STATUS] = c;
17805       }
17806     }
17807
17808
17809     if(sscanf(p, "%d", &i) == 1) {
17810         FENrulePlies = i; /* 50-move ply counter */
17811         /* (The move number is still ignored)    */
17812     }
17813
17814     return TRUE;
17815 }
17816
17817 void
17818 EditPositionPasteFEN (char *fen)
17819 {
17820   if (fen != NULL) {
17821     Board initial_position;
17822
17823     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
17824       DisplayError(_("Bad FEN position in clipboard"), 0);
17825       return ;
17826     } else {
17827       int savedBlackPlaysFirst = blackPlaysFirst;
17828       EditPositionEvent();
17829       blackPlaysFirst = savedBlackPlaysFirst;
17830       CopyBoard(boards[0], initial_position);
17831       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
17832       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
17833       DisplayBothClocks();
17834       DrawPosition(FALSE, boards[currentMove]);
17835     }
17836   }
17837 }
17838
17839 static char cseq[12] = "\\   ";
17840
17841 Boolean
17842 set_cont_sequence (char *new_seq)
17843 {
17844     int len;
17845     Boolean ret;
17846
17847     // handle bad attempts to set the sequence
17848         if (!new_seq)
17849                 return 0; // acceptable error - no debug
17850
17851     len = strlen(new_seq);
17852     ret = (len > 0) && (len < sizeof(cseq));
17853     if (ret)
17854       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
17855     else if (appData.debugMode)
17856       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
17857     return ret;
17858 }
17859
17860 /*
17861     reformat a source message so words don't cross the width boundary.  internal
17862     newlines are not removed.  returns the wrapped size (no null character unless
17863     included in source message).  If dest is NULL, only calculate the size required
17864     for the dest buffer.  lp argument indicats line position upon entry, and it's
17865     passed back upon exit.
17866 */
17867 int
17868 wrap (char *dest, char *src, int count, int width, int *lp)
17869 {
17870     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
17871
17872     cseq_len = strlen(cseq);
17873     old_line = line = *lp;
17874     ansi = len = clen = 0;
17875
17876     for (i=0; i < count; i++)
17877     {
17878         if (src[i] == '\033')
17879             ansi = 1;
17880
17881         // if we hit the width, back up
17882         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
17883         {
17884             // store i & len in case the word is too long
17885             old_i = i, old_len = len;
17886
17887             // find the end of the last word
17888             while (i && src[i] != ' ' && src[i] != '\n')
17889             {
17890                 i--;
17891                 len--;
17892             }
17893
17894             // word too long?  restore i & len before splitting it
17895             if ((old_i-i+clen) >= width)
17896             {
17897                 i = old_i;
17898                 len = old_len;
17899             }
17900
17901             // extra space?
17902             if (i && src[i-1] == ' ')
17903                 len--;
17904
17905             if (src[i] != ' ' && src[i] != '\n')
17906             {
17907                 i--;
17908                 if (len)
17909                     len--;
17910             }
17911
17912             // now append the newline and continuation sequence
17913             if (dest)
17914                 dest[len] = '\n';
17915             len++;
17916             if (dest)
17917                 strncpy(dest+len, cseq, cseq_len);
17918             len += cseq_len;
17919             line = cseq_len;
17920             clen = cseq_len;
17921             continue;
17922         }
17923
17924         if (dest)
17925             dest[len] = src[i];
17926         len++;
17927         if (!ansi)
17928             line++;
17929         if (src[i] == '\n')
17930             line = 0;
17931         if (src[i] == 'm')
17932             ansi = 0;
17933     }
17934     if (dest && appData.debugMode)
17935     {
17936         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17937             count, width, line, len, *lp);
17938         show_bytes(debugFP, src, count);
17939         fprintf(debugFP, "\ndest: ");
17940         show_bytes(debugFP, dest, len);
17941         fprintf(debugFP, "\n");
17942     }
17943     *lp = dest ? line : old_line;
17944
17945     return len;
17946 }
17947
17948 // [HGM] vari: routines for shelving variations
17949 Boolean modeRestore = FALSE;
17950
17951 void
17952 PushInner (int firstMove, int lastMove)
17953 {
17954         int i, j, nrMoves = lastMove - firstMove;
17955
17956         // push current tail of game on stack
17957         savedResult[storedGames] = gameInfo.result;
17958         savedDetails[storedGames] = gameInfo.resultDetails;
17959         gameInfo.resultDetails = NULL;
17960         savedFirst[storedGames] = firstMove;
17961         savedLast [storedGames] = lastMove;
17962         savedFramePtr[storedGames] = framePtr;
17963         framePtr -= nrMoves; // reserve space for the boards
17964         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17965             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17966             for(j=0; j<MOVE_LEN; j++)
17967                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17968             for(j=0; j<2*MOVE_LEN; j++)
17969                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17970             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17971             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17972             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17973             pvInfoList[firstMove+i-1].depth = 0;
17974             commentList[framePtr+i] = commentList[firstMove+i];
17975             commentList[firstMove+i] = NULL;
17976         }
17977
17978         storedGames++;
17979         forwardMostMove = firstMove; // truncate game so we can start variation
17980 }
17981
17982 void
17983 PushTail (int firstMove, int lastMove)
17984 {
17985         if(appData.icsActive) { // only in local mode
17986                 forwardMostMove = currentMove; // mimic old ICS behavior
17987                 return;
17988         }
17989         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17990
17991         PushInner(firstMove, lastMove);
17992         if(storedGames == 1) GreyRevert(FALSE);
17993         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17994 }
17995
17996 void
17997 PopInner (Boolean annotate)
17998 {
17999         int i, j, nrMoves;
18000         char buf[8000], moveBuf[20];
18001
18002         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18003         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18004         nrMoves = savedLast[storedGames] - currentMove;
18005         if(annotate) {
18006                 int cnt = 10;
18007                 if(!WhiteOnMove(currentMove))
18008                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18009                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18010                 for(i=currentMove; i<forwardMostMove; i++) {
18011                         if(WhiteOnMove(i))
18012                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18013                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18014                         strcat(buf, moveBuf);
18015                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18016                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18017                 }
18018                 strcat(buf, ")");
18019         }
18020         for(i=1; i<=nrMoves; i++) { // copy last variation back
18021             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18022             for(j=0; j<MOVE_LEN; j++)
18023                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18024             for(j=0; j<2*MOVE_LEN; j++)
18025                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18026             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18027             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18028             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18029             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18030             commentList[currentMove+i] = commentList[framePtr+i];
18031             commentList[framePtr+i] = NULL;
18032         }
18033         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18034         framePtr = savedFramePtr[storedGames];
18035         gameInfo.result = savedResult[storedGames];
18036         if(gameInfo.resultDetails != NULL) {
18037             free(gameInfo.resultDetails);
18038       }
18039         gameInfo.resultDetails = savedDetails[storedGames];
18040         forwardMostMove = currentMove + nrMoves;
18041 }
18042
18043 Boolean
18044 PopTail (Boolean annotate)
18045 {
18046         if(appData.icsActive) return FALSE; // only in local mode
18047         if(!storedGames) return FALSE; // sanity
18048         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18049
18050         PopInner(annotate);
18051         if(currentMove < forwardMostMove) ForwardEvent(); else
18052         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18053
18054         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18055         return TRUE;
18056 }
18057
18058 void
18059 CleanupTail ()
18060 {       // remove all shelved variations
18061         int i;
18062         for(i=0; i<storedGames; i++) {
18063             if(savedDetails[i])
18064                 free(savedDetails[i]);
18065             savedDetails[i] = NULL;
18066         }
18067         for(i=framePtr; i<MAX_MOVES; i++) {
18068                 if(commentList[i]) free(commentList[i]);
18069                 commentList[i] = NULL;
18070         }
18071         framePtr = MAX_MOVES-1;
18072         storedGames = 0;
18073 }
18074
18075 void
18076 LoadVariation (int index, char *text)
18077 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18078         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18079         int level = 0, move;
18080
18081         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18082         // first find outermost bracketing variation
18083         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18084             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18085                 if(*p == '{') wait = '}'; else
18086                 if(*p == '[') wait = ']'; else
18087                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18088                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18089             }
18090             if(*p == wait) wait = NULLCHAR; // closing ]} found
18091             p++;
18092         }
18093         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18094         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18095         end[1] = NULLCHAR; // clip off comment beyond variation
18096         ToNrEvent(currentMove-1);
18097         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18098         // kludge: use ParsePV() to append variation to game
18099         move = currentMove;
18100         ParsePV(start, TRUE, TRUE);
18101         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18102         ClearPremoveHighlights();
18103         CommentPopDown();
18104         ToNrEvent(currentMove+1);
18105 }
18106
18107 void
18108 LoadTheme ()
18109 {
18110     char *p, *q, buf[MSG_SIZ];
18111     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18112         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18113         ParseArgsFromString(buf);
18114         ActivateTheme(TRUE); // also redo colors
18115         return;
18116     }
18117     p = nickName;
18118     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18119     {
18120         int len;
18121         q = appData.themeNames;
18122         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18123       if(appData.useBitmaps) {
18124         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18125                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18126                 appData.liteBackTextureMode,
18127                 appData.darkBackTextureMode );
18128       } else {
18129         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18130                 Col2Text(2),   // lightSquareColor
18131                 Col2Text(3) ); // darkSquareColor
18132       }
18133       if(appData.useBorder) {
18134         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18135                 appData.border);
18136       } else {
18137         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18138       }
18139       if(appData.useFont) {
18140         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18141                 appData.renderPiecesWithFont,
18142                 appData.fontToPieceTable,
18143                 Col2Text(9),    // appData.fontBackColorWhite
18144                 Col2Text(10) ); // appData.fontForeColorBlack
18145       } else {
18146         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18147                 appData.pieceDirectory);
18148         if(!appData.pieceDirectory[0])
18149           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18150                 Col2Text(0),   // whitePieceColor
18151                 Col2Text(1) ); // blackPieceColor
18152       }
18153       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18154                 Col2Text(4),   // highlightSquareColor
18155                 Col2Text(5) ); // premoveHighlightColor
18156         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18157         if(insert != q) insert[-1] = NULLCHAR;
18158         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18159         if(q)   free(q);
18160     }
18161     ActivateTheme(FALSE);
18162 }